import { useEffect, useMemo, useRef, type MutableRefObject } from 'react';
import { MapboxOverlay } from '@deck.gl/mapbox';
import { type PickingInfo } from '@deck.gl/core';
import type { AisTarget } from '../../../entities/aisTarget/model/types';
import type { LegacyVesselInfo } from '../../../entities/legacyVessel/model/types';
import { IconLayer, ScatterplotLayer } from '@deck.gl/layers';
import { ALARM_BADGE, type LegacyAlarmKind } from '../../../features/legacyDashboard/model/types';
import type { FcLink, FleetCircle, PairLink } from '../../../features/legacyDashboard/model/types';
import type { MapToggleState } from '../../../features/mapToggles/MapToggles';
import type { DashSeg, Map3DSettings, MapProjectionId, PairRangeCircle } from '../types';
import { MaplibreDeckCustomLayer } from '../MaplibreDeckCustomLayer';
import { toSafeNumber } from '../lib/setUtils';
import {
  getShipTooltipHtml,
  getPairLinkTooltipHtml,
  getFcLinkTooltipHtml,
  getRangeTooltipHtml,
  getFleetCircleTooltipHtml,
} from '../lib/tooltips';
import { sanitizeDeckLayerList } from '../lib/mapCore';
import { buildMercatorDeckLayers, buildGlobeDeckLayers } from '../lib/deckLayerFactories';
import {
  FLAT_LEGACY_HALO_RADIUS,
  FLAT_LEGACY_HALO_RADIUS_SELECTED,
  HALO_BREATHE_PERIOD_MS,
  HALO_BREATHE_SELECTED_R_MIN,
  HALO_BREATHE_SELECTED_R_MAX,
  HALO_BREATHE_HIGHLIGHTED_R_MIN,
  HALO_BREATHE_HIGHLIGHTED_R_MAX,
} from '../constants';
// NOTE:
// Globe mode now relies on MapLibre native overlays (useGlobeOverlays/useGlobeShips).
// Keep Deck custom-layer rendering disabled in globe to avoid projection-space artifacts.
const ENABLE_GLOBE_DECK_OVERLAYS = false;

/** 64×64 white ring SVG for alarm pulse IconLayer */
const ALARM_RING_ICON_URL = `data:image/svg+xml,${encodeURIComponent(
  '<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><circle cx="32" cy="32" r="27" fill="none" stroke="white" stroke-width="5"/></svg>',
)}`;
const ALARM_RING_ICON_MAPPING = { ring: { x: 0, y: 0, width: 64, height: 64, mask: true } } as const;


