/**
 * useGearReplayLayers — 어구 궤적 리플레이 레이어 빌더 훅
 *
 * iran 프로젝트 useReplayLayer.ts 패턴 그대로 적용:
 *
 * 1. animationStore rAF 루프 → set({ currentTime }) 매 프레임 (gearReplayStore)
 * 2. zustand.subscribe(currentTime) → renderFrame()
 *    - 재생 중: ~10fps 쓰로틀 + pendingRafId로 다음 프레임 보장 (프레임 드롭 방지)
 *    - seek/정지: 즉시 렌더
 * 3. renderFrame() → 레이어 빌드 → overlay.setProps({ layers }) 직접 호출
 *
 * 레이어 구성 (iran 대비 KCG 적용):
 * - PathLayer: 중심 궤적 (gold) — iran의 정적 PathLayer에 대응
 * - TripsLayer: 멤버 궤적 fade trail — iran과 동일
 * - IconLayer: 멤버 현재 위치 (보간) — iran의 가상 선박 레이어에 대응
 * - PolygonLayer: 현재 폴리곤 (보간으로 확장/축소 애니메이션)
 * - TextLayer: MMSI 라벨
 *
 * 제거된 것: 멤버 배경 PathLayer (TripsLayer와 중복), 멤버-중심 연결선 (불필요)
 */
import { useEffect, useRef, useCallback } from 'react';
import type { Layer } from 'deck.gl';
import { ScatterplotLayer, IconLayer, PolygonLayer, TextLayer } from 'deck.gl';
import type { MapboxOverlay } from '@deck.gl/mapbox';
import { useGearReplayStore } from '@stores/gearReplayStore';
import {
  findFrameAtTime,
  interpolateFromTripsData,
  computeConvexHull,
  type MemberPosition,
} from '@stores/gearReplayPreprocess';
import { createTripsLayer } from '@lib/map/layers/trips';

// ── SVG Data URI ──

const ICON_SIZE = 64;

const SHIP_URI = (() => {
  const s = ICON_SIZE;
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${s}" height="${s}" viewBox="0 0 ${s} ${s}"><polygon points="${s / 2},2 ${s * 0.12},${s - 2} ${s / 2},${s * 0.62} ${s * 0.88},${s - 2}" fill="white"/></svg>`;
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
})();

const GEAR_URI = (() => {
  const s = ICON_SIZE;
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${s}" height="${s}" viewBox="0 0 ${s} ${s}"><polygon points="${s / 2},4 ${s - 4},${s / 2} ${s / 2},${s - 4} 4,${s / 2}" fill="white"/></svg>`;
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
})();

// ── 색상 ──

const CYAN: [number, number, number, number] = [6, 182, 212, 220];
const AMBER: [number, number, number, number] = [245, 158, 11, 200];
const SLATE: [number, number, number, number] = [148, 163, 184, 120];
const POLYGON_FILL: [number, number, number, number] = [245, 158, 11, 30];
const POLYGON_STROKE: [number, number, number, number] = [245, 158, 11, 120];

const RENDER_INTERVAL_MS = 100; // iran과 동일: ~10fps 쓰로틀

function memberIconColor(m: MemberPosition): [number, number, number, number] {
  if (m.stale) return SLATE;
  if (m.isParent) return CYAN;
  if (m.isGear) return AMBER;
  return [148, 163, 184, 200];
}

// ── 훅 ──

