/**
 * useVesselTrackLayer — 항적(Track) 렌더링 hook
 *
 * Mercator: TripsLayer 애니메이션 + ScatterplotLayer 포인트
 * Globe: MapLibre 네이티브 line + circle + symbol(arrow)
 */
import { useCallback, useEffect, useMemo, useRef, type MutableRefObject } from 'react';
import maplibregl from 'maplibre-gl';
import { TripsLayer } from '@deck.gl/geo-layers';
import { PathLayer, ScatterplotLayer } from '@deck.gl/layers';
import { MapboxOverlay } from '@deck.gl/mapbox';
import type { ActiveTrack, NormalizedTrip, TrackPoint } from '../../../entities/vesselTrack/model/types';
import {
  normalizeTrip,
  buildTrackLineGeoJson,
  buildTrackPointsGeoJson,
  getTrackTimeRange,
} from '../../../entities/vesselTrack/lib/buildTrackGeoJson';
import { getTrackLineTooltipHtml, getTrackPointTooltipHtml } from '../lib/tooltips';
import { useNativeMapLayers, type NativeLayerSpec, type NativeSourceConfig } from './useNativeMapLayers';
import type { MapProjectionId } from '../types';

/* ── Constants ──────────────────────────────────────────────────────── */
const TRACK_COLOR: [number, number, number] = [0, 224, 255]; // cyan
const TRACK_COLOR_CSS = `rgb(${TRACK_COLOR.join(',')})`;

// Globe 네이티브 레이어/소스 ID
const LINE_SRC = 'vessel-track-line-src';
const PTS_SRC = 'vessel-track-pts-src';
const LINE_ID = 'vessel-track-line';
const ARROW_ID = 'vessel-track-arrow';
const HITAREA_ID = 'vessel-track-line-hitarea';
const PTS_ID = 'vessel-track-pts';
const PTS_HL_ID = 'vessel-track-pts-highlight';

// Mercator Deck.gl 레이어 ID
const DECK_PATH_ID = 'vessel-track-path';
const DECK_TRIPS_ID = 'vessel-track-trips';
const DECK_POINTS_ID = 'vessel-track-deck-pts';

/* ── Globe 네이티브 레이어 스펙 ────────────────────────────────────── */
const GLOBE_LAYERS: NativeLayerSpec[] = [
  {
    id: LINE_ID,
    type: 'line',
    sourceId: LINE_SRC,
    paint: {
      'line-color': TRACK_COLOR_CSS,
      'line-width': ['interpolate', ['linear'], ['zoom'], 2, 2, 6, 3, 10, 4],
      'line-opacity': 0.8,
    },
    layout: { 'line-cap': 'round', 'line-join': 'round' },
  },
  {
    id: HITAREA_ID,
    type: 'line',
    sourceId: LINE_SRC,
    paint: { 'line-color': 'rgba(0,0,0,0)', 'line-width': 14, 'line-opacity': 0 },
    layout: { 'line-cap': 'round', 'line-join': 'round' },
  },
  {
    id: ARROW_ID,
    type: 'symbol',
    sourceId: LINE_SRC,
    paint: {
      'text-color': TRACK_COLOR_CSS,
      'text-opacity': 0.7,
    },
    layout: {
      'symbol-placement': 'line',
      'text-field': '▶',
      'text-size': 10,
      'symbol-spacing': 80,
      'text-rotation-alignment': 'map',
      'text-allow-overlap': true,
      'text-ignore-placement': true,
    },
  },
  {
    id: PTS_ID,
    type: 'circle',
    sourceId: PTS_SRC,
    paint: {
      'circle-radius': ['interpolate', ['linear'], ['zoom'], 2, 2, 6, 3, 10, 5],
      'circle-color': TRACK_COLOR_CSS,
      'circle-stroke-width': 1,
      'circle-stroke-color': 'rgba(0,0,0,0.5)',
      'circle-opacity': 0.85,
    },
  },
  {
    id: PTS_HL_ID,
    type: 'circle',
    sourceId: PTS_SRC,
    paint: {
      'circle-radius': ['interpolate', ['linear'], ['zoom'], 2, 6, 6, 8, 10, 12],
      'circle-color': '#ffffff',
      'circle-stroke-width': 2,
      'circle-stroke-color': TRACK_COLOR_CSS,
      'circle-opacity': 0,
    },
    filter: ['==', ['get', 'index'], -1],
  },
];

