import { useCallback, useEffect, useRef, type MutableRefObject } from 'react';
import type maplibregl from 'maplibre-gl';
import { MapboxOverlay } from '@deck.gl/mapbox';
import { MaplibreDeckCustomLayer } from '../MaplibreDeckCustomLayer';
import type { MapProjectionId } from '../types';
import { kickRepaint, onMapStyleReady, extractProjectionType } from '../lib/mapCore';

export function useProjectionToggle(
  mapRef: MutableRefObject<maplibregl.Map | null>,
  overlayRef: MutableRefObject<MapboxOverlay | null>,
  overlayInteractionRef: MutableRefObject<MapboxOverlay | null>,
  globeDeckLayerRef: MutableRefObject<MaplibreDeckCustomLayer | null>,
  projectionBusyRef: MutableRefObject<boolean>,
  opts: {
    projection: MapProjectionId;
    ensureMercatorOverlay: () => MapboxOverlay | null;
    onProjectionLoadingChange?: (loading: boolean) => void;
    pulseMapSync: () => void;
    setMapSyncEpoch: (updater: (prev: number) => number) => void;
  },
): () => void {
  const { projection, ensureMercatorOverlay, onProjectionLoadingChange, pulseMapSync, setMapSyncEpoch } = opts;

  const projectionBusyTokenRef = useRef(0);
  const projectionBusyTimerRef = useRef<ReturnType<typeof window.setTimeout> | null>(null);
  const projectionPrevRef = useRef<MapProjectionId>(projection);
  const projectionRef = useRef<MapProjectionId>(projection);

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

  const clearProjectionBusyTimer = useCallback(() => {
    if (projectionBusyTimerRef.current == null) return;
    clearTimeout(projectionBusyTimerRef.current);
    projectionBusyTimerRef.current = null;
  }, []);

  // eslint-disable-next-line react-hooks/preserve-manual-memoization
  const endProjectionLoading = useCallback(() => {
    if (!projectionBusyRef.current) return;
    projectionBusyRef.current = false;
    clearProjectionBusyTimer();
    if (onProjectionLoadingChange) {
      onProjectionLoadingChange(false);
    }
    setMapSyncEpoch((prev) => prev + 1);
    kickRepaint(mapRef.current);
  }, [clearProjectionBusyTimer, onProjectionLoadingChange, setMapSyncEpoch]);

  const setProjectionLoading = useCallback(
    // eslint-disable-next-line react-hooks/preserve-manual-memoization
    (loading: boolean) => {
      if (projectionBusyRef.current === loading) return;
      if (!loading) {
        endProjectionLoading();
        return;
      }

      clearProjectionBusyTimer();
      projectionBusyRef.current = true;
      const token = ++projectionBusyTokenRef.current;
      if (onProjectionLoadingChange) {
        onProjectionLoadingChange(true);
      }

      projectionBusyTimerRef.current = setTimeout(() => {
        if (!projectionBusyRef.current || projectionBusyTokenRef.current !== token) return;
        console.debug('Projection loading fallback timeout reached.');
        endProjectionLoading();
      }, 2000);
    },
    [clearProjectionBusyTimer, endProjectionLoading, onProjectionLoadingChange],
  );

  useEffect(() => {
    return () => {
      clearProjectionBusyTimer();
      endProjectionLoading();
    };
  }, [clearProjectionBusyTimer, endProjectionLoading]);

  const reorderRafRef = useRef(0);

  // eslint-disable-next-line react-hooks/preserve-manual-memoization
  const reorderGlobeFeatureLayers = useCallback(() => {
    if (!mapRef.current || projectionRef.current !== 'globe') return;
    if (projectionBusyRef.current) return;
    if (reorderRafRef.current) return; // 이미 스케줄됨 — 프레임당 1회 실행
    reorderRafRef.current = requestAnimationFrame(() => {
      reorderRafRef.current = 0;
      const m = mapRef.current;
      if (!m || !m.isStyleLoaded()) return;

      const ordering = [
        'subcables-hitarea',
        'subcables-casing',
        'subcables-line',
        'subcables-glow',
        'subcables-points',
        'subcables-label',
        'vessel-track-line',
        'vessel-track-line-hitarea',
        'vessel-track-arrow',
        'vessel-track-pts',
        'vessel-track-pts-highlight',
        'track-replay-globe-path',
        'track-replay-globe-points',
        'track-replay-globe-virtual-ship',
        'track-replay-globe-virtual-label',
        'zones-fill',
        'zones-line',
        'zones-label',
        'fleet-circles-ml',
        'fc-lines-ml',
        'pair-range-ml',
        'pair-lines-ml',
        'predict-vectors-outline',
        'predict-vectors',
        'predict-vectors-hl-outline',
        'predict-vectors-hl',
        'ships-globe-halo',
        'ships-globe-outline',
        'ships-globe-alarm-pulse',
        'ships-globe-lite',
        'ships-globe',
        'ships-globe-label',
        'ships-globe-alarm-badge',
        'ships-globe-hover-halo',
        'ships-globe-hover-outline',
        'ships-globe-hover',
      ];

      for (const layerId of ordering) {
        try {
          if (m.getLayer(layerId)) m.moveLayer(layerId);
        } catch {
          // ignore
        }
      }

      kickRepaint(m);
    });
  }, []);

  // Projection toggle (mercator <-> globe)
  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    let cancelled = false;
    let retries = 0;
    const maxRetries = 18;
    const isTransition = projectionPrevRef.current !== projection;
    projectionPrevRef.current = projection;
    let settleScheduled = false;
    let settleCleanup: (() => void) | null = null;

    const startProjectionSettle = () => {
      if (!isTransition || settleScheduled) return;
      settleScheduled = true;

      const finalize = () => {
        if (!cancelled && isTransition) setProjectionLoading(false);
      };

      const finalizeSoon = () => {
        if (cancelled || !isTransition || projectionBusyRef.current === false) return;
        if (!map.isStyleLoaded()) {
          requestAnimationFrame(finalizeSoon);
          return;
        }
        requestAnimationFrame(finalize);
      };

      const onIdle = () => finalizeSoon();
      try {
        map.on('idle', onIdle);
        const styleReadyCleanup = onMapStyleReady(map, finalizeSoon);
        settleCleanup = () => {
          map.off('idle', onIdle);
          styleReadyCleanup();
        };
      } catch {
        requestAnimationFrame(finalize);
        settleCleanup = null;
      }

      finalizeSoon();
    };

    if (isTransition) setProjectionLoading(true);

    // 파괴하지 않고 레이어만 비움 — 양쪽 파이프라인 항상 유지
    const quietMercatorOverlays = () => {
      try { overlayRef.current?.setProps({ layers: [] } as never); } catch { /* ignore */ }
      try { overlayInteractionRef.current?.setProps({ layers: [] } as never); } catch { /* ignore */ }
    };

    const quietGlobeDeckLayer = () => {
      try { globeDeckLayerRef.current?.setProps({ layers: [] } as never); } catch { /* ignore */ }
    };

    const syncProjectionAndDeck = () => {
      if (cancelled) return;
      if (!isTransition) {
        return;
      }

      if (!map.isStyleLoaded()) {
        if (!cancelled && retries < maxRetries) {
          retries += 1;
          window.requestAnimationFrame(() => syncProjectionAndDeck());
        }
        return;
      }

      const next = projection;
      const currentProjection = extractProjectionType(map);
      const shouldSwitchProjection = currentProjection !== next;

      if (projection === 'globe') {
        quietMercatorOverlays();
      } else {
        quietGlobeDeckLayer();
        quietMercatorOverlays();
        // Globe custom layer를 맵에서 분리 — setProjection() 중 render 콜백에서
        // stale WebGL 자원(uniform buffer 등) 참조를 방지
        const gl = globeDeckLayerRef.current;
        if (gl?.id) {
          try { if (map.getLayer(gl.id)) map.removeLayer(gl.id); } catch { /* ignore */ }
        }
      }

      try {
        if (shouldSwitchProjection) {
          map.setProjection({ type: next });
        }
        map.setRenderWorldCopies(next !== 'globe');

        // Globe에서는 easeTo around 미지원 → scrollZoom 동작 전환
        // dragRotate/touchPitch는 Map3D의 freeCamera effect에서 제어
        try {
          map.scrollZoom.disable();
          if (next === 'globe') {
            map.scrollZoom.enable();
          } else {
            map.scrollZoom.enable({ around: 'center' });
          }
        } catch { /* ignore */ }

        if (shouldSwitchProjection && currentProjection !== next && !cancelled && retries < maxRetries) {
          retries += 1;
          window.requestAnimationFrame(() => syncProjectionAndDeck());
          return;
        }
      } catch (e) {
        if (!cancelled && retries < maxRetries) {
          retries += 1;
          window.requestAnimationFrame(() => syncProjectionAndDeck());
          return;
        }
        if (isTransition) setProjectionLoading(false);
        console.warn('Projection switch failed:', e);
      }

      // 양쪽 overlay가 항상 존재하므로 재생성 불필요
      // deck-globe가 map에서 빠져있을 경우에만 재추가
      if (projection === 'globe') {
        const layer = globeDeckLayerRef.current;
        const layerId = layer?.id;
        if (layer && layerId && map.isStyleLoaded() && !map.getLayer(layerId)) {
          try {
            map.addLayer(layer);
          } catch {
            // ignore
          }
        }
      } else {
        ensureMercatorOverlay();
      }

      reorderGlobeFeatureLayers();
      kickRepaint(map);
      try {
        map.resize();
      } catch {
        // ignore
      }
      if (isTransition) {
        startProjectionSettle();
      }
      pulseMapSync();
    };

    if (!isTransition) return;

    if (map.isStyleLoaded()) syncProjectionAndDeck();
    else {
      const stop = onMapStyleReady(map, syncProjectionAndDeck);
      return () => {
        cancelled = true;
        if (settleCleanup) settleCleanup();
        stop();
        if (isTransition) setProjectionLoading(false);
      };
    }

    return () => {
      cancelled = true;
      if (settleCleanup) settleCleanup();
      if (isTransition) setProjectionLoading(false);
    };
  }, [projection, ensureMercatorOverlay, reorderGlobeFeatureLayers, setProjectionLoading]);

  return reorderGlobeFeatureLayers;
}