export function useGearReplayLayers(
  overlayRef: React.RefObject<MapboxOverlay | null>,
  buildBaseLayers: () => Layer[],
) {
  const frameCursorRef = useRef(0);
  // iran의 positionCursors — 멤버별 커서 (재생 시 O(1) 위치 탐색)
  const memberCursorsRef = useRef(new Map<string, number>());

  // buildBaseLayers를 최신 참조로 유지
  const baseLayersRef = useRef(buildBaseLayers);
  baseLayersRef.current = buildBaseLayers;

  /**
   * renderFrame — iran의 renderFrame과 동일 구조:
   * 1. 현재 위치 계산 (보간)
   * 2. 레이어 빌드
   * 3. overlay.setProps({ layers }) 직접 호출
   */
  const renderFrame = useCallback(() => {
    const overlay = overlayRef.current;
    if (!overlay) return;

    const state = useGearReplayStore.getState();
    const {
      historyFrames, frameTimes, memberTripsData, memberMetadata,
      currentTime, startTime, correlationItems,
      candidateTripsData, candidateMetadata,
    } = state;

    if (historyFrames.length === 0) {
      overlay.setProps({ layers: baseLayersRef.current() });
      return;
    }

    // 현재 프레임 찾기 (폴리곤 보간용)
    const { index: frameIdx, cursor: newCursor } = findFrameAtTime(
      frameTimes, currentTime, frameCursorRef.current,
    );
    frameCursorRef.current = newCursor;

    // 멤버 보간 — iran의 getCurrentVesselPositions 패턴:
    // 프레임 기반이 아닌 멤버별 개별 타임라인에서 보간 → 빈 구간도 연속 보간
    const relativeTime = currentTime - startTime;
    const members = interpolateFromTripsData(
      memberTripsData, memberMetadata, relativeTime, memberCursorsRef.current,
    );

    // 멤버 위치 기반 convex hull 폴리곤 (프레임 보간이 아닌 실시간 생성)
    const hullRing = computeConvexHull(members);

    const currentFrame = frameIdx >= 0 ? historyFrames[frameIdx] : null;

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const replayLayers: any[] = [];

    // 1. TripsLayer — 멤버 궤적 fade trail (iran과 동일 패턴)
    //    TripsLayer가 자체적으로 부드러운 궤적 애니메이션 처리
    if (memberTripsData.length > 0) {
      replayLayers.push(createTripsLayer(
        'replay-member-trails',
        memberTripsData,
        relativeTime,
        3_600_000, // 1시간 fade trail
      ));
    }

    // 4. 멤버 현재 위치 IconLayer (iran의 createVirtualShipLayers에 대응)
    const ships = members.filter(m => m.isParent);
    const gears = members.filter(m => m.isGear);
    const others = members.filter(m => !m.isParent && !m.isGear);

    if (ships.length > 0) {
      replayLayers.push(new IconLayer<MemberPosition>({
        id: 'replay-member-ships',
        data: ships,
        pickable: true,
        iconAtlas: SHIP_URI,
        iconMapping: {
          ship: { x: 0, y: 0, width: ICON_SIZE, height: ICON_SIZE, anchorY: ICON_SIZE * 0.62, mask: true },
        },
        getIcon: () => 'ship',
        getPosition: d => [d.lon, d.lat],
        getSize: 24,
        getAngle: d => -(d.cog ?? 0),
        getColor: d => memberIconColor(d),
        sizeUnits: 'pixels',
        sizeMinPixels: 10,
        billboard: false,
      }));
    }

    if (gears.length > 0) {
      replayLayers.push(new IconLayer<MemberPosition>({
        id: 'replay-member-gears',
        data: gears,
        pickable: true,
        iconAtlas: GEAR_URI,
        iconMapping: {
          gear: { x: 0, y: 0, width: ICON_SIZE, height: ICON_SIZE, anchorY: ICON_SIZE / 2, mask: true },
        },
        getIcon: () => 'gear',
        getPosition: d => [d.lon, d.lat],
        getSize: 16,
        getAngle: 0,
        getColor: d => memberIconColor(d),
        sizeUnits: 'pixels',
        sizeMinPixels: 6,
        billboard: false,
      }));
    }

    if (others.length > 0) {
      replayLayers.push(new IconLayer<MemberPosition>({
        id: 'replay-member-others',
        data: others,
        pickable: true,
        iconAtlas: SHIP_URI,
        iconMapping: {
          ship: { x: 0, y: 0, width: ICON_SIZE, height: ICON_SIZE, anchorY: ICON_SIZE * 0.62, mask: true },
        },
        getIcon: () => 'ship',
        getPosition: d => [d.lon, d.lat],
        getSize: 18,
        getAngle: d => -(d.cog ?? 0),
        getColor: d => memberIconColor(d),
        sizeUnits: 'pixels',
        sizeMinPixels: 8,
        billboard: false,
      }));
    }

    // 5. MMSI 라벨
    if (members.length > 0) {
      replayLayers.push(new TextLayer<MemberPosition>({
        id: 'replay-member-labels',
        data: members,
        getPosition: d => [d.lon, d.lat],
        getText: d => d.mmsi,
        getColor: [255, 255, 255, 200],
        getSize: 10,
        getPixelOffset: [0, -18],
        fontFamily: 'monospace',
        fontWeight: 'bold',
        outlineWidth: 2,
        outlineColor: [0, 0, 0, 200],
        sizeUnits: 'pixels',
        sizeMinPixels: 8,
        sizeMaxPixels: 12,
        billboard: true,
      }));
    }

    // 6. 멤버 위치 기반 convex hull 폴리곤
    if (hullRing) {
      replayLayers.push(new PolygonLayer({
        id: 'replay-polygon',
        data: [{ ring: hullRing }],
        getPolygon: d => d.ring,
        getFillColor: POLYGON_FILL,
        getLineColor: POLYGON_STROKE,
        getLineWidth: 2,
        lineWidthUnits: 'pixels',
        lineWidthMinPixels: 1,
        filled: true,
        stroked: true,
      }));
    }

    // 7. 추론 후보 위치 (correlation)
    if (correlationItems.length > 0 && currentFrame) {
      const memberMap = new Map(currentFrame.members.map(m => [m.mmsi, m]));
      const corrPositions = correlationItems
        .filter(c => {
          const m = memberMap.get(c.targetMmsi);
          return m && m.lat != null && m.lon != null;
        })
        .map(c => {
          const m = memberMap.get(c.targetMmsi)!;
          return { ...c, lon: m.lon, lat: m.lat };
        });

      if (corrPositions.length > 0) {
        replayLayers.push(new ScatterplotLayer({
          id: 'replay-corr-positions',
          data: corrPositions,
          getPosition: d => [d.lon, d.lat],
          getFillColor: d => {
            const s = d.score;
            if (s >= 0.72) return [16, 185, 129, 180] as [number, number, number, number];
            if (s >= 0.5) return [245, 158, 11, 180] as [number, number, number, number];
            return [100, 116, 139, 140] as [number, number, number, number];
          },
          getRadius: d => 5 + d.score * 8,
          radiusUnits: 'pixels',
          radiusMinPixels: 4,
          lineWidthMinPixels: 1,
          stroked: true,
          getLineColor: [255, 255, 255, 120],
          getLineWidth: 1,
          pickable: true,
        }));
      }
    }

    // 8. 후보 선박 항적 TripsLayer + 현재 위치 IconLayer
    if (candidateTripsData.length > 0) {
      // 후보 선박 궤적 fade trail (emerald)
      replayLayers.push(createTripsLayer(
        'replay-candidate-trails',
        candidateTripsData,
        relativeTime,
        3_600_000,
      ));

      // 후보 선박 현재 위치 (개별 타임라인 보간)
      const candPositions = interpolateFromTripsData(
        candidateTripsData,
        new Map([...candidateMetadata].map(([k, v]) => [k, { name: v.name, role: 'CANDIDATE', isParent: false }])),
        relativeTime,
        memberCursorsRef.current,
      );
      if (candPositions.length > 0) {
        replayLayers.push(new IconLayer({
          id: 'replay-candidate-ships',
          data: candPositions,
          pickable: true,
          iconAtlas: SHIP_URI,
          iconMapping: {
            ship: { x: 0, y: 0, width: ICON_SIZE, height: ICON_SIZE, anchorY: ICON_SIZE * 0.62, mask: true },
          },
          getIcon: () => 'ship',
          getPosition: (d: MemberPosition) => [d.lon, d.lat],
          getSize: 22,
          getAngle: (d: MemberPosition) => -(d.cog ?? 0),
          getColor: [16, 185, 129, 220] as [number, number, number, number], // emerald
          sizeUnits: 'pixels' as const,
          sizeMinPixels: 10,
          billboard: false,
        }));

        // 후보 선박 라벨
        replayLayers.push(new TextLayer({
          id: 'replay-candidate-labels',
          data: candPositions,
          getPosition: (d: MemberPosition) => [d.lon, d.lat],
          getText: (d: MemberPosition) => {
            const meta = candidateMetadata.get(d.mmsi);
            return meta?.name || d.mmsi;
          },
          getColor: [16, 185, 129, 220],
          getSize: 10,
          getPixelOffset: [0, -18],
          fontFamily: 'monospace',
          fontWeight: 'bold' as const,
          outlineWidth: 2,
          outlineColor: [0, 0, 0, 200],
          sizeUnits: 'pixels' as const,
          sizeMinPixels: 8,
          sizeMaxPixels: 12,
          billboard: true,
        }));
      }
    }

    // iran 핵심: baseLayers + replayLayers 합쳐서 overlay.setProps() 직접 호출
    const baseLayers = baseLayersRef.current();
    overlay.setProps({ layers: [...baseLayers, ...replayLayers] });
  }, [overlayRef]);

  /**
   * currentTime 구독 — iran useReplayLayer.ts:425~458 그대로 적용
   *
   * 핵심: 재생 중 쓰로틀에 걸려도 pendingRafId로 다음 rAF에 반드시 렌더 예약
   * → 프레임 드롭 없이 부드러운 애니메이션
   *
   * 가드 없이 항상 구독 — renderFrame 내부에서 historyFrames.length===0이면 baseLayers만 표시
   */
  useEffect(() => {
    let lastRenderTime = 0;
    let pendingRafId: number | null = null;

    const unsub = useGearReplayStore.subscribe(
      (s) => s.currentTime,
      () => {
        // 데이터 없으면 무시
        if (!useGearReplayStore.getState().groupKey) return;

        const isPlaying = useGearReplayStore.getState().isPlaying;
        // seek/정지: 즉시 렌더 (iran:437~439)
        if (!isPlaying) {
          renderFrame();
          return;
        }
        // 재생 중: 쓰로틀 + pending rAF (iran:441~451)
        const now = performance.now();
        if (now - lastRenderTime >= RENDER_INTERVAL_MS) {
          lastRenderTime = now;
          renderFrame();
        } else if (!pendingRafId) {
          pendingRafId = requestAnimationFrame(() => {
            pendingRafId = null;
            lastRenderTime = performance.now();
            renderFrame();
          });
        }
      },
    );
    return () => {
      unsub();
      if (pendingRafId) cancelAnimationFrame(pendingRafId);
    };
  }, [renderFrame]);

  // groupKey 변경 구독
  useEffect(() => {
    const unsub = useGearReplayStore.subscribe(
      s => s.groupKey,
      (groupKey) => {
        if (groupKey) {
          frameCursorRef.current = 0;
          memberCursorsRef.current.clear();
          renderFrame();
        } else {
          // 리플레이 종료 → 기본 레이어 복원
          const overlay = overlayRef.current;
          if (overlay) {
            overlay.setProps({ layers: baseLayersRef.current() });
          }
        }
      },
    );
    return unsub;
  }, [renderFrame, overlayRef]);
}
