/**
 * 클라이언트 지리정보 스토어 (Zustand)
 *
 * 폴더 선택으로 파싱한 드론 프레임 / POI / 측점 / 중심선 / ENU 원점을 보관한다.
 * 4개 소비 컴포넌트(GeoSearch / StationVerify / StationOverlay / RoutePanel)가
 * 이 스토어를 단일 소스로 구독한다(서버 /api/geo/* 대체).
 *
 * playerStore.ts 패턴을 따른다.
 *
 * POI 위치 보정(poiOverrides): 마우스 드래그로 만든 title→{lat,lon,z} 맵.
 *   basePois(파싱 원본)에 applyPoiOverrides 를 적용한 결과가 pois 다.
 *   내보내기/가져오기 파일(`<base>_poi_overrides.json`)로 영속화한다.
 */

import { create } from 'zustand';
import type {
  DroneFrame,
  GeoPoint,
  CenterlinePoint,
  GeoOrigin,
  RouteMeta,
  RouteStructure,
  DirectionChange,
  PoiOverride,
  PoiOverrideMap,
  DetectedCameraInfo,
} from '../types/geo';
import { loadFolderGeoData, applyPoiOverrides, parseDroneFrames } from '../utils/geoData';
import { refineFramesWithDjmd } from '../utils/djmdTrack';

interface GeoStore {
  loaded: boolean;
  frames: DroneFrame[];
  pois: GeoPoint[]; // type==='poi' (poiOverrides 적용 결과)
  basePois: GeoPoint[]; // 파싱 원본 (보정 전) — 보정 재적용용
  poiOverrides: PoiOverrideMap; // title → {lat,lon,z}
  stations: GeoPoint[]; // type==='station' (stationOrder 정렬)
  centerline: CenterlinePoint[];
  origin: GeoOrigin | null;
  baseName: string | null;
  routeMeta: RouteMeta | null;
  /** KMZ 원본 구조물(교량/터널/구교/역사) + route.json 보정. */
  structures: RouteStructure[];
  /** 측점 비고에서 추출한 방향전환점 목록. */
  directionChanges: DirectionChange[];
  /** 마지막 폴더 로드에서 KMZ(POI·구조물 원본)가 누락됐는지. true면 재구축 필요. */
  kmzMissing: boolean;
  /** 폴더 내 영상 파일 전체(이름 오름차순) — 분할본 연속재생용. */
  videoFiles: File[];
  /** videoFiles 각 영상 길이(초). 측정 실패 시 null. */
  videoDurations: (number | null)[];
  /** 현재 재생 중인 세그먼트 인덱스 (videoFiles 기준). */
  videoIndex: number;
  /** 폴더 원본 파일 전체 — 세그먼트 전환 시 드론 CSV 재파싱용. */
  folderFiles: File[];
  /** 영상 djmd 트랙에서 감지한 카메라 정보 (예: ZenmuseP1, 초점거리). */
  cameraInfo: DetectedCameraInfo | null;
  /** 폴더의 `<영상 base>.camera.json` 카메라 파라미터 — 감지값/저장값보다 최우선 적용. */
  cameraJson: Record<string, number> | null;
  /** 폴더의 `<영상 base>.display.json` 화면표시 옵션 — 로드 시 자동 적용. */
  displayJson: Record<string, unknown> | null;
  /** 드론 고도 datum 보정(m) — 타원체고 데이터셋의 정표고→타원체고 변환량. 세그먼트 전환에도 적용. */
  altitudeOffsetM: number;
  /** frames 가 djmd(영상 내장 텔레메트리) 프레임 동기 위치로 정밀화됐는지.
   *  true 면 입력이 매끄러워 오버레이 포즈 평활(smoothHalf)을 최소로 제한한다. */
  frameSynced: boolean;
  /** 전체 비행(모든 세그먼트) 프레임 — 측점 '전 구간' 검색용. */
  fullFrames: DroneFrame[];
  /** djmd 교차상관 시간 오프셋(초) — 영상시간 = CSV시간 − 이 값. 검색 시각 변환용. */
  djmdDtSec: number;

  /**
   * 폴더 선택 파일에서 지리정보를 파싱해 스토어에 적재한다.
   * 발견한 영상 File 을 반환(없으면 null) — 호출자가 loadLocalFile 로 재생.
   */
  loadFromFolder: (files: FileList | File[]) => Promise<File | null>;
  /**
   * 분할 영상 세그먼트 전환 — 드론 로그(시간 기반)를 해당 영상 구간(누적 오프셋)으로
   * 재정렬해 frames 를 교체한다. 재생할 영상 File 을 반환(범위 밖이면 null).
   * baseName 은 폴더(첫 영상) 기준을 유지한다 — 카메라 보정 localStorage 연속성.
   */
  loadSegment: (index: number) => Promise<File | null>;
  /** 단일 POI 보정 설정/갱신 (드래그 종료 시). title 키. */
  setPoiOverride: (title: string, ov: PoiOverride) => void;
  /** 단일 POI 보정 해제. */
  clearPoiOverride: (title: string) => void;
  /** 보정맵 전체 교체 (가져오기). */
  setPoiOverrides: (map: PoiOverrideMap) => void;
  clear: () => void;
}

