import { MapboxOverlay } from '@deck.gl/mapbox';
import maplibregl from 'maplibre-gl';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { AisTarget } from '../../entities/aisTarget/model/types';
import { MaplibreDeckCustomLayer } from './MaplibreDeckCustomLayer';
import type { BaseMapId, Map3DProps, MapProjectionId } from './types';
import type { DashSeg, PairRangeCircle } from './types';
import {
  mergeNumberSets,
  makeSetSignature,
  isFiniteNumber,
  toIntMmsi,
  makeUniqueSorted,
  equalNumberArrays,
} from './lib/setUtils';
import { dashifyLine } from './lib/dashifyLine';
import { useHoverState } from './hooks/useHoverState';
import { useMapInit } from './hooks/useMapInit';
import { useProjectionToggle } from './hooks/useProjectionToggle';
import { useBaseMapToggle } from './hooks/useBaseMapToggle';
import { useFlyTo } from './hooks/useFlyTo';
import { useZonesLayer } from './hooks/useZonesLayer';
import { usePredictionVectors } from './hooks/usePredictionVectors';
import { useGlobeShips } from './hooks/useGlobeShips';
import { useGlobeOverlays } from './hooks/useGlobeOverlays';
import { useGlobeInteraction } from './hooks/useGlobeInteraction';
import { useDeckLayers } from './hooks/useDeckLayers';
import { useSubcablesLayer } from './hooks/useSubcablesLayer';
import { useMapStyleSettings } from './hooks/useMapStyleSettings';

export type { Map3DSettings, BaseMapId, MapProjectionId } from './types';

type Props = Map3DProps;


