import { useCallback, useEffect, useRef, type MutableRefObject } from 'react';
import type maplibregl from 'maplibre-gl';
import type { GeoJSONSource, GeoJSONSourceSpecification, LayerSpecification } from 'maplibre-gl';
import type { PairLink } from '../../../features/legacyDashboard/model/types';
import type { MapToggleState } from '../../../features/mapToggles/MapToggles';
import type { MapProjectionId, PairRangeCircle } from '../types';
import {
  PAIR_LINE_NORMAL_ML, PAIR_LINE_WARN_ML,
  PAIR_LINE_NORMAL_ML_HL, PAIR_LINE_WARN_ML_HL,
  PAIR_RANGE_NORMAL_ML, PAIR_RANGE_WARN_ML,
  PAIR_RANGE_NORMAL_ML_HL, PAIR_RANGE_WARN_ML_HL,
} from '../constants';
import { makePairLinkFeatureId } from '../lib/featureIds';
import { makeMmsiPairHighlightExpr } from '../lib/mlExpressions';
import { kickRepaint, onMapStyleReady } from '../lib/mapCore';
import { circleRingLngLat } from '../lib/geometry';

// ── Overlay line width constants ──
const PAIR_LINE_W_NORMAL = 2.5;
const PAIR_LINE_W_WARN = 3.5;
const PAIR_LINE_W_HL = 4.5;
const PAIR_RANGE_W_NORMAL = 1.8;
const PAIR_RANGE_W_HL = 2.8;

// ── Breathing animation constants ──
const BREATHE_AMP = 2.0;
const BREATHE_PERIOD_MS = 1200;

