import React, { useRef, useImperativeHandle, forwardRef, useState, useEffect, useMemo } from 'react';
import StationOverlay from '../overlay/StationOverlay';
import RoutePanel from '../overlay/RoutePanel';
import RouteInfoOverlay from '../overlay/RouteInfoOverlay';
import 'video.js/dist/video-js.css';
import { useVideoPlayer } from '../../hooks/useVideoPlayer';
import { useFrameStep } from '../../hooks/useFrameStep';
import { useKeyboard } from '../../hooks/useKeyboard';
import { usePlayerStore } from '../../store/playerStore';
import { useGeoStore } from '../../store/geoStore';
import { useSettingsStore, KNOWN_GRADES } from '../../store/settingsStore';
import { captureFrame, downloadDataUrl } from '../../utils/frameCapture';
import { generateByDemKmls } from '../../utils/demKml';
import { secondsToTimecode, secondsToFrame } from '../../utils/timecode';
import { useCaptureStore } from '../../store/captureStore';
import HlsConversionStatus from './HlsConversionStatus';
import { StationBar } from '../../stationbar/StationBar';

/** 드롭된 디렉토리/파일 엔트리에서 모든 파일을 재귀 수집 (폴더 드래그&드롭 지원). */
function collectDropEntry(entry: FileSystemEntry, out: File[]): Promise<void> {
  return new Promise((resolve) => {
    if (entry.isFile) {
      (entry as FileSystemFileEntry).file((f) => { out.push(f); resolve(); }, () => resolve());
    } else if (entry.isDirectory) {
      const reader = (entry as FileSystemDirectoryEntry).createReader();
      const readBatch = () => {
        reader.readEntries(async (ents) => {
          if (!ents.length) { resolve(); return; } // 모든 배치 소진
          await Promise.all(ents.map((en) => collectDropEntry(en, out)));
          readBatch();
        }, () => resolve());
      };
      readBatch();
    } else {
      resolve();
    }
  });
}

export interface VideoPlayerHandle {
  loadLocalFile: (file: File, autoPlay?: boolean, startAtSec?: number) => void;
  loadServerStream: (videoId: string, filename: string) => void | Promise<void>;
  seekTo: (time: number) => void;
  getVideoElement: () => HTMLVideoElement | null;
}

/** 초 → "m:ss" (재생목록 길이 표시용). 미상이면 "--:--". */
function fmtDur(d: number | null | undefined): string {
  if (d == null || !isFinite(d)) return '--:--';
  return `${Math.floor(d / 60)}:${String(Math.floor(d % 60)).padStart(2, '0')}`;
}

interface VideoPlayerProps {
  onAddMemo: (time: number) => void;
  onToggleHelp?: () => void;
}