/* ── Animation speed: 전체 궤적을 ~20초에 재생 ────────────────────── */
const ANIM_CYCLE_SEC = 20;

/* ── Hook ──────────────────────────────────────────────────────────── */
/** @deprecated trackReplay store 엔진으로 이관 완료. 유지보수 호환 용도로만 남겨둔다. */
export function useVesselTrackLayer(
  mapRef: MutableRefObject<maplibregl.Map | null>,
  overlayRef: MutableRefObject<MapboxOverlay | null>,
  projectionBusyRef: MutableRefObject<boolean>,
  reorderGlobeFeatureLayers: () => void,
  opts: {
    activeTrack: ActiveTrack | null;
    projection: MapProjectionId;
    mapSyncEpoch: number;
  },
) {
  const { activeTrack, projection, mapSyncEpoch } = opts;

  /* ── 정규화 데이터 ── */
  const normalizedTrip = useMemo<NormalizedTrip | null>(() => {
    if (!activeTrack || activeTrack.points.length < 2) return null;
    return normalizeTrip(activeTrack, TRACK_COLOR);
  }, [activeTrack]);

  const timeRange = useMemo(() => {
    if (!normalizedTrip) return null;
    return getTrackTimeRange(normalizedTrip);
  }, [normalizedTrip]);

  /* ── Globe 네이티브 GeoJSON ── */
  const lineGeoJson = useMemo(() => {
    if (!activeTrack || activeTrack.points.length < 2) return null;
    return buildTrackLineGeoJson(activeTrack);
  }, [activeTrack]);

  const pointsGeoJson = useMemo(() => {
    if (!activeTrack || activeTrack.points.length === 0) return null;
    return buildTrackPointsGeoJson(activeTrack);
  }, [activeTrack]);

  /* ── Globe 네이티브 레이어 (useNativeMapLayers) ── */
  const globeSources = useMemo<NativeSourceConfig[]>(() => [
    { id: LINE_SRC, data: lineGeoJson, options: { lineMetrics: true } },
    { id: PTS_SRC, data: pointsGeoJson },
  ], [lineGeoJson, pointsGeoJson]);

  const isGlobeVisible = projection === 'globe' && activeTrack != null && activeTrack.points.length >= 2;

  useNativeMapLayers(
    mapRef,
    projectionBusyRef,
    reorderGlobeFeatureLayers,
    {
      sources: globeSources,
      layers: GLOBE_LAYERS,
      visible: isGlobeVisible,
      beforeLayer: ['zones-fill', 'zones-line'],
    },
    [lineGeoJson, pointsGeoJson, isGlobeVisible, projection, mapSyncEpoch],
  );

  /* ── Globe 호버 툴팁 ── */
  const tooltipRef = useRef<maplibregl.Popup | null>(null);

  const clearTooltip = useCallback(() => {
    try { tooltipRef.current?.remove(); } catch { /* ignore */ }
    tooltipRef.current = null;
  }, []);

  useEffect(() => {
    const map = mapRef.current;
    if (!map || projection !== 'globe' || !activeTrack) {
      clearTooltip();
      return;
    }

    const onMove = (e: maplibregl.MapMouseEvent) => {
      if (projectionBusyRef.current || !map.isStyleLoaded()) {
        clearTooltip();
        return;
      }

      const layers = [PTS_ID, HITAREA_ID].filter((id) => map.getLayer(id));
      if (layers.length === 0) { clearTooltip(); return; }

      let features: maplibregl.MapGeoJSONFeature[] = [];
      try {
        features = map.queryRenderedFeatures(e.point, { layers });
      } catch { /* ignore */ }

      if (features.length === 0) {
        clearTooltip();
        // 하이라이트 리셋
        try {
          if (map.getLayer(PTS_HL_ID)) {
            map.setFilter(PTS_HL_ID, ['==', ['get', 'index'], -1] as never);
            map.setPaintProperty(PTS_HL_ID, 'circle-opacity', 0);
          }
        } catch { /* ignore */ }
        return;
      }

      const feat = features[0];
      const props = feat.properties || {};
      const layerId = feat.layer?.id;
      let tooltipHtml = '';

      if (layerId === PTS_ID && props.index != null) {
        tooltipHtml = getTrackPointTooltipHtml({
          name: String(props.name ?? ''),
          sog: Number(props.sog),
          cog: Number(props.cog),
          heading: Number(props.heading),
          status: String(props.status ?? ''),
          messageTimestamp: String(props.messageTimestamp ?? ''),
        }).html;
        // 하이라이트
        try {
          if (map.getLayer(PTS_HL_ID)) {
            map.setFilter(PTS_HL_ID, ['==', ['get', 'index'], Number(props.index)] as never);
            map.setPaintProperty(PTS_HL_ID, 'circle-opacity', 0.8);
          }
        } catch { /* ignore */ }
      } else if (layerId === HITAREA_ID) {
        tooltipHtml = getTrackLineTooltipHtml({
          name: String(props.name ?? ''),
          pointCount: Number(props.pointCount ?? 0),
          minutes: Number(props.minutes ?? 0),
          totalDistanceNm: Number(props.totalDistanceNm ?? 0),
        }).html;
      }

      if (!tooltipHtml) { clearTooltip(); return; }

      if (!tooltipRef.current) {
        tooltipRef.current = new maplibregl.Popup({
          closeButton: false, closeOnClick: false,
          maxWidth: '360px', className: 'maplibre-tooltip-popup',
        });
      }
      const container = document.createElement('div');
      container.className = 'maplibre-tooltip-popup__content';
      container.innerHTML = tooltipHtml;
      tooltipRef.current.setLngLat(e.lngLat).setDOMContent(container).addTo(map);
    };

    const onOut = () => {
      clearTooltip();
      try {
        if (map.getLayer(PTS_HL_ID)) {
          map.setFilter(PTS_HL_ID, ['==', ['get', 'index'], -1] as never);
          map.setPaintProperty(PTS_HL_ID, 'circle-opacity', 0);
        }
      } catch { /* ignore */ }
    };

    map.on('mousemove', onMove);
    map.on('mouseout', onOut);
    return () => {
      map.off('mousemove', onMove);
      map.off('mouseout', onOut);
      clearTooltip();
    };
  }, [projection, activeTrack, clearTooltip]);

  /* ── Mercator: 정적 레이어 1회 생성 + rAF 애니메이션 (React state 미사용) ── */
  const animRef = useRef(0);

  useEffect(() => {
    const overlay = overlayRef.current;
    if (!overlay || projection !== 'mercator') {
      cancelAnimationFrame(animRef.current);
      return;
    }

    const isTrackLayer = (id?: string) =>
      id === DECK_PATH_ID || id === DECK_TRIPS_ID || id === DECK_POINTS_ID;

    if (!normalizedTrip || !activeTrack || activeTrack.points.length < 2 || !timeRange || timeRange.durationSec === 0) {
      cancelAnimationFrame(animRef.current);
      try {
        const existing = (overlay as unknown as { props?: { layers?: unknown[] } }).props?.layers ?? [];
        const filtered = (existing as { id?: string }[]).filter((l) => !isTrackLayer(l.id));
        if (filtered.length !== (existing as unknown[]).length) {
          overlay.setProps({ layers: filtered } as never);
        }
      } catch { /* ignore */ }
      return;
    }

    // 정적 레이어: activeTrack 변경 시 1회만 생성, rAF 루프에서 재사용
    const pathLayer = new PathLayer<NormalizedTrip>({
      id: DECK_PATH_ID,
      data: [normalizedTrip],
      getPath: (d) => d.path,
      getColor: [...TRACK_COLOR, 90] as [number, number, number, number],
      getWidth: 2,
      widthMinPixels: 2,
      widthUnits: 'pixels' as const,
      capRounded: true,
      jointRounded: true,
      pickable: false,
    });

    const sorted = [...activeTrack.points].sort(
      (a, b) => new Date(a.messageTimestamp).getTime() - new Date(b.messageTimestamp).getTime(),
    );

    const pointsLayer = new ScatterplotLayer<TrackPoint>({
      id: DECK_POINTS_ID,
      data: sorted,
      getPosition: (d) => [d.lon, d.lat],
      getRadius: 4,
      radiusUnits: 'pixels' as const,
      getFillColor: TRACK_COLOR,
      getLineColor: [0, 0, 0, 128],
      lineWidthMinPixels: 1,
      stroked: true,
      pickable: true,
    });

    // rAF 루프: TripsLayer만 매 프레임 갱신 (React 재렌더링 없음)
    const { minTime, maxTime, durationSec } = timeRange;
    const speed = durationSec / ANIM_CYCLE_SEC;
    let current = minTime;

    const loop = () => {
      current += speed / 60;
      if (current > maxTime) current = minTime;

      const tripsLayer = new TripsLayer({
        id: DECK_TRIPS_ID,
        data: [normalizedTrip],
        getPath: (d: NormalizedTrip) => d.path,
        getTimestamps: (d: NormalizedTrip) => d.timestamps,
        getColor: (d: NormalizedTrip) => d.color,
        currentTime: current,
        trailLength: durationSec * 0.15,
        fadeTrail: true,
        widthMinPixels: 4,
        capRounded: true,
        jointRounded: true,
        pickable: false,
      });

      try {
        const existing = (overlay as unknown as { props?: { layers?: unknown[] } }).props?.layers ?? [];
        const filtered = (existing as { id?: string }[]).filter((l) => !isTrackLayer(l.id));
        overlay.setProps({ layers: [...filtered, pathLayer, tripsLayer, pointsLayer] } as never);
      } catch { /* ignore */ }

      animRef.current = requestAnimationFrame(loop);
    };

    animRef.current = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(animRef.current);
  }, [projection, normalizedTrip, activeTrack, timeRange]);

  /* ── 항적 조회 시 자동 fitBounds ── */
  useEffect(() => {
    const map = mapRef.current;
    if (!map || !activeTrack || activeTrack.points.length < 2) return;
    if (projectionBusyRef.current) return;

    let minLon = Infinity;
    let minLat = Infinity;
    let maxLon = -Infinity;
    let maxLat = -Infinity;
    for (const pt of activeTrack.points) {
      if (pt.lon < minLon) minLon = pt.lon;
      if (pt.lat < minLat) minLat = pt.lat;
      if (pt.lon > maxLon) maxLon = pt.lon;
      if (pt.lat > maxLat) maxLat = pt.lat;
    }

    const fitOpts = { padding: 80, duration: 1000, maxZoom: 14 };
    const apply = () => {
      try {
        map.fitBounds([[minLon, minLat], [maxLon, maxLat]], fitOpts);
      } catch { /* ignore */ }
    };

    if (map.isStyleLoaded()) {
      apply();
    } else {
      const onLoad = () => { apply(); map.off('styledata', onLoad); };
      map.on('styledata', onLoad);
      return () => { map.off('styledata', onLoad); };
    }
  }, [activeTrack]);
}