/** Globe pair lines + pair range 오버레이 */
export function useGlobePairOverlay(
  mapRef: MutableRefObject<maplibregl.Map | null>,
  _projectionBusyRef: MutableRefObject<boolean>,
  reorderGlobeFeatureLayers: () => void,
  opts: {
    overlays: MapToggleState;
    pairLinks: PairLink[] | undefined;
    projection: MapProjectionId;
    mapSyncEpoch: number;
    hoveredPairMmsiList: number[];
  },
) {
  const { overlays, pairLinks, projection, mapSyncEpoch, hoveredPairMmsiList } = opts;
  const breatheRafRef = useRef<number>(0);

  // paint state ref — 데이터 effect에서 레이어 생성 직후 최신 paint state를 즉시 적용
  const paintStateRef = useRef<() => void>(() => {});

  // ── Pair lines 데이터 effect ──
  // projectionBusy/isStyleLoaded 선행 가드 제거 — try/catch로 처리
  // 실패 시 다음 AIS poll(mapSyncEpoch 변경)에서 자연스럽게 재시도
  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;

    const srcId = 'pair-lines-ml-src';
    const layerId = 'pair-lines-ml';

    const ensure = () => {
      if (projection !== 'globe') {
        try {
          if (map.getLayer(layerId)) map.setPaintProperty(layerId, 'line-opacity', 0);
        } catch { /* ignore */ }
        return;
      }
      if ((pairLinks?.length ?? 0) === 0) {
        try {
          if (map.getLayer(layerId)) map.setPaintProperty(layerId, 'line-opacity', 0);
        } catch { /* ignore */ }
        return;
      }

      const fc: GeoJSON.FeatureCollection<GeoJSON.LineString> = {
        type: 'FeatureCollection',
        features: (pairLinks || []).map((p) => ({
          type: 'Feature',
          id: makePairLinkFeatureId(p.aMmsi, p.bMmsi),
          geometry: { type: 'LineString', coordinates: [p.from, p.to] },
            properties: {
              type: 'pair',
              aMmsi: p.aMmsi,
              bMmsi: p.bMmsi,
              distanceNm: p.distanceNm,
              warn: p.warn,
            },
        })),
      };

      try {
        const existing = map.getSource(srcId) as GeoJSONSource | undefined;
        if (existing) existing.setData(fc);
        else map.addSource(srcId, { type: 'geojson', data: fc } as GeoJSONSourceSpecification);
      } catch {
        return; // 다음 poll에서 재시도
      }

      const needReorder = !map.getLayer(layerId);
      if (needReorder) {
        try {
          map.addLayer(
            {
              id: layerId,
              type: 'line',
              source: srcId,
              layout: { 'line-cap': 'round', 'line-join': 'round', visibility: 'visible' },
              paint: {
                'line-color': PAIR_LINE_NORMAL_ML,
                'line-width': PAIR_LINE_W_NORMAL,
                'line-opacity': 0,
              },
            } as unknown as LayerSpecification,
            undefined,
          );
        } catch {
          return; // 다음 poll에서 재시도
        }
        reorderGlobeFeatureLayers();
      }

      // 즉시 올바른 paint state 적용 — 타이밍 간극으로 opacity:0 고착 방지
      paintStateRef.current();
      kickRepaint(map);
    };

    // 초기 style 로드 대기 — 이후에는 AIS poll 사이클이 재시도 보장
    const stop = onMapStyleReady(map, ensure);
    ensure();
    return () => { stop(); };
  }, [projection, pairLinks, mapSyncEpoch, reorderGlobeFeatureLayers]);

  // ── Pair range 데이터 effect ──
  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;

    const srcId = 'pair-range-ml-src';
    const layerId = 'pair-range-ml';

    const ensure = () => {
      if (projection !== 'globe') {
        try {
          if (map.getLayer(layerId)) map.setPaintProperty(layerId, 'line-opacity', 0);
        } catch { /* ignore */ }
        return;
      }

      const ranges: PairRangeCircle[] = [];
      for (const p of pairLinks || []) {
        const center: [number, number] = [(p.from[0] + p.to[0]) / 2, (p.from[1] + p.to[1]) / 2];
        ranges.push({
          center,
          radiusNm: Math.max(0.05, p.distanceNm / 2),
          warn: p.warn,
          aMmsi: p.aMmsi,
          bMmsi: p.bMmsi,
          distanceNm: p.distanceNm,
        });
      }
      if (ranges.length === 0) {
        try {
          if (map.getLayer(layerId)) map.setPaintProperty(layerId, 'line-opacity', 0);
        } catch { /* ignore */ }
        return;
      }

      const fc: GeoJSON.FeatureCollection<GeoJSON.LineString> = {
        type: 'FeatureCollection',
        features: ranges.map((c) => {
          const ring = circleRingLngLat(c.center, c.radiusNm * 1852);
          return {
            type: 'Feature',
            id: makePairLinkFeatureId(c.aMmsi, c.bMmsi),
            geometry: { type: 'LineString', coordinates: ring },
            properties: {
              type: 'pair-range',
              warn: c.warn,
              aMmsi: c.aMmsi,
              bMmsi: c.bMmsi,
              distanceNm: c.distanceNm,
            },
          };
        }),
      };

      try {
        const existing = map.getSource(srcId) as GeoJSONSource | undefined;
        if (existing) existing.setData(fc);
        else map.addSource(srcId, { type: 'geojson', data: fc } as GeoJSONSourceSpecification);
      } catch {
        return; // 다음 poll에서 재시도
      }

      const needReorder = !map.getLayer(layerId);
      if (needReorder) {
        try {
          map.addLayer(
            {
              id: layerId,
              type: 'line',
              source: srcId,
              layout: {
                'line-cap': 'round',
                'line-join': 'round',
                visibility: 'visible',
              },
              paint: {
                'line-color': PAIR_RANGE_NORMAL_ML,
                'line-width': PAIR_RANGE_W_NORMAL,
                'line-opacity': 0,
              },
            } as unknown as LayerSpecification,
            undefined,
          );
        } catch {
          return; // 다음 poll에서 재시도
        }
        reorderGlobeFeatureLayers();
      }

      paintStateRef.current();
      kickRepaint(map);
    };

    // 초기 style 로드 대기 — 이후에는 AIS poll 사이클이 재시도 보장
    const stop = onMapStyleReady(map, ensure);
    ensure();
    return () => { stop(); };
  }, [projection, pairLinks, mapSyncEpoch, reorderGlobeFeatureLayers]);

  // ── Pair paint state update (가시성 + 하이라이트 통합) ──
  // setLayoutProperty(visibility) 대신 setPaintProperty(line-opacity)로 가시성 제어
  // → style._changed 미트리거 → alarm badge symbol placement 재계산 방지
  // eslint-disable-next-line react-hooks/preserve-manual-memoization
  const updatePairPaintStates = useCallback(() => {
    if (projection !== 'globe') return;
    const map = mapRef.current;
    if (!map) return;

    const active = hoveredPairMmsiList.length >= 2;
    const pairHighlightExpr = active
      ? makeMmsiPairHighlightExpr('aMmsi', 'bMmsi', hoveredPairMmsiList)
      : false;

    // ── Pair lines: 가시성 + 하이라이트 ──
    const pairLinesVisible = overlays.pairLines;
    try {
      if (map.getLayer('pair-lines-ml')) {
        map.setPaintProperty('pair-lines-ml', 'line-opacity', pairLinesVisible ? 0.9 : 0);
        if (pairLinesVisible) {
          map.setPaintProperty(
            'pair-lines-ml', 'line-color',
            pairHighlightExpr !== false
              ? ['case', pairHighlightExpr, ['case', ['boolean', ['get', 'warn'], false], PAIR_LINE_WARN_ML_HL, PAIR_LINE_NORMAL_ML_HL], ['boolean', ['get', 'warn'], false], PAIR_LINE_WARN_ML, PAIR_LINE_NORMAL_ML] as never
              : ['case', ['boolean', ['get', 'warn'], false], PAIR_LINE_WARN_ML, PAIR_LINE_NORMAL_ML] as never,
          );
          map.setPaintProperty(
            'pair-lines-ml', 'line-width',
            pairHighlightExpr !== false
              ? ['case', pairHighlightExpr, PAIR_LINE_W_HL, ['boolean', ['get', 'warn'], false], PAIR_LINE_W_WARN, PAIR_LINE_W_NORMAL] as never
              : ['case', ['boolean', ['get', 'warn'], false], PAIR_LINE_W_WARN, PAIR_LINE_W_NORMAL] as never,
          );
        }
      }
    } catch {
      // ignore
    }

    // ── Pair range: 가시성 + 하이라이트 ──
    const pairRangeVisible = overlays.pairRange;
    try {
      if (map.getLayer('pair-range-ml')) {
        map.setPaintProperty('pair-range-ml', 'line-opacity', pairRangeVisible ? 0.85 : 0);
        if (pairRangeVisible) {
          map.setPaintProperty(
            'pair-range-ml', 'line-color',
            pairHighlightExpr !== false
              ? ['case', pairHighlightExpr, ['case', ['boolean', ['get', 'warn'], false], PAIR_RANGE_WARN_ML_HL, PAIR_RANGE_NORMAL_ML_HL], ['boolean', ['get', 'warn'], false], PAIR_RANGE_WARN_ML, PAIR_RANGE_NORMAL_ML] as never
              : ['case', ['boolean', ['get', 'warn'], false], PAIR_RANGE_WARN_ML, PAIR_RANGE_NORMAL_ML] as never,
          );
          map.setPaintProperty(
            'pair-range-ml', 'line-width',
            pairHighlightExpr !== false
              ? ['case', pairHighlightExpr, PAIR_RANGE_W_HL, PAIR_RANGE_W_NORMAL] as never
              : PAIR_RANGE_W_NORMAL,
          );
        }
      }
    } catch {
      // ignore
    }

    kickRepaint(map);
  }, [projection, hoveredPairMmsiList, overlays.pairLines, overlays.pairRange]);

  // paintStateRef를 최신 콜백으로 유지 — useEffect 내에서만 ref 업데이트 (react-hooks/refs 준수)
  useEffect(() => {
    paintStateRef.current = updatePairPaintStates;
  }, [updatePairPaintStates]);

  // paint state 동기화: 호버/토글/epoch 변경 시 즉시 반영
  useEffect(() => {
    updatePairPaintStates();
  }, [mapSyncEpoch, hoveredPairMmsiList, projection, overlays.pairLines, overlays.pairRange, updatePairPaintStates, pairLinks]);

  // ── Breathing animation for highlighted pair overlays ──
  useEffect(() => {
    const map = mapRef.current;
    if (!map || hoveredPairMmsiList.length < 2 || projection !== 'globe') {
      if (breatheRafRef.current) cancelAnimationFrame(breatheRafRef.current);
      breatheRafRef.current = 0;
      return;
    }

    const pairHighlightExpr = makeMmsiPairHighlightExpr('aMmsi', 'bMmsi', hoveredPairMmsiList);

    const animate = () => {
      if (!map.isStyleLoaded()) {
        breatheRafRef.current = requestAnimationFrame(animate);
        return;
      }
      const t = (Math.sin(Date.now() / BREATHE_PERIOD_MS * Math.PI * 2) + 1) / 2;
      try {
        if (map.getLayer('pair-lines-ml')) {
          const hlW = PAIR_LINE_W_HL + t * BREATHE_AMP;
          map.setPaintProperty('pair-lines-ml', 'line-width',
            ['case', pairHighlightExpr, hlW, ['boolean', ['get', 'warn'], false], PAIR_LINE_W_WARN, PAIR_LINE_W_NORMAL] as never);
        }
        if (map.getLayer('pair-range-ml')) {
          const hlW = PAIR_RANGE_W_HL + t * BREATHE_AMP;
          map.setPaintProperty('pair-range-ml', 'line-width',
            ['case', pairHighlightExpr, hlW, PAIR_RANGE_W_NORMAL] as never);
        }
      } catch {
        // ignore
      }
      breatheRafRef.current = requestAnimationFrame(animate);
    };
    breatheRafRef.current = requestAnimationFrame(animate);
    return () => {
      if (breatheRafRef.current) cancelAnimationFrame(breatheRafRef.current);
      breatheRafRef.current = 0;
    };
  }, [hoveredPairMmsiList, projection]);
}