const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
  function VideoPlayer({ onAddMemo, onToggleHelp }, ref) {
    const containerRef = useRef<HTMLDivElement>(null);
    const wrapperRef = useRef<HTMLDivElement>(null);

    const { playerRef, loadLocalFile, loadServerStream, switchToHls, getVideoElement } =
      useVideoPlayer(containerRef);

    const { stepForward, stepBackward } = useFrameStep(playerRef);
    const { currentTime, duration, playing, source, playbackRate, videoReady, videoWidth, videoHeight } = usePlayerStore();

    // 커서 매끄러운 이동:
    //  - 라이브 시간(smoothTimeRef): 매 프레임 '단조' 벽시계 보간. StationBar가 ref로 읽어
    //    커서/진행바를 직접(transform) 갱신 → React 60fps 리렌더 없이 매끄럽게.
    //  - state(smoothTime): 배지 숫자·색 변경용으로만 throttle(≈10fps) 갱신.
    const [smoothTime, setSmoothTime] = useState(0);
    const smoothTimeRef = useRef(0);
    const anchorRef = useRef({ media: 0, wall: 0 });
    const lastSetRef = useRef(0);
    useEffect(() => {
      let raf = 0;
      // 시크(클릭/드래그 이동) 시 커서를 즉시 그 위치로 재동기화.
      // (단조 보간은 앵커보다 앞쪽으로의 뒤로가기 시크를 무시하므로 별도 처리 필요)
      const onSeeked = (): void => {
        const p = playerRef.current;
        if (!p || p.isDisposed()) return;
        const t = p.currentTime() ?? 0;
        anchorRef.current = { media: t, wall: performance.now() };
        smoothTimeRef.current = t;
        lastSetRef.current = t;
        setSmoothTime(t);
      };
      playerRef.current?.on('seeked', onSeeked);
      const tick = (): void => {
        const p = playerRef.current;
        if (p && !p.isDisposed()) {
          const dur = p.duration() ?? 0;
          let t: number;
          if (p.paused()) {
            t = p.currentTime() ?? 0;
            anchorRef.current = { media: t, wall: performance.now() };
          } else {
            const a = anchorRef.current;
            const rate = p.playbackRate() ?? 1;
            let est = a.media + ((performance.now() - a.wall) / 1000) * rate;
            const real = p.currentTime() ?? 0;
            // 단조: 작은 역행은 무시(흔들림 방지). 뒤처짐(real이 앞섬) 또는 시크(뒤로)만 재동기화.
            if (real - est > 0.3 || real < a.media - 0.3) {
              est = real;
              anchorRef.current = { media: real, wall: performance.now() };
            }
            t = dur > 0 ? Math.min(est, dur) : est;
          }
          smoothTimeRef.current = t;
          if (Math.abs(t - lastSetRef.current) >= 0.1) {
            lastSetRef.current = t;
            setSmoothTime(t);
          }
        }
        raf = requestAnimationFrame(tick);
      };
      raf = requestAnimationFrame(tick);
      return () => {
        cancelAnimationFrame(raf);
        playerRef.current?.off('seeked', onSeeked);
      };
    }, [playerRef, source]);
    const loadFromFolder = useGeoStore((s) => s.loadFromFolder);
    const geoLoaded = useGeoStore((s) => s.loaded);
    // 드론 정보 — 하단바에 GPS/고도 항상 표시.
    const storeFrames = useGeoStore((s) => s.frames);
    // dev_mode (route.json) — 0 이면 '영상제어' 버튼과 제어 UI 전체를 숨김(뷰어 모드).
    // routeInfo.dev_mode 우선, 최상위 dev_mode 도 허용. 없으면 1(표시) — 기존 동작 유지.
    const storeRouteMeta = useGeoStore((s) => s.routeMeta);
    const fileDevMode = (() => {
      const ri = storeRouteMeta?.routeInfo as Record<string, unknown> | undefined;
      const v = ri?.dev_mode ?? (storeRouteMeta as unknown as Record<string, unknown> | null)?.dev_mode;
      return v === 0 || v === '0' ? 0 : 1;
    })();
    // F2 → 개발자 모드(1↔0) 전환 — route.json 값을 세션 한정으로 덮어씀(파일 미변경).
    // 새 폴더 로드 시 초기화되어 다시 파일 값을 따른다. 텍스트 입력 중에는 무시.
    const [devModeOverride, setDevModeOverride] = useState<number | null>(null);
    useEffect(() => { setDevModeOverride(null); }, [storeRouteMeta]);
    const devMode = devModeOverride ?? fileDevMode;
    useEffect(() => {
      const onKey = (e: KeyboardEvent): void => {
        if (e.code !== 'F2') return;
        const t = e.target as HTMLElement;
        if (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable) return;
        e.preventDefault();
        setDevModeOverride((prev) => ((prev ?? fileDevMode) === 0 ? 1 : 0));
      };
      window.addEventListener('keydown', onKey);
      return () => window.removeEventListener('keydown', onKey);
    }, [fileDevMode]);
    // 분할 영상 재생목록 — 폴더 로드 시 이름 오름차순. 목록 클릭으로 세그먼트 직접 선택 재생.
    const videoFiles = useGeoStore((s) => s.videoFiles);
    const videoDurations = useGeoStore((s) => s.videoDurations);
    const videoIndex = useGeoStore((s) => s.videoIndex);
    // 영상목록 콤보(좌상단 타이틀 아래) 펼침 상태 — 기본 접힘.
    const [showPlaylist, setShowPlaylist] = useState(false);
    // 시설등급(시설종별) 표시 필터 — 재생바/패널이 구독하는 설정 스토어.
    const gradeFilter = useSettingsStore((s) => s.gradeFilter);
    const setGradeFilter = useSettingsStore((s) => s.setGradeFilter);
    const poiOverlapExclude = useSettingsStore((s) => s.poiOverlapExclude);
    const setPoiOverlapExclude = useSettingsStore((s) => s.setPoiOverlapExclude);
    // 선형(중심선)·드론 궤적 영상 오버레이 토글 — StationOverlay 와 공유(설정 스토어).
    const showCenterline = useSettingsStore((s) => s.showCenterline);
    const setShowCenterline = useSettingsStore((s) => s.setShowCenterline);
    // A → 선형(중심선+측점 라벨) 표시/숨김 토글. 텍스트 입력·수정키 조합(Ctrl+A 등)은 무시.
    useEffect(() => {
      const onKey = (e: KeyboardEvent): void => {
        if (e.code !== 'KeyA' || e.ctrlKey || e.metaKey || e.altKey) return;
        const t = e.target as HTMLElement;
        if (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable) return;
        e.preventDefault();
        setShowCenterline(!showCenterline);
      };
      window.addEventListener('keydown', onKey);
      return () => window.removeEventListener('keydown', onKey);
    }, [showCenterline, setShowCenterline]);
    const showDronePath = useSettingsStore((s) => s.showDronePath);
    const setShowDronePath = useSettingsStore((s) => s.setShowDronePath);
    const showRoutePanel = useSettingsStore((s) => s.showRoutePanel);
    const setShowRoutePanel = useSettingsStore((s) => s.setShowRoutePanel);
    // 하단 도구 패널: UI에서 숨김(코드는 보존). 다시 표시하려면 true 로.
    const SHOW_TOOLBAR = false;

    // 폴더 선택: 지리정보 파싱 + 영상 재생
    const handleSelectFolder = async (files: File[]) => {
      if (!files.length) return;
      try {
        const videoFile = await loadFromFolder(files);
        if (videoFile) loadLocalFile(videoFile);
        else console.warn('[geo] 폴더에 영상 파일(mp4/webm)이 없습니다 — 지리정보만 로드');
        // KMZ(POI·구조물 원본) 필수 — 측점/드론은 있는데 KMZ만 빠진 경우 = 데이터 누락.
        const { kmzMissing, stations, frames } = useGeoStore.getState();
        if (kmzMissing && (stations.length > 0 || frames.length > 0)) {
          alert(
            'KMZ(POI·구조물 원본)가 폴더에 없습니다.\n' +
            'POI·구조물이 표시되지 않습니다. KMZ를 포함해 데이터를 재구축·전달하세요.\n' +
            '(측점·드론 정보는 정상 로드되었습니다.)',
          );
        }
        // 표고(z) 없는 KML → DEM(SRTM 30m)으로 채운 <이름>_byDem.kml 자동 생성·다운로드.
        // 폴더에 _byDem 이 이미 있으면 건너뛰고, 로더는 _byDem 을 원본보다 우선 사용한다.
        void generateByDemKmls(files)
          .then((made) => {
            if (made.length) {
              alert(
                `표고 없는 KML을 발견해 DEM 표고를 채운 파일을 생성했습니다:\n${made.join(', ')}\n\n` +
                '다운로드된 파일을 영상 폴더에 넣어두면 다음 폴더 선택부터 자동으로 사용됩니다.',
              );
            }
          })
          .catch((e) => console.warn('[DEM] _byDem.kml 생성 실패(오프라인이면 정상):', e));
      } catch (err) {
        console.error('[geo] 폴더 로드 실패', err);
      }
    };

    // 재생목록에서 세그먼트 선택 — 같은 항목이면 처음부터 재생, 다른 항목이면
    // 드론 로그를 해당 구간으로 재정렬(loadSegment)한 뒤 전환 재생.
    const handleSelectSegment = async (i: number): Promise<void> => {
      const geo = useGeoStore.getState();
      if (usePlayerStore.getState().source?.kind !== 'local') return;
      if (i === geo.videoIndex) {
        playerRef.current?.currentTime(0);
        const p = playerRef.current?.play();
        if (p && typeof p.catch === 'function') p.catch(() => {});
      } else {
        const file = await geo.loadSegment(i);
        if (!file) return;
        loadLocalFile(file, true);  // 목록 선택 — 로딩 완료 시 0초부터 자동 재생
      }
    };

    // 측점 검색 전 구간 점프 — 다른 세그먼트의 통과 지점으로 전환+시크.
    // 같은 세그먼트면 시크만, 다르면 loadSegment 후 해당 시각부터(재생 상태 유지).
    const handleSeekSegment = async (i: number, timeSec: number): Promise<void> => {
      const geo = useGeoStore.getState();
      if (usePlayerStore.getState().source?.kind !== 'local') return;
      if (i === geo.videoIndex) {
        playerRef.current?.currentTime(timeSec);
        return;
      }
      const wasPlaying = usePlayerStore.getState().playing;
      const file = await geo.loadSegment(i);
      if (!file) return;
      loadLocalFile(file, wasPlaying, timeSec);
    };

    // 분할 영상 연속재생 — 영상이 끝나면 같은 폴더의 다음 영상(이름 오름차순)을 자동 재생.
    // 드론 로그(시간 기반)는 loadSegment 가 누적 오프셋으로 재정렬해 궤적 정합을 유지한다.
    useEffect(() => {
      const player = playerRef.current;
      if (!player) return;
      const onEnded = (): void => {
        void (async () => {
          const geo = useGeoStore.getState();
          if (!geo.loaded || geo.videoFiles.length < 2) return;
          // 폴더 기반 로컬 재생에서만 동작 (서버 스트림/단일 파일은 대상 아님)
          if (usePlayerStore.getState().source?.kind !== 'local') return;
          const next = geo.videoIndex + 1;
          if (next >= geo.videoFiles.length) return;
          const file = await geo.loadSegment(next);
          if (!file) return;
          loadLocalFile(file, true);  // 연속재생 — 로딩 완료 시 0초부터 자동 재생
        })();
      };
      player.on('ended', onEnded);
      return () => { player.off('ended', onEnded); };
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [playerRef, loadLocalFile]);

    // Expose methods to parent via ref
    useImperativeHandle(ref, () => ({
      loadLocalFile,
      loadServerStream,
      seekTo: (time: number) => {
        playerRef.current?.currentTime(time);
      },
      getVideoElement,
    }));

    const addCapture = useCaptureStore((s) => s.addCapture);

    const handleCaptureFrame = () => {
      const video = getVideoElement();
      if (!video) return;
      const dataUrl = captureFrame(video);
      if (!dataUrl) return;
      const filename = `frame_${secondsToTimecode(currentTime).replace(/[:.]/g, '-')}.jpg`;
      downloadDataUrl(dataUrl, filename);
      addCapture({
        id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
        dataUrl,
        time: currentTime,
        filename,
        createdAt: Date.now(),
      });
    };

    const handleAddMemo = () => onAddMemo(currentTime);
    const [showStations, setShowStations] = useState(true);
    // 영상제어 토글: 카메라 파라미터·프레임상태·배속 표시 on/off
    const [showVideoControls, setShowVideoControls] = useState(true);
    // 좌하단 그룹을 재생바 위에 두기 위해 StationBar 높이를 동적 측정.
    const barWrapRef = useRef<HTMLDivElement>(null);
    const [barHeight, setBarHeight] = useState(0);
    useEffect(() => {
      const el = barWrapRef.current;
      if (!el) return;
      const update = (): void => setBarHeight(el.offsetHeight);
      update();
      const ro = new ResizeObserver(update);
      ro.observe(el);
      return () => ro.disconnect();
    }, [geoLoaded]);
    // 노선 배너(429.5×65 @1920 비율) 높이만큼 카메라 파라미터를 아래로.
    const [stageWidth, setStageWidth] = useState(0);
    useEffect(() => {
      const el = wrapperRef.current;
      if (!el) return;
      const update = (): void => setStageWidth(el.clientWidth);
      update();
      const ro = new ResizeObserver(update);
      ro.observe(el);
      return () => ro.disconnect();
    }, []);
    const paramTop = Math.round((stageWidth / 1920) * 65) + 10;
    const [showUtilBar, setShowUtilBar] = useState(false);

    const handleTogglePlay = (): void => {
      const p = playerRef.current;
      if (!p) return;
      if (playing) p.pause();
      else void p.play();
    };
    const handleStop = (): void => {
      const p = playerRef.current;
      if (!p) return;
      p.pause();
      p.currentTime(0);
    };
    const handleSeek = (t: number): void => {
      playerRef.current?.currentTime(t);
    };

    useKeyboard({
      playerRef,
      onStepForward: stepForward,
      onStepBackward: stepBackward,
      onCaptureFrame: handleCaptureFrame,
      onAddMemo: handleAddMemo,
      onToggleHelp,
      containerRef: wrapperRef,
    });

    // 드래그&드롭 — 폴더(영상+측점/POI) 또는 단일 영상 파일.
    //   폴더는 dataTransfer.files 가 비어 있으므로 webkitGetAsEntry 로 디렉토리를 재귀 순회한다.
    const handleDrop = (e: React.DragEvent) => {
      e.preventDefault();
      // await 후엔 dataTransfer 가 무효화될 수 있어 동기적으로 먼저 캡처.
      const entries: FileSystemEntry[] = [];
      const dt = e.dataTransfer;
      if (dt.items) {
        for (let i = 0; i < dt.items.length; i++) {
          const en = dt.items[i].webkitGetAsEntry?.();
          if (en) entries.push(en);
        }
      }
      const flatFiles = Array.from(dt.files);
      void (async () => {
        if (entries.length) {
          const out: File[] = [];
          await Promise.all(entries.map((en) => collectDropEntry(en, out)));
          if (out.length) { handleSelectFolder(out); return; }
        }
        if (flatFiles.length > 1) { handleSelectFolder(flatFiles); return; }
        const file = flatFiles[0];
        if (file?.type.startsWith('video/')) loadLocalFile(file);
      })();
    };

    // 영상 실제 fps — 데이터(드론 CSV 마지막 프레임 번호) ÷ 영상 길이(초) 로 자동 산출 후
    //   표준 fps(24/25/29.97/30/50/60 등) 중 가까운 값에 스냅. 영상마다 fps 가 달라도 자동 대응.
    //   (드론 CSV 엔 시간이 없고 frame_cnt 만 있어, 영상 length 와 결합해야 fps 를 알 수 있다.)
    //   데이터/영상길이 미확보 시 29.97 폴백. VFC 자동감지는 31fps 오감지가 있어 사용 안 함.
    const effectiveFps = useMemo(() => {
      const FALLBACK = 30000 / 1001;
      if (!storeFrames.length || !duration || duration <= 0) return FALLBACK;
      let maxF = 0;
      for (const f of storeFrames) if (f.frame > maxF) maxF = f.frame;
      if (maxF <= 0) return FALLBACK;
      const raw = maxF / duration;                       // 마지막 프레임 번호 ÷ 영상 길이(초)
      const STD = [23.976, 24, 25, 29.97, 30, 50, 59.94, 60];
      let best = STD[0], bd = Math.abs(raw - STD[0]);
      for (const s of STD) { const d = Math.abs(raw - s); if (d < bd) { bd = d; best = s; } }
      return bd <= best * 0.1 ? best : raw;              // 표준값 ±10% 이내면 스냅, 아니면 원시값
    }, [storeFrames, duration]);
    const frame = secondsToFrame(currentTime, effectiveFps);
    const videoId = source?.kind === 'server' ? source.videoId : null;

    // 드론 정보(측점진단) — 현재 프레임에 가장 가까운 드론 프레임의 GPS/고도만 표시.
    // 드론 GPS·고도 HUD 는 항상 표시 → 토글 없이 프레임만 있으면 계산.
    const stationDiag = useMemo(() => {
      if (!storeFrames.length) return null;
      let best = storeFrames[0], bd = Math.abs(storeFrames[0].frame - frame);
      for (const df of storeFrames) {
        const d = Math.abs(df.frame - frame);
        if (d < bd) { bd = d; best = df; }
      }
      return { f: best };
    }, [storeFrames, frame]);

    return (
      <div
        ref={wrapperRef}
        className="relative bg-black w-full h-full"
        onDrop={handleDrop}
        onDragOver={(e) => e.preventDefault()}
      >
        {/* 영상 영역 — 컨테이너 전체를 채우는 relative 래퍼 (사이니지: 화면 가득) */}
        <div className="relative w-full h-full">
          {/* Video.js container — 영상이 영역을 꽉 채우는 베이스 레이어 (object-fit:cover) */}
          {/* 영상 클릭 = 재생/일시정지 토글 (컨트롤바 숨김 상태) */}
          <div
            data-vjs-player
            ref={containerRef}
            className="absolute inset-0 w-full h-full"
            style={{ cursor: source ? 'pointer' : 'default' }}
            onClick={() => {
              if (source) handleTogglePlay();
            }}
          />
          {/* 노선 정보 배너 — 영상 좌상단 (route.json routeInfo) */}
          <RouteInfoOverlay />
          {/* 좌하단 그룹(재생바 위) — 한 줄: 영상제어 → 배속 → 프레임정보 → 겹침제외 → 시설등급 */}
          {/* dev_mode=0(route.json) 이면 그룹 전체 숨김 — 뷰어 모드 */}
          {source && devMode !== 0 && (
            <div
              className="absolute left-2 z-30 flex items-center gap-2 flex-wrap pointer-events-auto"
              style={{ bottom: (barHeight || 130) + 8 }}
            >
              {/* 영상제어 토글 — 버튼만, 고정폭(가장 긴 'OFF' 기준이라 ON/OFF로 폭이 안 변함) */}
              <button
                type="button"
                onClick={() => setShowVideoControls((v) => !v)}
                className={`text-xs px-2 py-1 rounded border font-semibold text-center min-w-[96px] ${showVideoControls ? 'bg-amber-400 border-amber-300 text-black' : 'bg-black/70 border-gray-600 text-gray-300'}`}
              >영상제어 {showVideoControls ? 'ON' : 'OFF'}</button>
              {showVideoControls && (
                <>
                  {/* 배속 패널 (영상제어 우측) */}
                  <div className="flex items-center gap-1 bg-black/70 px-2 py-1 rounded">
                    <span className="text-gray-400 text-xs">배속</span>
                    {[0.5, 1, 1.5, 2, 3, 4].map((r) => (
                      <button
                        key={r}
                        type="button"
                        onClick={() => playerRef.current?.playbackRate(r)}
                        className={`text-xs px-1.5 py-0.5 rounded ${
                          Math.abs(playbackRate - r) < 0.01
                            ? 'bg-amber-400 text-black'
                            : 'bg-gray-700 text-gray-200 hover:bg-gray-600'
                        }`}
                      >
                        {r}x
                      </button>
                    ))}
                  </div>
                  {/* 영상 프레임 표시 패널 — 프레임 번호를 고정폭(6ch)으로 묶어 자릿수가 늘어도 폭 불변 */}
                  <span className="bg-black/70 text-gray-200 text-xs px-2 py-1 rounded font-mono whitespace-nowrap">
                    {secondsToTimecode(currentTime)} | F<span className="inline-block text-left" style={{ minWidth: '6ch' }}>{frame}</span> | {effectiveFps.toFixed(2)}fps
                  </span>
                  {geoLoaded && (
                    <>
                      {/* 좌측 패널 토글 — 선형 왼편, 동일 폭(min-w-[96px]) */}
                      <button
                        type="button"
                        onClick={() => setShowRoutePanel(!showRoutePanel)}
                        title="좌측 노선 패널 표시/숨김"
                        className={`text-xs px-2 py-1 rounded border text-center min-w-[96px] ${showRoutePanel ? 'bg-amber-400 border-amber-300 text-black' : 'bg-black/70 border-gray-600 text-gray-300'}`}
                      >좌측 패널 {showRoutePanel ? 'ON' : 'OFF'}</button>
                      {/* 선형(중심선) 토글 — 겹침제외와 동일 폭(min-w-[96px]) */}
                      <button
                        type="button"
                        onClick={() => setShowCenterline(!showCenterline)}
                        title="선형(중심선) 표시/숨김"
                        className={`text-xs px-2 py-1 rounded border text-center min-w-[96px] ${showCenterline ? 'bg-amber-400 border-amber-300 text-black' : 'bg-black/70 border-gray-600 text-gray-300'}`}
                      >선형 {showCenterline ? 'ON' : 'OFF'}</button>
                      {/* 드론 궤적 토글 — 겹침제외와 동일 폭(min-w-[96px]) */}
                      <button
                        type="button"
                        onClick={() => setShowDronePath(!showDronePath)}
                        title="드론 궤적 표시/숨김"
                        className={`text-xs px-2 py-1 rounded border text-center min-w-[96px] ${showDronePath ? 'bg-amber-400 border-amber-300 text-black' : 'bg-black/70 border-gray-600 text-gray-300'}`}
                      >드론궤적 {showDronePath ? 'ON' : 'OFF'}</button>
                      {/* 드론 위치정보 — 드론궤적 버튼 바로 우측에 붙임. GPS·고도 항상 표시. */}
                      {stationDiag && (
                        <div className="bg-black/70 px-2 py-1 rounded font-mono text-xs text-gray-200 whitespace-nowrap">
                          GPS {stationDiag.f.lat.toFixed(6)}, {stationDiag.f.lon.toFixed(6)}
                          <span className="text-gray-400"> · 절대고도 {stationDiag.f.altitude.toFixed(1)}m</span>
                        </div>
                      )}
                      {/* 겹침제외 토글 — UI 숨김(기능/상태는 보존, 기본 ON). 다시 보이려면 주석 해제. */}
                      {/* <button
                        type="button"
                        onClick={() => setPoiOverlapExclude(!poiOverlapExclude)}
                        title="ON=겹친 POI 숨김 / OFF=모든 POI 표시"
                        className={`text-xs px-2 py-1 rounded border text-center min-w-[96px] ${poiOverlapExclude ? 'bg-blue-600/80 border-blue-400 text-white' : 'bg-black/70 border-gray-600 text-gray-300'}`}
                      >겹침제외 {poiOverlapExclude ? 'ON' : 'OFF'}</button> */}
                      {/* 시설등급 패널 */}
                      <div className="flex items-center gap-2 bg-black/70 px-2 py-1 rounded">
                        <span className="text-gray-400 text-xs">시설등급</span>
                        {KNOWN_GRADES.map((g) => (
                          <label
                            key={g}
                            className="flex items-center gap-1 text-xs text-gray-200 cursor-pointer select-none"
                          >
                            <input
                              type="checkbox"
                              className="accent-amber-400"
                              checked={gradeFilter[g] ?? false}
                              onChange={(e) => setGradeFilter(g, e.target.checked)}
                            />
                            {g}
                          </label>
                        ))}
                      </div>
                    </>
                  )}
                </>
              )}
            </div>
          )}
          {/* 루트 패널 미니맵 — 위(배너+카메라파라미터)·아래(배속/토글/재생바) 침범 방지 */}
          <RoutePanel
            currentTime={currentTime}
            visible={showStations && showRoutePanel}
            onSeek={(time) => playerRef.current?.currentTime(time)}
            topPx={paramTop + 40}
            bottomPx={(barHeight || 130) + 90}
          />
        </div>

        {/* 영상 목록 콤보 — 좌상단 타이틀(노선 배너) 아래. 버튼 클릭 시 목록이 아래로 펼쳐지고
            항목 선택 시 해당 영상으로 즉시 전환(드론 로그도 세그먼트에 맞춰 자동 재정렬).
            현재 재생 항목 재선택 시 처음부터 재생. */}
        {source?.kind === 'local' && videoFiles.length > 0 && (
          <div className="absolute left-2 z-40 w-80 pointer-events-auto" style={{ top: paramTop }}>
            <button
              type="button"
              onClick={() => setShowPlaylist((v) => !v)}
              title="폴더 영상 목록 펼치기/접기"
              className={`w-full flex items-center gap-1.5 px-2 py-1 rounded border text-xs text-left ${
                showPlaylist
                  ? 'bg-amber-400 border-amber-300 text-black font-semibold'
                  : 'bg-black/70 border-gray-600 text-gray-200 hover:bg-black/90'
              }`}
            >
              <span className="shrink-0">▶</span>
              <span className="flex-1 truncate">{videoFiles[videoIndex]?.name ?? ''}</span>
              <span className={`shrink-0 font-mono ${showPlaylist ? 'text-black/70' : 'text-gray-400'}`}>
                {videoIndex + 1}/{videoFiles.length} {showPlaylist ? '▲' : '▼'}
              </span>
            </button>
            {showPlaylist && (
              <ul className="mt-1 max-h-56 overflow-y-auto bg-black/85 border border-gray-600 rounded-md">
                {videoFiles.map((f, i) => (
                  <li key={`${i}-${f.name}`}>
                    <button
                      type="button"
                      onClick={() => { setShowPlaylist(false); void handleSelectSegment(i); }}
                      title={i === videoIndex ? `${f.name} (재선택 시 처음부터)` : f.name}
                      className={`w-full flex items-center gap-1.5 px-2 py-1 text-xs text-left ${
                        i === videoIndex
                          ? 'bg-amber-400/90 text-black font-semibold'
                          : 'text-gray-200 hover:bg-gray-700/80'
                      }`}
                    >
                      <span className="w-4 shrink-0 text-center">{i === videoIndex ? '▶' : i + 1}</span>
                      <span className="flex-1 truncate">{f.name}</span>
                      <span className={`shrink-0 font-mono ${i === videoIndex ? 'text-black/70' : 'text-gray-400'}`}>
                        {fmtDur(videoDurations[i])}
                      </span>
                    </button>
                  </li>
                ))}
              </ul>
            )}
          </div>
        )}

        {/* Empty state placeholder — 가운데 반투명 폴더 선택 */}
        {!source && (
          <div className="absolute inset-0 flex flex-col items-center justify-center text-gray-400 pointer-events-none select-none" style={{ minHeight: '240px' }}>
            <div className="text-5xl mb-3 opacity-60">&#9654;</div>
            <p className="text-lg">동영상 폴더를 드래그하거나 선택하세요</p>
            <p className="text-sm mt-1 mb-6">영상 + 측점/POI 데이터가 함께 로드됩니다</p>
            <label className="pointer-events-auto cursor-pointer bg-emerald-500/20 hover:bg-emerald-500/40 backdrop-blur-sm border border-emerald-300/40 text-white text-base font-medium px-7 py-3 rounded-xl shadow-lg transition-colors">
              폴더 선택
              <input
                type="file"
                className="hidden"
                webkitdirectory=""
                directory=""
                multiple
                onChange={(e) => {
                  const files = e.target.files;
                  if (files?.length) void handleSelectFolder(Array.from(files));
                }}
              />
            </label>
          </div>
        )}

        {/* 측점 오버레이 */}
        <StationOverlay
          currentFrame={frame}
          currentTime={currentTime}
          timeRef={smoothTimeRef}
          fps={effectiveFps}
          visible={showStations}
          videoReady={videoReady}
          videoWidth={videoWidth}
          videoHeight={videoHeight}
          onTogglePlay={handleTogglePlay}
          showPanel={devMode !== 0 && showVideoControls && !!source}
          topPx={paramTop}
          barHeight={barHeight}
        />

        {/* 측점 기반 재생 바 — 영상 하단에 오버레이로 앵커 (폴더 로드 후) */}
        {geoLoaded && (
        <div ref={barWrapRef} className="absolute bottom-0 left-0 right-0 z-20">
        <StationBar
          currentTime={smoothTime}
          timeRef={smoothTimeRef}
          duration={duration}
          playing={playing}
          onTogglePlay={handleTogglePlay}
          onStop={handleStop}
          onCapture={handleCaptureFrame}
          onSeek={handleSeek}
          onSeekSegment={(i, t) => { void handleSeekSegment(i, t); }}
          showStations={showStations}
          onToggleStations={() => setShowStations((v) => !v)}
        />
        </div>
        )}

        {/* abcVideo 전용 유틸 행 (파일/프레임이동/HLS) — UI 숨김(코드 보존, SHOW_TOOLBAR 로 제어) */}
        {SHOW_TOOLBAR && (
        <div className="bg-gray-900 border-t border-gray-800">
          <button
            type="button"
            onClick={() => setShowUtilBar((v) => !v)}
            className="w-full flex items-center gap-1.5 px-2 py-1 text-xs text-gray-400 hover:text-white"
          >
            <span className={`inline-block transition-transform ${showUtilBar ? 'rotate-90' : ''}`}>&#9656;</span>
            도구 {showUtilBar ? '접기' : '펼치기'}
          </button>
          {showUtilBar && (
          <div className="flex items-center gap-2 px-2 pb-2 flex-wrap">
          <label className="cursor-pointer bg-emerald-600 hover:bg-emerald-700 text-white text-sm px-3 py-1.5 rounded">
            폴더 선택
            <input
              type="file"
              className="hidden"
              webkitdirectory=""
              directory=""
              multiple
              onChange={(e) => {
                const files = e.target.files;
                if (files?.length) void handleSelectFolder(Array.from(files));
              }}
            />
          </label>

          {/* 프레임 직접 이동 */}
          <form
            onSubmit={e => {
              e.preventDefault();
              const input = (e.currentTarget.elements.namedItem('frameInput') as HTMLInputElement);
              const frameNum = parseInt(input.value, 10);
              if (!isNaN(frameNum)) {
                playerRef.current?.currentTime(frameNum / effectiveFps);
              }
              input.blur();
            }}
            className="flex items-center gap-1"
          >
            <span className="text-gray-500 text-xs">F</span>
            <input
              name="frameInput"
              type="number"
              min={0}
              step={1}
              placeholder="프레임"
              className="w-20 bg-black/60 border border-gray-600 rounded px-1.5 py-1 text-xs text-yellow-300 font-mono
                         [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
            />
            <button
              type="submit"
              className="text-xs px-2 py-1 rounded border border-gray-600 bg-gray-800 text-gray-300 hover:text-white"
            >
              이동
            </button>
          </form>

          {videoId && (
            <HlsConversionStatus
              videoId={videoId}
              onConversionDone={() => switchToHls(videoId)}
            />
          )}

          <span className="text-gray-500 text-xs ml-auto hidden sm:inline">
            Space 재생 | ←/→ 5초 | J/L 10초 | ,/. 프레임 | Shift+S 캡처
          </span>
          </div>
          )}
        </div>
        )}
      </div>
    );
  }
);

export default VideoPlayer;