const EMPTY = {
  loaded: false,
  frames: [] as DroneFrame[],
  /** 전체 비행(모든 세그먼트) 프레임 — 측점 '전 구간' 검색용. 표시용 frames 와 별개. */
  fullFrames: [] as DroneFrame[],
  djmdDtSec: 0,
  frameSynced: false,
  pois: [] as GeoPoint[],
  basePois: [] as GeoPoint[],
  poiOverrides: {} as PoiOverrideMap,
  stations: [] as GeoPoint[],
  centerline: [] as CenterlinePoint[],
  origin: null as GeoOrigin | null,
  baseName: null as string | null,
  routeMeta: null as RouteMeta | null,
  structures: [] as RouteStructure[],
  directionChanges: [] as DirectionChange[],
  kmzMissing: false,
  videoFiles: [] as File[],
  videoDurations: [] as (number | null)[],
  videoIndex: 0,
  folderFiles: [] as File[],
  cameraInfo: null as DetectedCameraInfo | null,
  cameraJson: null as Record<string, number> | null,
  displayJson: null as Record<string, unknown> | null,
  altitudeOffsetM: 0,
};

/**
 * djmd(영상 내장 텔레메트리) 정밀 위치 보정 — 백그라운드 적용.
 * CSV 기반 프레임으로 즉시 표시한 뒤, 영상 파일에서 프레임 동기 위치/고도를 읽어 교체한다.
 * (오르막/내리막·가감속에서 시간동기 오차를 원천 제거 — 자세는 CSV 유지)
 */
async function refineWithDjmd(
  videoFile: File,
  forIndex: number,
  get: () => GeoStore,
  set: (partial: Partial<GeoStore>) => void,
): Promise<void> {
  try {
    const s0 = get();
    const refined = await refineFramesWithDjmd(videoFile, s0.frames, s0.altitudeOffsetM > 0);
    if (!refined) return;
    const s = get();
    // 파싱 중 세그먼트/폴더가 바뀌었으면 폐기
    if (s.videoIndex !== forIndex || s.videoFiles[forIndex] !== videoFile || s.frames !== s0.frames) return;
    set({ frames: refined.frames, frameSynced: true, djmdDtSec: refined.dtSec });
    console.log(`[djmd] 영상 내장 텔레메트리로 위치/고도 정밀화 적용 (${refined.frames.length}행, 프레임 동기)`);
  } catch (e) {
    console.warn('[djmd] 정밀 위치 적용 실패(CSV 기반 유지):', e);
  }
}

export const useGeoStore = create<GeoStore>((set, get) => ({
  ...EMPTY,

  loadFromFolder: async (files) => {
    const data = await loadFolderGeoData(files);
    set({
      loaded: true,
      frames: data.frames,
      fullFrames: data.fullFrames,
      frameSynced: false,
      basePois: data.pois,
      poiOverrides: data.poiOverrides,
      pois: applyPoiOverrides(data.pois, data.poiOverrides),
      stations: data.stations,
      centerline: data.centerline,
      origin: data.origin,
      baseName: data.baseName,
      routeMeta: data.routeMeta,
      structures: data.structures,
      directionChanges: data.directionChanges,
      kmzMissing: data.kmzMissing,
      videoFiles: data.videoFiles,
      videoDurations: data.videoDurations,
      videoIndex: 0,
      folderFiles: data.folderFiles,
      cameraInfo: data.cameraInfo,
      cameraJson: data.cameraJson,
      displayJson: data.displayJson,
      altitudeOffsetM: data.altitudeOffsetM,
    });
    // 백그라운드 djmd 정밀 위치 보정 (완료 시 frames 교체 → 라벨 캐시 자동 재계산)
    if (data.videoFile) void refineWithDjmd(data.videoFile, 0, get, set);
    return data.videoFile;
  },

  loadSegment: async (index) => {
    const s = get();
    const file = s.videoFiles[index];
    if (!file) return null;

    // 누적 오프셋 = 앞 세그먼트 길이 합. 하나라도 측정 실패면 정렬 불가 → 궤적만 비운다(재생은 계속).
    let offsetSec = 0;
    let offsetKnown = true;
    for (let i = 0; i < index; i++) {
      const d = s.videoDurations[i];
      if (d == null) { offsetKnown = false; break; }
      offsetSec += d;
    }

    if (!offsetKnown) {
      console.warn(`[geo] 세그먼트 ${index + 1} 오프셋 계산 불가(영상 길이 측정 실패) — 드론 궤적 미표시`);
      set({ frames: [], videoIndex: index, frameSynced: false });
      return file;
    }

    const frames = await parseDroneFrames(s.folderFiles, s.baseName, {
      offsetSec,
      durationSec: s.videoDurations[index],
    });
    // datum 보정(타원체고 데이터셋) 유지 — 폴더 로드 때 산출한 오프셋을 재파싱 프레임에도 적용.
    if (s.altitudeOffsetM) {
      for (const f of frames) f.altitude += s.altitudeOffsetM;
    }
    console.log(
      `[geo] 세그먼트 ${index + 1}/${s.videoFiles.length} 전환 — 로그 ${offsetSec.toFixed(1)}s~ 정렬, ${frames.length}행`,
    );
    set({ frames, videoIndex: index, frameSynced: false });
    // 백그라운드 djmd 정밀 위치 보정 — 세그먼트 영상의 자체 텔레메트리 사용(프레임 동기)
    void refineWithDjmd(file, index, get, set);
    return file;
  },

  setPoiOverride: (title, ov) =>
    set((s) => {
      const poiOverrides = { ...s.poiOverrides, [title]: ov };
      return { poiOverrides, pois: applyPoiOverrides(s.basePois, poiOverrides) };
    }),

  clearPoiOverride: (title) =>
    set((s) => {
      const poiOverrides = { ...s.poiOverrides };
      delete poiOverrides[title];
      return { poiOverrides, pois: applyPoiOverrides(s.basePois, poiOverrides) };
    }),

  setPoiOverrides: (map) =>
    set((s) => ({ poiOverrides: map, pois: applyPoiOverrides(s.basePois, map) })),

  clear: () => set({ ...EMPTY }),
}));
