import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { MouseEvent as ReactMouseEvent } from 'react';
import type { DroneFrameBasic } from '../utils/geoProjection';
import {
  STAGE_WIDTH,
  TRACK_END_PX,
  TRACK_START_PX,
  TRACK_WIDTH_PX,
  trackXFromRender,
} from './mocks/route';
import { cssVars } from './utils/cssVars';
import { formatMileage, mileageAtPx } from './utils/mileage';
import { PlaybackControls } from './components/PlaybackControls/PlaybackControls';
import { Timeline } from './components/Timeline/Timeline';
import { TimelineCursor } from './components/TimelineCursor/TimelineCursor';
import styles from './StationBar.module.scss';
import './tokens.css';

/** 영상 실제 fps (RoutePanel·StationOverlay 와 동일 기준). */
const VIDEO_FPS = 30000 / 1001;
/** 프레임→현재측점 precompute 시 프레임 샘플 간격(영상 step). */
const FRAME_STEP = 10;

interface GeoPoint {
  title: string;
  category: string;
  lat: number;
  lon: number;
  z: number;
  type: string;
}

/** 프레임별 precompute 결과. km=배지용 최근접측점, chain=구간방향용 연속 체이니지. */
interface ViewedPoint {
  frameNum: number;
  km: number;
  chain: number;
  time: number;
}

/** 측점 폴리라인(평면 투영). 연속 체이니지 계산용. */
interface StationLine {
  pts: { x: number; y: number; km: number }[];
  k: number; // 경도→m 환산 (cos(lat0)*111000)
}

/** 드론 lat/lon 을 측점 폴리라인에 투영 → 연속 체이니지(m).
 *  100m 양자화된 최근접측점과 달리 전진/후진 전환 시점이 실제 이동과 일치. */
function projectChainage(lat: number, lon: number, line: StationLine): number {
  const px = lon * line.k;
  const py = lat * 111000;
  const pts = line.pts;
  let bestD = Infinity;
  let bestKm = pts.length ? pts[0].km : -1;
  for (let i = 0; i < pts.length - 1; i++) {
    const a = pts[i];
    const b = pts[i + 1];
    const dx = b.x - a.x;
    const dy = b.y - a.y;
    const L2 = dx * dx + dy * dy;
    const t = L2 === 0 ? 0 : Math.max(0, Math.min(1, ((px - a.x) * dx + (py - a.y) * dy) / L2));
    const cx = a.x + dx * t;
    const cy = a.y + dy * t;
    const d = (px - cx) ** 2 + (py - cy) ** 2;
    if (d < bestD) {
      bestD = d;
      bestKm = a.km + (b.km - a.km) * t;
    }
  }
  return bestKm;
}

/** 데이터 기반 트랙 구간: km 증가(dir 1, 주황) / 감소(dir -1, 하늘색). px는 시간축. */
export interface BarSegment {
  startPx: number;
  endPx: number;
  dir: 1 | -1;
}
/** 측점값 라벨 (방향 전환점·시종점). px는 시간축. */
export interface KmLabel {
  px: number;
  text: string;
}
/** 구조물 마크 (교량/터널/역사). px는 통과 시점(시간축). */
export interface StructMark {
  px: number;
  title: string;
  category: string;
}

interface StationBarProps {
  currentTime: number;
  duration: number;
  playing: boolean;
  onTogglePlay: () => void;
  onStop: () => void;
  onCapture: () => void;
  onSeek: (time: number) => void;
  showStations: boolean;
  onToggleStations: () => void;
}

const clamp = (v: number, lo: number, hi: number): number =>
  Math.min(hi, Math.max(lo, v));

function stationKm(title: string): number {
  const m = title.match(/(\d+)[Kk](\d+)/);
  return m ? parseInt(m[1], 10) * 1000 + parseInt(m[2], 10) : -1;
}

/** 드론 GPS 최근접 측점 km (FOV 안에 측점이 없을 때 폴백). */
function nearestStationKm(f: DroneFrameBasic, stations: GeoPoint[]): number {
  let best = -1;
  let bd = Infinity;
  for (const st of stations) {
    const d = (f.lat - st.lat) ** 2 + (f.lon - st.lon) ** 2;
    if (d < bd) {
      bd = d;
      best = stationKm(st.title);
    }
  }
  return best;
}

/**
 * abcVideo 실제 영상 데이터 기반 측점 바.
 * - 트랙 x축 = 프레임(시간) 진행. 구간 색 = 측점 km 증가(주황)/감소(하늘색).
 * - 측점값 라벨·구조물(교량/터널/역사) = 재생 파일의 측점/POI 데이터로 배치.
 * - 커서 = 프레임 진행, 배지 = 드론 GPS 최근접 측점.
 * - 클릭/드래그/측점입력 seek = 시간(프레임) 기준 이동.
 */