export function Map3D({
  targets,
  zones,
  selectedMmsi,
  hoveredMmsiSet = [],
  hoveredFleetMmsiSet = [],
  hoveredPairMmsiSet = [],
  hoveredFleetOwnerKey = null,
  highlightedMmsiSet = [],
  settings,
  baseMap,
  projection,
  overlays,
  onSelectMmsi,
  onToggleHighlightMmsi,
  onViewBboxChange,
  legacyHits,
  pairLinks,
  fcLinks,
  fleetCircles,
  onProjectionLoadingChange,
  fleetFocus,
  onHoverFleet,
  onClearFleetHover,
  onHoverMmsi,
  onClearMmsiHover,
  onHoverPair,
  onClearPairHover,
  subcableGeo = null,
  hoveredCableId = null,
  onHoverCable,
  onClickCable,
  mapStyleSettings,
  onMapReady,
}: Props) {
  void onHoverFleet;
  void onClearFleetHover;
  void onHoverMmsi;
  void onClearMmsiHover;
  void onHoverPair;
  void onClearPairHover;

  // ── Shared refs ──────────────────────────────────────────────────────
  const containerRef = useRef<HTMLDivElement | null>(null);
  const mapRef = useRef<maplibregl.Map | null>(null);
  const overlayRef = useRef<MapboxOverlay | null>(null);
  const overlayInteractionRef = useRef<MapboxOverlay | null>(null);
  const globeDeckLayerRef = useRef<MaplibreDeckCustomLayer | null>(null);
  const baseMapRef = useRef<BaseMapId>(baseMap);
  const projectionRef = useRef<MapProjectionId>(projection);
  const projectionBusyRef = useRef(false);
  const deckHoverRafRef = useRef<number | null>(null);
  const deckHoverHasHitRef = useRef(false);

  useEffect(() => { baseMapRef.current = baseMap; }, [baseMap]);
  useEffect(() => { projectionRef.current = projection; }, [projection]);

  // ── Hover state ──────────────────────────────────────────────────────
  const {
    setHoveredDeckMmsiSet,
    setHoveredDeckPairMmsiSet,
    setHoveredDeckFleetOwnerKey,
    setHoveredDeckFleetMmsiSet,
    hoveredZoneId, setHoveredZoneId,
    hoveredMmsiSetRef, hoveredFleetMmsiSetRef, hoveredPairMmsiSetRef,
    externalHighlightedSetRef,
    hoveredDeckMmsiSetRef, hoveredDeckPairMmsiSetRef, hoveredDeckFleetMmsiSetRef,
    hoveredFleetOwnerKeys,
  } = useHoverState({
    hoveredMmsiSet, hoveredFleetMmsiSet, hoveredPairMmsiSet,
    hoveredFleetOwnerKey, highlightedMmsiSet,
  });

  const fleetFocusId = fleetFocus?.id;
  const fleetFocusLon = fleetFocus?.center?.[0];
  const fleetFocusLat = fleetFocus?.center?.[1];
  const fleetFocusZoom = fleetFocus?.zoom;

  // ── Highlight memos ──────────────────────────────────────────────────
  const effectiveHoveredPairMmsiSet = useMemo(
    () => mergeNumberSets(hoveredPairMmsiSetRef, hoveredDeckPairMmsiSetRef),
    [hoveredPairMmsiSetRef, hoveredDeckPairMmsiSetRef],
  );
  const effectiveHoveredFleetMmsiSet = useMemo(
    () => mergeNumberSets(hoveredFleetMmsiSetRef, hoveredDeckFleetMmsiSetRef),
    [hoveredFleetMmsiSetRef, hoveredDeckFleetMmsiSetRef],
  );
  const [mapSyncEpoch, setMapSyncEpoch] = useState(0);
  const highlightedMmsiSetCombined = useMemo(
    () =>
      mergeNumberSets(
        hoveredMmsiSetRef,
        hoveredDeckMmsiSetRef,
        externalHighlightedSetRef,
        effectiveHoveredFleetMmsiSet,
        effectiveHoveredPairMmsiSet,
      ),
    [
      hoveredMmsiSetRef,
      hoveredDeckMmsiSetRef,
      externalHighlightedSetRef,
      effectiveHoveredFleetMmsiSet,
      effectiveHoveredPairMmsiSet,
    ],
  );
  const highlightedMmsiSetForShips = useMemo(
    () => (projection === 'globe' ? mergeNumberSets(hoveredMmsiSetRef, externalHighlightedSetRef) : highlightedMmsiSetCombined),
    [projection, hoveredMmsiSetRef, externalHighlightedSetRef, highlightedMmsiSetCombined],
  );
  const hoveredShipSignature = useMemo(
    () =>
      `${makeSetSignature(hoveredMmsiSetRef)}|${makeSetSignature(externalHighlightedSetRef)}|${makeSetSignature(
        hoveredDeckMmsiSetRef,
      )}|${makeSetSignature(effectiveHoveredFleetMmsiSet)}|${makeSetSignature(effectiveHoveredPairMmsiSet)}`,
    [
      hoveredMmsiSetRef,
      externalHighlightedSetRef,
      hoveredDeckMmsiSetRef,
      effectiveHoveredFleetMmsiSet,
      effectiveHoveredPairMmsiSet,
    ],
  );
  void hoveredShipSignature;
  const hoveredPairMmsiList = useMemo(() => makeUniqueSorted(Array.from(effectiveHoveredPairMmsiSet)), [effectiveHoveredPairMmsiSet]);
  const hoveredFleetOwnerKeyList = useMemo(() => Array.from(hoveredFleetOwnerKeys).sort(), [hoveredFleetOwnerKeys]);
  const hoveredFleetMmsiList = useMemo(() => makeUniqueSorted(Array.from(effectiveHoveredFleetMmsiSet)), [effectiveHoveredFleetMmsiSet]);

  const isHighlightedMmsi = useCallback(
    (mmsi: number) => highlightedMmsiSetCombined.has(mmsi),
    [highlightedMmsiSetCombined],
  );
  const baseHighlightedMmsiSet = useMemo(() => {
    const out = new Set<number>();
    if (selectedMmsi != null) out.add(selectedMmsi);
    externalHighlightedSetRef.forEach((value) => { out.add(value); });
    return out;
  }, [selectedMmsi, externalHighlightedSetRef]);
  const isBaseHighlightedMmsi = useCallback(
    (mmsi: number) => baseHighlightedMmsiSet.has(mmsi),
    [baseHighlightedMmsiSet],
  );
  const isHighlightedPair = useCallback(
    (aMmsi: number, bMmsi: number) =>
      effectiveHoveredPairMmsiSet.size === 2 &&
      effectiveHoveredPairMmsiSet.has(aMmsi) &&
      effectiveHoveredPairMmsiSet.has(bMmsi),
    [effectiveHoveredPairMmsiSet],
  );
  const isHighlightedFleet = useCallback(
    (ownerKey: string, vesselMmsis: number[]) => {
      if (hoveredFleetOwnerKeys.has(ownerKey)) return true;
      return vesselMmsis.some((x) => isHighlightedMmsi(x));
    },
    [hoveredFleetOwnerKeys, isHighlightedMmsi],
  );

  // ── Ship data memos ──────────────────────────────────────────────────
  const shipData = useMemo(() => {
    return targets.filter((t) => isFiniteNumber(t.lat) && isFiniteNumber(t.lon) && isFiniteNumber(t.mmsi));
  }, [targets]);

  const shipByMmsi = useMemo(() => {
    const byMmsi = new Map<number, AisTarget>();
    for (const t of shipData) byMmsi.set(t.mmsi, t);
    return byMmsi;
  }, [shipData]);

  const shipLayerData = useMemo(() => {
    if (shipData.length === 0) return shipData;
    return [...shipData];
  }, [shipData]);

  const shipHighlightSet = useMemo(() => {
    const out = new Set(highlightedMmsiSetForShips);
    if (selectedMmsi) out.add(selectedMmsi);
    return out;
  }, [highlightedMmsiSetForShips, selectedMmsi]);

  const shipHoverOverlaySet = useMemo(
    () =>
      projection === 'globe'
        ? mergeNumberSets(highlightedMmsiSetCombined, shipHighlightSet)
        : shipHighlightSet,
    [projection, highlightedMmsiSetCombined, shipHighlightSet],
  );

  const shipOverlayLayerData = useMemo(() => {
    if (shipLayerData.length === 0) return [];
    if (shipHighlightSet.size === 0) return [];
    return shipLayerData.filter((target) => shipHighlightSet.has(target.mmsi));
  }, [shipHighlightSet, shipLayerData]);

  // ── Deck hover management ────────────────────────────────────────────
  const hasAuxiliarySelectModifier = useCallback(
    (ev?: { shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean } | null): boolean => {
      if (!ev) return false;
      return !!(ev.shiftKey || ev.ctrlKey || ev.metaKey);
    },
    [],
  );

  const toFleetMmsiList = useCallback((value: unknown) => {
    if (!Array.isArray(value)) return [];
    const out: number[] = [];
    for (const item of value) {
      const v = toIntMmsi(item);
      if (v != null) out.push(v);
    }
    return out;
  }, []);

  const onDeckSelectOrHighlight = useCallback(
    (info: unknown, allowMultiSelect = false) => {
      const obj = info as {
        mmsi?: unknown;
        srcEvent?: { shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean } | null;
      };
      const mmsi = toIntMmsi(obj.mmsi);
      if (mmsi == null) return;
      const evt = obj.srcEvent ?? null;
      const isAux = hasAuxiliarySelectModifier(evt);
      if (onToggleHighlightMmsi && isAux) {
        onToggleHighlightMmsi(mmsi);
        return;
      }
      if (!allowMultiSelect && selectedMmsi === mmsi) {
        onSelectMmsi(null);
        return;
      }
      onSelectMmsi(mmsi);
    },
    [hasAuxiliarySelectModifier, onSelectMmsi, onToggleHighlightMmsi, selectedMmsi],
  );

  // eslint-disable-next-line react-hooks/preserve-manual-memoization
  const setHoveredDeckFleetMmsis = useCallback((next: number[]) => {
    const normalized = makeUniqueSorted(next);
    setHoveredDeckFleetMmsiSet((prev) => (equalNumberArrays(prev, normalized) ? prev : normalized));
  }, []);

  // eslint-disable-next-line react-hooks/preserve-manual-memoization
  const setHoveredDeckFleetOwner = useCallback((ownerKey: string | null) => {
    setHoveredDeckFleetOwnerKey((prev) => (prev === ownerKey ? prev : ownerKey));
  }, []);

  const mapDeckMmsiHoverRef = useRef<number[]>([]);
  const mapDeckPairHoverRef = useRef<number[]>([]);
  const mapFleetHoverStateRef = useRef<{ ownerKey: string | null; vesselMmsis: number[] }>({
    ownerKey: null,
    vesselMmsis: [],
  });

  const clearMapFleetHoverState = useCallback(() => {
    mapFleetHoverStateRef.current = { ownerKey: null, vesselMmsis: [] };
    setHoveredDeckFleetOwner(null);
    setHoveredDeckFleetMmsis([]);
  }, [setHoveredDeckFleetOwner, setHoveredDeckFleetMmsis]);

  const clearDeckHoverPairs = useCallback(() => {
    mapDeckPairHoverRef.current = [];
    setHoveredDeckPairMmsiSet((prevState) => (prevState.length === 0 ? prevState : []));
  }, [setHoveredDeckPairMmsiSet]);

  const clearDeckHoverMmsi = useCallback(() => {
    mapDeckMmsiHoverRef.current = [];
    setHoveredDeckMmsiSet((prevState) => (prevState.length === 0 ? prevState : []));
  }, [setHoveredDeckMmsiSet]);

  const scheduleDeckHoverResolve = useCallback(() => {
    if (deckHoverRafRef.current != null) return;
    deckHoverRafRef.current = window.requestAnimationFrame(() => {
      deckHoverRafRef.current = null;
      if (!deckHoverHasHitRef.current) {
        clearDeckHoverMmsi();
        clearDeckHoverPairs();
        clearMapFleetHoverState();
      }
      deckHoverHasHitRef.current = false;
    });
  }, [clearDeckHoverMmsi, clearDeckHoverPairs, clearMapFleetHoverState]);

  const touchDeckHoverState = useCallback(
    (isHover: boolean) => {
      if (isHover) deckHoverHasHitRef.current = true;
      scheduleDeckHoverResolve();
    },
    [scheduleDeckHoverResolve],
  );

  const setDeckHoverMmsi = useCallback(
    (next: number[]) => {
      const normalized = makeUniqueSorted(next);
      touchDeckHoverState(normalized.length > 0);
      setHoveredDeckMmsiSet((prev) => (equalNumberArrays(prev, normalized) ? prev : normalized));
      mapDeckMmsiHoverRef.current = normalized;
    },
    [setHoveredDeckMmsiSet, touchDeckHoverState],
  );

  const setDeckHoverPairs = useCallback(
    (next: number[]) => {
      const normalized = makeUniqueSorted(next);
      touchDeckHoverState(normalized.length > 0);
      setHoveredDeckPairMmsiSet((prev) => (equalNumberArrays(prev, normalized) ? prev : normalized));
      mapDeckPairHoverRef.current = normalized;
    },
    [setHoveredDeckPairMmsiSet, touchDeckHoverState],
  );

  const setMapFleetHoverState = useCallback(
    (ownerKey: string | null, vesselMmsis: number[]) => {
      const normalized = makeUniqueSorted(vesselMmsis);
      const prev = mapFleetHoverStateRef.current;
      if (prev.ownerKey === ownerKey && equalNumberArrays(prev.vesselMmsis, normalized)) {
        return;
      }
      touchDeckHoverState(!!ownerKey || normalized.length > 0);
      setHoveredDeckFleetOwner(ownerKey);
      setHoveredDeckFleetMmsis(normalized);
      mapFleetHoverStateRef.current = { ownerKey, vesselMmsis: normalized };
    },
    [setHoveredDeckFleetOwner, setHoveredDeckFleetMmsis, touchDeckHoverState],
  );

  // hover RAF cleanup
  useEffect(() => {
    return () => {
      if (deckHoverRafRef.current != null) {
        window.cancelAnimationFrame(deckHoverRafRef.current);
        deckHoverRafRef.current = null;
      }
      deckHoverHasHitRef.current = false;
    };
  }, []);

  // sync external fleet hover state
  useEffect(() => {
    mapFleetHoverStateRef.current = {
      ownerKey: hoveredFleetOwnerKey,
      vesselMmsis: hoveredFleetMmsiSet,
    };
  }, [hoveredFleetOwnerKey, hoveredFleetMmsiSet]);

  // ── Overlay data memos ───────────────────────────────────────────────
  const fcDashed = useMemo(() => {
    const segs: DashSeg[] = [];
    for (const l of fcLinks || []) {
      segs.push(...dashifyLine(l.from, l.to, l.suspicious, l.distanceNm, l.fcMmsi, l.otherMmsi));
    }
    return segs;
  }, [fcLinks]);

  const pairRanges = useMemo(() => {
    const out: PairRangeCircle[] = [];
    for (const p of pairLinks || []) {
      const center: [number, number] = [(p.from[0] + p.to[0]) / 2, (p.from[1] + p.to[1]) / 2];
      out.push({
        center,
        radiusNm: Math.max(0.05, p.distanceNm / 2),
        warn: p.warn,
        aMmsi: p.aMmsi,
        bMmsi: p.bMmsi,
        distanceNm: p.distanceNm,
      });
    }
    return out;
  }, [pairLinks]);

  const pairLinksInteractive = useMemo(() => {
    if (!overlays.pairLines || (pairLinks?.length ?? 0) === 0) return [];
    if (hoveredPairMmsiSetRef.size < 2) return [];
    const links = pairLinks || [];
    return links.filter((link) =>
      hoveredPairMmsiSetRef.has(link.aMmsi) && hoveredPairMmsiSetRef.has(link.bMmsi),
    );
  }, [pairLinks, hoveredPairMmsiSetRef, overlays.pairLines]);

  const pairRangesInteractive = useMemo(() => {
    if (!overlays.pairRange || pairRanges.length === 0) return [];
    if (hoveredPairMmsiSetRef.size < 2) return [];
    return pairRanges.filter((range) =>
      hoveredPairMmsiSetRef.has(range.aMmsi) && hoveredPairMmsiSetRef.has(range.bMmsi),
    );
  }, [pairRanges, hoveredPairMmsiSetRef, overlays.pairRange]);

  const fcLinesInteractive = useMemo(() => {
    if (!overlays.fcLines || fcDashed.length === 0) return [];
    if (highlightedMmsiSetCombined.size === 0) return [];
    return fcDashed.filter(
      (line) =>
        [line.fromMmsi, line.toMmsi].some((mmsi) => (mmsi == null ? false : highlightedMmsiSetCombined.has(mmsi))),
    );
  }, [fcDashed, overlays.fcLines, highlightedMmsiSetCombined]);

  const fleetCirclesInteractive = useMemo(() => {
    if (!overlays.fleetCircles || (fleetCircles?.length ?? 0) === 0) return [];
    if (hoveredFleetOwnerKeys.size === 0 && highlightedMmsiSetCombined.size === 0) return [];
    const circles = fleetCircles || [];
    return circles.filter((item) => isHighlightedFleet(item.ownerKey, item.vesselMmsis));
  }, [fleetCircles, hoveredFleetOwnerKeys, isHighlightedFleet, overlays.fleetCircles, highlightedMmsiSetCombined]);

  // ── Hook orchestration ───────────────────────────────────────────────
  const { ensureMercatorOverlay, clearGlobeNativeLayers, pulseMapSync } = useMapInit(
    containerRef, mapRef, overlayRef, overlayInteractionRef, globeDeckLayerRef,
    baseMapRef, projectionRef,
    { baseMap, projection, showSeamark: settings.showSeamark, onViewBboxChange, setMapSyncEpoch },
  );

  const reorderGlobeFeatureLayers = useProjectionToggle(
    mapRef, overlayRef, overlayInteractionRef, globeDeckLayerRef, projectionBusyRef,
    { projection, clearGlobeNativeLayers, ensureMercatorOverlay, onProjectionLoadingChange, pulseMapSync, setMapSyncEpoch },
  );

  useBaseMapToggle(
    mapRef,
    { baseMap, projection, showSeamark: settings.showSeamark, mapSyncEpoch, pulseMapSync },
  );

  useMapStyleSettings(mapRef, mapStyleSettings, { baseMap, mapSyncEpoch });

  useZonesLayer(
    mapRef, projectionBusyRef, reorderGlobeFeatureLayers,
    { zones, overlays, projection, baseMap, hoveredZoneId, mapSyncEpoch },
  );

  usePredictionVectors(
    mapRef, projectionBusyRef, reorderGlobeFeatureLayers,
    {
      overlays, settings, shipData, legacyHits, selectedMmsi,
      externalHighlightedSetRef, projection, baseMap, mapSyncEpoch,
    },
  );

  useGlobeShips(
    mapRef, projectionBusyRef, reorderGlobeFeatureLayers,
    {
      projection, settings, shipData, shipHighlightSet, shipHoverOverlaySet,
      shipOverlayLayerData, shipLayerData, shipByMmsi, mapSyncEpoch,
      onSelectMmsi, onToggleHighlightMmsi, targets, overlays,
      legacyHits, selectedMmsi, isBaseHighlightedMmsi, hasAuxiliarySelectModifier,
    },
  );

  useGlobeOverlays(
    mapRef, projectionBusyRef, reorderGlobeFeatureLayers,
    {
      overlays, pairLinks, fcLinks, fleetCircles, projection,
      mapSyncEpoch, hoveredFleetMmsiList, hoveredFleetOwnerKeyList, hoveredPairMmsiList,
    },
  );

  useGlobeInteraction(
    mapRef, projectionBusyRef,
    {
      projection, settings, overlays, targets, shipData, shipByMmsi, selectedMmsi,
      hoveredZoneId, legacyHits,
      clearDeckHoverPairs, clearDeckHoverMmsi, clearMapFleetHoverState,
      setDeckHoverPairs, setDeckHoverMmsi, setMapFleetHoverState, setHoveredZoneId,
    },
  );

  useDeckLayers(
    mapRef, overlayRef, globeDeckLayerRef, projectionBusyRef,
    {
      projection, settings, shipLayerData, shipOverlayLayerData, shipData,
      legacyHits, pairLinks, fcLinks, 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, projectionRef,
    },
  );

  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const noopCable = useCallback((_: string | null) => {}, []);

  useSubcablesLayer(
    mapRef, projectionBusyRef, reorderGlobeFeatureLayers,
    {
      subcableGeo: subcableGeo ?? null,
      overlays, projection, mapSyncEpoch,
      hoveredCableId: hoveredCableId ?? null,
      onHoverCable: onHoverCable ?? noopCable,
      onClickCable: onClickCable ?? noopCable,
    },
  );

  useFlyTo(
    mapRef, projectionRef,
    { selectedMmsi, shipData, fleetFocusId, fleetFocusLon, fleetFocusLat, fleetFocusZoom },
  );

  // Map ready 콜백 — mapSyncEpoch 초기 증가 시 1회 호출
  const mapReadyFiredRef = useRef(false);
  useEffect(() => {
    if (mapReadyFiredRef.current || !onMapReady || !mapRef.current) return;
    mapReadyFiredRef.current = true;
    onMapReady(mapRef.current);
  }, [mapSyncEpoch, onMapReady]);

  return <div ref={containerRef} style={{ width: '100%', height: '100%' }} />;
}