export function useDeckLayers(
  mapRef: MutableRefObject<import('maplibre-gl').Map | null>,
  overlayRef: MutableRefObject<MapboxOverlay | null>,
  globeDeckLayerRef: MutableRefObject<MaplibreDeckCustomLayer | null>,
  projectionBusyRef: MutableRefObject<boolean>,
  opts: {
    projection: MapProjectionId;
    settings: Map3DSettings;
    trackReplayDeckLayers: unknown[];
    shipLayerData: AisTarget[];
    shipData: AisTarget[];
    legacyHits: Map<number, LegacyVesselInfo> | null | undefined;
    pairLinks: PairLink[] | undefined;
    fcLinks: FcLink[] | undefined;
    fcDashed: DashSeg[];
    fleetCircles: FleetCircle[] | undefined;
    pairRanges: PairRangeCircle[];
    pairLinksInteractive: PairLink[];
    pairRangesInteractive: PairRangeCircle[];
    fcLinesInteractive: DashSeg[];
    fleetCirclesInteractive: FleetCircle[];
    overlays: MapToggleState;
    shipByMmsi: Map<number, AisTarget>;
    selectedMmsi: number | null;
    shipHighlightSet: Set<number>;
    isHighlightedFleet: (ownerKey: string, vesselMmsis: number[]) => boolean;
    isHighlightedPair: (aMmsi: number, bMmsi: number) => boolean;
    isHighlightedMmsi: (mmsi: number) => boolean;
    clearDeckHoverPairs: () => void;
    clearDeckHoverMmsi: () => void;
    clearMapFleetHoverState: () => void;
    setDeckHoverPairs: (next: number[]) => void;
    setDeckHoverMmsi: (next: number[]) => void;
    setMapFleetHoverState: (ownerKey: string | null, vesselMmsis: number[]) => void;
    toFleetMmsiList: (value: unknown) => number[];
    touchDeckHoverState: (isHover: boolean) => void;
    hasAuxiliarySelectModifier: (ev?: { shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean } | null) => boolean;
    onDeckSelectOrHighlight: (info: unknown, allowMultiSelect?: boolean) => void;
    onSelectMmsi: (mmsi: number | null) => void;
    onToggleHighlightMmsi?: (mmsi: number) => void;
    ensureMercatorOverlay: () => MapboxOverlay | null;
    alarmMmsiMap?: Map<number, LegacyAlarmKind>;
    onClickShipPhoto?: (mmsi: number) => void;
    shipLabelColors?: import('../lib/labelColor').ShipLabelColors;
  },
) {
  const {
    projection, settings, trackReplayDeckLayers, shipLayerData, shipData,
    legacyHits, pairLinks, fcDashed, fleetCircles, pairRanges,
    pairLinksInteractive, pairRangesInteractive, fcLinesInteractive, fleetCirclesInteractive,
    overlays, shipByMmsi, selectedMmsi, shipHighlightSet,
    isHighlightedFleet, isHighlightedPair, isHighlightedMmsi,
    clearDeckHoverPairs, clearDeckHoverMmsi, clearMapFleetHoverState,
    setDeckHoverPairs, setDeckHoverMmsi, setMapFleetHoverState,
    toFleetMmsiList, touchDeckHoverState, hasAuxiliarySelectModifier,
    onDeckSelectOrHighlight, onSelectMmsi, onToggleHighlightMmsi,
    ensureMercatorOverlay, alarmMmsiMap,
    onClickShipPhoto,
    shipLabelColors,
  } = opts;

  // Use shipLayerData (clustered/visible) instead of shipData (all) so halo
  // only appears for targets that are currently rendered after clustering.
  const legacyTargets = useMemo(() => {
    if (!legacyHits) return [];
    return shipLayerData.filter((t) => legacyHits.has(t.mmsi));
  }, [shipLayerData, legacyHits]);

  const legacyTargetsOrdered = useMemo(() => {
    if (legacyTargets.length === 0) return legacyTargets;
    const layer = [...legacyTargets];
    layer.sort((a, b) => a.mmsi - b.mmsi);
    return layer;
  }, [legacyTargets]);

  const legacyOverlayTargets = useMemo(() => {
    if (shipHighlightSet.size === 0 && selectedMmsi == null) return [];
    return legacyTargets.filter((target) =>
      shipHighlightSet.has(target.mmsi) ||
      (selectedMmsi != null && target.mmsi === selectedMmsi),
    );
  }, [legacyTargets, shipHighlightSet, selectedMmsi]);

  const alarmTargets = useMemo(() => {
    if (!alarmMmsiMap || alarmMmsiMap.size === 0) return [];
    return shipLayerData.filter((t) => alarmMmsiMap.has(t.mmsi));
  }, [shipLayerData, alarmMmsiMap]);

  const shipPhotoTargets = useMemo(() => {
    return shipData.filter((t) => !!t.shipImagePath);
  }, [shipData]);

  const mercatorLayersRef = useRef<unknown[]>([]);
  const alarmRafRef = useRef(0);

  // Mercator Deck layers
  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    if (projection !== 'mercator' || projectionBusyRef.current) {
      if (projection !== 'mercator') {
        try {
          if (overlayRef.current) overlayRef.current.setProps({ layers: [] } as never);
        } catch {
          // ignore
        }
      }
      return;
    }

    const deckTarget = ensureMercatorOverlay();
    if (!deckTarget) return;

    const layers = buildMercatorDeckLayers({
      shipLayerData,
      legacyTargetsOrdered,
      legacyOverlayTargets,
      legacyHits,
      pairLinks,
      fcDashed,
      fleetCircles,
      pairRanges,
      pairLinksInteractive,
      pairRangesInteractive,
      fcLinesInteractive,
      fleetCirclesInteractive,
      overlays,
      showDensity: settings.showDensity,
      showShips: settings.showShips,
      selectedMmsi,
      shipHighlightSet,
      touchDeckHoverState,
      setDeckHoverPairs,
      setDeckHoverMmsi,
      clearDeckHoverPairs,
      clearMapFleetHoverState,
      setMapFleetHoverState,
      toFleetMmsiList,
      hasAuxiliarySelectModifier,
      onSelectMmsi,
      onToggleHighlightMmsi,
      onDeckSelectOrHighlight,
      alarmTargets,
      alarmMmsiMap,
      alarmPulseRadius: 8,
      alarmPulseHoverRadius: 12,
      shipPhotoTargets,
      onClickShipPhoto,
      shipLabelColors,
    });

    const normalizedBaseLayers = sanitizeDeckLayerList(layers);
    const normalizedTrackLayers = sanitizeDeckLayerList(trackReplayDeckLayers);
    const normalizedLayers = sanitizeDeckLayerList([...normalizedBaseLayers, ...normalizedTrackLayers]);
    mercatorLayersRef.current = normalizedLayers;
    const deckProps = {
      layers: normalizedLayers,
      getTooltip: (info: PickingInfo) => {
        if (!info.object) return null;
        if (info.layer && info.layer.id === 'density') {
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          const o: any = info.object;
          const n = Array.isArray(o?.points) ? o.points.length : 0;
          return { text: `AIS density: ${n}` };
        }
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const obj: any = info.object;
        if (typeof obj.mmsi === 'number') {
          return getShipTooltipHtml({ mmsi: obj.mmsi, targetByMmsi: shipByMmsi, legacyHits });
        }
        if (info.layer && info.layer.id === 'pair-lines') {
          const aMmsi = toSafeNumber(obj.aMmsi);
          const bMmsi = toSafeNumber(obj.bMmsi);
          if (aMmsi == null || bMmsi == null) return null;
          return getPairLinkTooltipHtml({ warn: !!obj.warn, distanceNm: toSafeNumber(obj.distanceNm), aMmsi, bMmsi, legacyHits, targetByMmsi: shipByMmsi });
        }
        if (info.layer && info.layer.id === 'fc-lines') {
          const fcMmsi = toSafeNumber(obj.fcMmsi);
          const otherMmsi = toSafeNumber(obj.otherMmsi);
          if (fcMmsi == null || otherMmsi == null) return null;
          return getFcLinkTooltipHtml({ suspicious: !!obj.suspicious, distanceNm: toSafeNumber(obj.distanceNm), fcMmsi, otherMmsi, legacyHits, targetByMmsi: shipByMmsi });
        }
        if (info.layer && info.layer.id === 'pair-range') {
          const aMmsi = toSafeNumber(obj.aMmsi);
          const bMmsi = toSafeNumber(obj.bMmsi);
          if (aMmsi == null || bMmsi == null) return null;
          return getRangeTooltipHtml({ warn: !!obj.warn, distanceNm: toSafeNumber(obj.distanceNm), aMmsi, bMmsi, legacyHits });
        }
        if (info.layer && info.layer.id === 'fleet-circles') {
          return getFleetCircleTooltipHtml({ ownerKey: String(obj.ownerKey ?? ''), ownerLabel: String(obj.ownerKey ?? ''), count: Number(obj.count ?? 0) });
        }
        return null;
      },
      onClick: (info: PickingInfo) => {
        if (!info.object) { onSelectMmsi(null); return; }
        if (info.layer && info.layer.id === 'density') return;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const obj: any = info.object;
        if (typeof obj.mmsi === 'number') {
          const t = obj as AisTarget;
          const sourceEvent = (info as { srcEvent?: { shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean } | null }).srcEvent;
          if (sourceEvent && hasAuxiliarySelectModifier(sourceEvent)) {
            onToggleHighlightMmsi?.(t.mmsi);
            return;
          }
          onSelectMmsi(t.mmsi);
        }
      },
    };

    try {
      deckTarget.setProps(deckProps as never);
    } catch (e) {
      console.error('Failed to apply base mercator deck props. Keeping previous layer set.', e);
    }
  }, [
    ensureMercatorOverlay,
    projection,
    shipLayerData,
    shipByMmsi,
    pairRanges,
    pairLinks,
    fcDashed,
    fleetCircles,
    legacyTargetsOrdered,
    legacyHits,
    legacyOverlayTargets,
    pairRangesInteractive,
    pairLinksInteractive,
    fcLinesInteractive,
    fleetCirclesInteractive,
    overlays.pairRange,
    overlays.pairLines,
    overlays.fcLines,
    overlays.fleetCircles,
    overlays.shipLabels,
    settings.showDensity,
    settings.showShips,
    trackReplayDeckLayers,
    onDeckSelectOrHighlight,
    onSelectMmsi,
    onToggleHighlightMmsi,
    setDeckHoverPairs,
    clearMapFleetHoverState,
    setDeckHoverMmsi,
    clearDeckHoverMmsi,
    toFleetMmsiList,
    touchDeckHoverState,
    hasAuxiliarySelectModifier,
    alarmTargets,
    alarmMmsiMap,
    shipPhotoTargets,
    onClickShipPhoto,
    shipLabelColors,
  ]);

  // Mercator 브리딩 애니메이션 (rAF) — 알람 맥동 + 대상 선박 브리딩 합류
  const hasAlarms = alarmMmsiMap && alarmMmsiMap.size > 0 && alarmTargets.length > 0;
  const hasTargetOverlays = legacyOverlayTargets.length > 0;

  useEffect(() => {
    if (projection !== 'mercator' || (!hasAlarms && !hasTargetOverlays)) {
      if (alarmRafRef.current) cancelAnimationFrame(alarmRafRef.current);
      alarmRafRef.current = 0;
      return;
    }

    const animate = () => {
      // 프로젝션 전환 중에는 overlay에 접근하지 않음 — WebGL 자원 무효화 방지
      if (projectionBusyRef.current) {
        alarmRafRef.current = requestAnimationFrame(animate);
        return;
      }
      const currentOverlay = overlayRef.current;
      if (!currentOverlay) {
        alarmRafRef.current = requestAnimationFrame(animate);
        return;
      }

      const now = Date.now();
      let updated = mercatorLayersRef.current;

      // 1. 알람 맥동 — IconLayer + SVG ring, opacity/sizeScale uniform 애니메이션
      if (hasAlarms) {
        const tA = (Math.sin(now / 1500 * Math.PI * 2) + 1) / 2;

        const pulseLyr = new IconLayer<AisTarget>({
          id: 'alarm-pulse',
          data: alarmTargets,
          pickable: false,
          billboard: true,
          iconAtlas: ALARM_RING_ICON_URL,
          iconMapping: ALARM_RING_ICON_MAPPING,
          getIcon: () => 'ring',
          sizeUnits: 'pixels',
          sizeScale: 0.9 + tA * 0.2,
          opacity: 0.2 + tA * 0.7,
          getSize: (d) => {
            const isHover = (selectedMmsi != null && d.mmsi === selectedMmsi) || shipHighlightSet.has(d.mmsi);
            return isHover
              ? (FLAT_LEGACY_HALO_RADIUS_SELECTED + 8) * 2
              : (FLAT_LEGACY_HALO_RADIUS + 8) * 2;
          },
          getColor: (d) => {
            const kind = alarmMmsiMap!.get(d.mmsi);
            return kind ? ALARM_BADGE[kind].rgba : [107, 114, 128, 200] as [number, number, number, number];
          },
          getPosition: (d) => [d.lon, d.lat] as [number, number],
          updateTriggers: {
            getSize: [selectedMmsi],
          },
        });
        updated = updated.map((l) =>
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          (l as any)?.id === 'alarm-pulse' ? pulseLyr : l,
        );
      }

      // 2. 대상 선박 브리딩 링
      if (hasTargetOverlays) {
        const tH = (Math.sin(now / HALO_BREATHE_PERIOD_MS * Math.PI * 2) + 1) / 2;
        const selR = HALO_BREATHE_SELECTED_R_MIN + tH * (HALO_BREATHE_SELECTED_R_MAX - HALO_BREATHE_SELECTED_R_MIN);
        const hlR = HALO_BREATHE_HIGHLIGHTED_R_MIN + tH * (HALO_BREATHE_HIGHLIGHTED_R_MAX - HALO_BREATHE_HIGHLIGHTED_R_MIN);
        const alpha = Math.round(155 + tH * 100);

        const haloLyr = new ScatterplotLayer<AisTarget>({
          id: 'legacy-halo-overlay',
          data: legacyOverlayTargets,
          pickable: false,
          billboard: false,
          filled: false,
          stroked: true,
          radiusUnits: 'pixels',
          getRadius: (d) => (selectedMmsi != null && d.mmsi === selectedMmsi ? selR : hlR),
          lineWidthUnits: 'pixels',
          getLineWidth: 2.5,
          getLineColor: (d) => {
            if (selectedMmsi != null && d.mmsi === selectedMmsi) return [14, 234, 255, alpha] as [number, number, number, number];
            return [245, 158, 11, alpha] as [number, number, number, number];
          },
          getPosition: (d) => [d.lon, d.lat] as [number, number],
          updateTriggers: { getRadius: [selR, hlR], getLineColor: [alpha, selectedMmsi] },
        });
        updated = updated.map((l) =>
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          (l as any)?.id === 'legacy-halo-overlay' ? haloLyr : l,
        );
      }

      try {
        currentOverlay.setProps({ layers: updated } as never);
      } catch {
        // ignore
      }
      alarmRafRef.current = requestAnimationFrame(animate);
    };
    alarmRafRef.current = requestAnimationFrame(animate);
    return () => {
      if (alarmRafRef.current) cancelAnimationFrame(alarmRafRef.current);
      alarmRafRef.current = 0;
    };
  }, [projection, alarmMmsiMap, alarmTargets, selectedMmsi, shipHighlightSet, hasAlarms, hasTargetOverlays, legacyOverlayTargets]);

  // Globe Deck overlay
  useEffect(() => {
    const map = mapRef.current;
    if (!map || projection !== 'globe' || projectionBusyRef.current) return;
    const deckTarget = globeDeckLayerRef.current;
    if (!deckTarget) return;

    if (!ENABLE_GLOBE_DECK_OVERLAYS) {
      // Globe에서는 Deck.gl ScatterplotLayer가 프로젝션 공간 아티팩트(막대)를 유발하므로
      // 빈 레이어만 설정. 사진 인디케이터는 Mercator에서만 동작.
      try {
        deckTarget.setProps({ layers: [], getTooltip: undefined, onClick: undefined } as never);
      } catch {
        // ignore
      }
      return;
    }

    const globeLayers = buildGlobeDeckLayers({
      pairRanges,
      pairLinks,
      fcDashed,
      fleetCircles,
      legacyTargetsOrdered,
      legacyHits,
      overlays,
      showShips: settings.showShips,
      selectedMmsi,
      isHighlightedFleet,
      isHighlightedPair,
      isHighlightedMmsi,
      touchDeckHoverState,
      setDeckHoverPairs,
      setDeckHoverMmsi,
      clearDeckHoverPairs,
      clearDeckHoverMmsi,
      clearMapFleetHoverState,
      setMapFleetHoverState,
      toFleetMmsiList,
    });

    const normalizedLayers = sanitizeDeckLayerList(globeLayers);
    const globeDeckProps = { layers: normalizedLayers, getTooltip: undefined, onClick: undefined };

    try {
      deckTarget.setProps(globeDeckProps as never);
    } catch (e) {
      console.error('Failed to apply globe deck props. Falling back to empty deck layer set.', e);
      try {
        deckTarget.setProps({ ...globeDeckProps, layers: [] as unknown[] } as never);
      } catch {
        // Ignore secondary failure.
      }
    }
  }, [
    projection,
    pairRanges,
    pairLinks,
    fcDashed,
    fleetCircles,
    legacyTargetsOrdered,
    overlays.pairRange,
    overlays.pairLines,
    overlays.fcLines,
    overlays.fleetCircles,
    settings.showShips,
    selectedMmsi,
    isHighlightedFleet,
    isHighlightedPair,
    clearDeckHoverPairs,
    clearDeckHoverMmsi,
    clearMapFleetHoverState,
    setDeckHoverPairs,
    setDeckHoverMmsi,
    setMapFleetHoverState,
    toFleetMmsiList,
    touchDeckHoverState,
    legacyHits,
  ]);
}