export function StationBar({
  currentTime,
  duration,
  playing,
  onTogglePlay,
  onStop,
  onCapture,
  onSeek,
  showStations,
  onToggleStations,
}: StationBarProps) {
  const wrapRef = useRef<HTMLDivElement>(null);
  const stageRef = useRef<HTMLDivElement>(null);
  const [scale, setScale] = useState(0.5);
  const draggingRef = useRef(false);

  const [pois, setPois] = useState<GeoPoint[]>([]);
  const framesRef = useRef<DroneFrameBasic[]>([]);
  const [framesVersion, setFramesVersion] = useState(0);
  const viewedRef = useRef<ViewedPoint[]>([]);
  const [ready, setReady] = useState(false);

  useEffect(() => {
    fetch('/api/geo/pois')
      .then((r) => r.json())
      .then((data: GeoPoint[]) => setPois(Array.isArray(data) ? data : []))
      .catch(() => {});
  }, []);
  useEffect(() => {
    fetch(`/api/geo/frames?step=${FRAME_STEP}`)
      .then((r) => r.json())
      .then((data: DroneFrameBasic[]) => {
        framesRef.current = Array.isArray(data) ? data : [];
        setFramesVersion((v) => v + 1);
      })
      .catch(() => {});
  }, []);

  const stations = useMemo(
    () => pois.filter((p) => p.type === 'station' && stationKm(p.title) >= 0),
    [pois],
  );

  // 측점 폴리라인(평면 투영) — 연속 체이니지 계산용.
  const stationLine = useMemo<StationLine | null>(() => {
    if (!stations.length) return null;
    const sorted = [...stations].sort((a, b) => stationKm(a.title) - stationKm(b.title));
    const lat0 = sorted.reduce((s, p) => s + p.lat, 0) / sorted.length;
    const k = Math.cos((lat0 * Math.PI) / 180) * 111000;
    return {
      pts: sorted.map((p) => ({ x: p.lon * k, y: p.lat * 111000, km: stationKm(p.title) })),
      k,
    };
  }, [stations]);

  // 프레임별 precompute (frames·stations 준비되면 1회).
  //  - km: 드론 GPS 최근접 측점 (배지 표시용, 좌측 RoutePanel 과 동일).
  //  - chain: 측점 폴리라인 투영 연속 체이니지 (구간 방향용, 전환 타이밍 정확).
  useEffect(() => {
    const frames = framesRef.current;
    if (!frames.length || !stations.length || !stationLine) return;
    const out: ViewedPoint[] = new Array(frames.length);
    for (let i = 0; i < frames.length; i++) {
      const f = frames[i];
      out[i] = {
        frameNum: f.frame,
        km: nearestStationKm(f, stations),
        chain: projectChainage(f.lat, f.lon, stationLine),
        time: f.frame / VIDEO_FPS,
      };
    }
    viewedRef.current = out;
    setReady(true);
  }, [stations, stationLine, framesVersion]);

  /** 현재 시간에 가장 가까운 precompute 인덱스. */
  const viewedIdxAtTime = useCallback((t: number): number => {
    const arr = viewedRef.current;
    if (!arr.length) return -1;
    const target = t * VIDEO_FPS;
    let best = 0;
    let bd = Math.abs(arr[0].frameNum - target);
    for (let i = 1; i < arr.length; i++) {
      const d = Math.abs(arr[i].frameNum - target);
      if (d < bd) {
        bd = d;
        best = i;
      }
    }
    return best;
  }, []);

  // 현재 보는 측점 km
  const realKm = useMemo<number | null>(() => {
    if (!ready) return null;
    const idx = viewedIdxAtTime(currentTime);
    return idx >= 0 ? viewedRef.current[idx].km : null;
  }, [currentTime, ready, viewedIdxAtTime]);

  // 프레임(시간) 진행률 px — 커서의 단조 이동 기준.
  // 영상은 노선을 시간순으로 한 번 통과(leg 01→10)하므로, 시간 진행률이 곧
  // 트랙을 좌→우로 지나는 경로 진행률이다.
  const progressPx =
    duration > 0
      ? TRACK_START_PX + clamp(currentTime / duration, 0, 1) * TRACK_WIDTH_PX
      : TRACK_START_PX;

  // 커서 위치 = 프레임 진행률(progressPx)로만 단조 이동.
  // 측점 km은 노선에서 여러 번 반복(증가↔감소)되어, km으로 px를 역산하면
  // 같은 km의 여러 후보 중 하나로 스냅되며 구간 점프가 생긴다(사용자 보고).
  // → 진행은 유니크한 프레임 기준, 표시(배지)만 현재 보는 측점(realKm)으로 한다.
  const cursorPx = progressPx;

  const cursorText =
    realKm !== null && realKm >= 0
      ? formatMileage(realKm)
      : formatMileage(mileageAtPx(cursorPx));

  // ── 데이터 기반 측점 바 ──────────────────────────────────────────
  // 시간(프레임) 진행을 트랙 px로 선형 변환.
  const pxAtTime = useCallback(
    (t: number): number =>
      duration > 0
        ? TRACK_START_PX + clamp(t / duration, 0, 1) * TRACK_WIDTH_PX
        : TRACK_START_PX,
    [duration],
  );

  // viewedRef(프레임→측점 km) 추이로 구간 색·전환점 산출.
  // km 증가 구간 = dir 1(주황), 감소 구간 = dir -1(하늘색). HYST로 최근접 지터 무시.
  const { barSegments, kmLabels } = useMemo<{
    barSegments: BarSegment[];
    kmLabels: KmLabel[];
  }>(() => {
    const arr = viewedRef.current;
    if (!ready || !arr.length || duration <= 0)
      return { barSegments: [], kmLabels: [] };
    // 방향은 연속 체이니지(chain)로 판정 — 100m 양자화 km은 전환을 ~13s 빨리 잡아 영상과 어긋남.
    const HYST = 100; // m — 이 이상 반대로 움직여야 방향 전환으로 인정 (GPS 노이즈 무시)
    const segs: BarSegment[] = [];
    const bounds: { chain: number; time: number }[] = [
      { chain: arr[0].chain, time: arr[0].time },
    ];
    let dir: 1 | -1 = 1;
    let extCh = arr[0].chain;
    let extIdx = 0;
    let startIdx = 0;
    for (let i = 1; i < arr.length; i++) {
      const c = arr[i].chain;
      if (dir > 0 ? c > extCh : c < extCh) {
        extCh = c;
        extIdx = i;
      } else if (dir > 0 ? extCh - c >= HYST : c - extCh >= HYST) {
        segs.push({
          startPx: pxAtTime(arr[startIdx].time),
          endPx: pxAtTime(arr[extIdx].time),
          dir,
        });
        bounds.push({ chain: extCh, time: arr[extIdx].time });
        dir = dir > 0 ? -1 : 1;
        startIdx = extIdx;
        extCh = c;
        extIdx = i;
      }
    }
    segs.push({
      startPx: pxAtTime(arr[startIdx].time),
      endPx: pxAtTime(arr[arr.length - 1].time),
      dir,
    });
    bounds.push({ chain: arr[arr.length - 1].chain, time: arr[arr.length - 1].time });
    // 라벨은 전환점 체이니지를 최근접 측점(100m)으로 스냅해 표시.
    const labels: KmLabel[] = bounds.map((b) => ({
      px: pxAtTime(b.time),
      text: formatMileage(Math.round(b.chain / 100) * 100),
    }));
    return { barSegments: segs, kmLabels: labels };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [ready, duration, framesVersion, pxAtTime]);

  // 구조물(교량/터널/역사) → 체이니지(최근접 측점 km) → 통과 시점 px.
  const structureMarks = useMemo<StructMark[]>(() => {
    const arr = viewedRef.current;
    if (!ready || !arr.length || duration <= 0 || !stations.length) return [];
    const cats = new Set(['교량', '터널', '역사']);
    const seen = new Set<string>();
    const out: StructMark[] = [];
    for (const p of pois) {
      if (!cats.has(p.category)) continue;
      // (상)/(하)/(인상) 등 변형은 같은 구조물 → base 이름으로 통합(중복 라벨 제거).
      const base = p.title.replace(/\s*[(（].*$/, '').trim();
      if (seen.has(base)) continue;
      seen.add(base);
      const chain = nearestStationKm(
        { lat: p.lat, lon: p.lon } as DroneFrameBasic,
        stations,
      );
      if (chain < 0) continue;
      let best = -1;
      let bd = Infinity;
      for (let i = 0; i < arr.length; i++) {
        const d = Math.abs(arr[i].km - chain);
        if (d < bd) {
          bd = d;
          best = i;
        }
      }
      if (best >= 0)
        out.push({ px: pxAtTime(arr[best].time), title: base, category: p.category });
    }
    return out;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [ready, duration, framesVersion, pois, stations, pxAtTime]);

  // 방향 색 트랙을 CSS linear-gradient 로 구성. 구간 경계(드론 회전, 측점 변화 ~0,
  // 약 3~4초)는 딱딱한 경계 대신 부드러운 그라데이션으로 주황↔하늘색이 섞이게 한다.
  const trackGradient = useMemo<string>(() => {
    const segs = barSegments;
    if (!segs.length) return '';
    const FWD = '#ff8a25'; // 전진(km 증가) 주황
    const BWD = '#06a4c8'; // 후진(km 감소) 하늘색
    const col = (d: 1 | -1) => (d > 0 ? FWD : BWD);
    // 전환 폭 ≈ 4초 (회전 소요시간). 최소/최대 px 로 가시성 보장.
    const transPx = duration > 0 ? clamp((4 / duration) * TRACK_WIDTH_PX, 6, 40) : 10;
    const pct = (px: number) =>
      clamp(((px - TRACK_START_PX) / TRACK_WIDTH_PX) * 100, 0, 100);
    const stops: string[] = [`${col(segs[0].dir)} 0%`];
    for (let i = 1; i < segs.length; i++) {
      const b = segs[i].startPx;
      const w = Math.min(
        transPx / 2,
        (segs[i - 1].endPx - segs[i - 1].startPx) / 2,
        (segs[i].endPx - segs[i].startPx) / 2,
      );
      stops.push(`${col(segs[i - 1].dir)} ${pct(b - w).toFixed(2)}%`);
      stops.push(`${col(segs[i].dir)} ${pct(b + w).toFixed(2)}%`);
    }
    stops.push(`${col(segs[segs.length - 1].dir)} 100%`);
    return `linear-gradient(to right, ${stops.join(', ')})`;
  }, [barSegments, duration]);

  // 현재 위치가 역방향(km 감소, 하늘색) 구간이면 커서도 파란색.
  const currentReverse = useMemo<boolean>(() => {
    for (const s of barSegments) {
      if (cursorPx >= s.startPx && cursorPx <= s.endPx) return s.dir < 0;
    }
    return false;
  }, [barSegments, cursorPx]);

  // 컨테이너 폭 기준 균등 스케일
  useEffect(() => {
    const el = wrapRef.current;
    if (!el) return;
    const update = (): void =>
      setScale((el.clientWidth || STAGE_WIDTH) / STAGE_WIDTH);
    update();
    const ro = new ResizeObserver(update);
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  // 클릭/드래그 트랙 px → 시간(프레임) 선형 변환으로 seek (시간축 일관).
  const seekToTrackX = useCallback(
    (trackX: number) => {
      if (duration <= 0) return;
      const px = clamp(trackX, TRACK_START_PX, TRACK_END_PX);
      const targetTime = ((px - TRACK_START_PX) / TRACK_WIDTH_PX) * duration;
      onSeek(clamp(targetTime, 0, duration));
    },
    [duration, onSeek],
  );

  const seekFromClientX = useCallback(
    (clientX: number) => {
      const stage = stageRef.current;
      if (!stage) return;
      const rect = stage.getBoundingClientRect();
      const stageX = ((clientX - rect.left) / rect.width) * STAGE_WIDTH;
      seekToTrackX(trackXFromRender(stageX));
    },
    [seekToTrackX],
  );

  useEffect(() => {
    const move = (e: globalThis.MouseEvent): void => {
      if (draggingRef.current) seekFromClientX(e.clientX);
    };
    const up = (): void => {
      draggingRef.current = false;
    };
    window.addEventListener('mousemove', move);
    window.addEventListener('mouseup', up);
    return () => {
      window.removeEventListener('mousemove', move);
      window.removeEventListener('mouseup', up);
    };
  }, [seekFromClientX]);

  const handleSeekDown = useCallback(
    (e: ReactMouseEvent<HTMLDivElement>) => {
      draggingRef.current = true;
      seekFromClientX(e.clientX);
    },
    [seekFromClientX],
  );

  // 측점입력(예: 158k200) → 그 측점을 보는(최근접) 프레임으로 seek (실데이터 기반).
  const handleJumpToMileage = useCallback(
    (km: number) => {
      const arr = viewedRef.current;
      if (!arr.length || duration <= 0) return;
      let best = -1;
      let bd = Infinity;
      for (let i = 0; i < arr.length; i++) {
        const d = Math.abs(arr[i].km - km);
        if (d < bd) {
          bd = d;
          best = i;
        }
      }
      if (best >= 0) onSeek(clamp(arr[best].time, 0, duration));
    },
    [duration, onSeek],
  );

  return (
    <div
      ref={wrapRef}
      className={styles.wrap}
      style={cssVars({ '--bar-scale': scale })}
    >
      <div ref={stageRef} className={styles.layer}>
        <div className={styles.bottomBar}>
          <PlaybackControls
            playing={playing}
            onTogglePlay={onTogglePlay}
            onStop={onStop}
            onCapture={onCapture}
            onJumpToMileage={handleJumpToMileage}
            lineOn={showStations}
            onToggleLine={onToggleStations}
          />
          <Timeline
            posPx={cursorPx}
            onSeekDown={handleSeekDown}
            trackGradient={trackGradient}
            kmLabels={kmLabels}
            structures={structureMarks}
          />
        </div>
        <TimelineCursor
          posPx={cursorPx}
          mileageText={cursorText}
          reverse={currentReverse}
          onSeekDown={handleSeekDown}
        />
      </div>
    </div>
  );
}
