/**
 * 클라이언트 폴더 기반 지리정보 로딩 + CSV 파싱
 *
 * `<input type="file" webkitdirectory>` 로 선택된 File[] 에서
 * 영상 / 드론 CSV / center.csv / building POI·측점 CSV 를 찾아 파싱한다.
 *
 * 인코딩(서버 geoMatch.ts readCsvUtf8 대응):
 *   - 드론 <base>.csv : UTF-8 (+BOM)
 *   - POI / 측점 / center.csv : EUC-KR → TextDecoder('euc-kr')
 *
 * center.csv 는 헤더가 EUC-KR 로 깨지므로 위치 인덱스(1=lat, 2=lon, 5=z)로 파싱한다.
 * 영상명 비의존 — '회덕' 하드코딩 없음. base 는 영상 파일명에서 유도한다.
 */

import type {
  DroneFrame,
  GeoPoint,
  CenterlinePoint,
  GeoOrigin,
  FolderGeoData,
  RouteMeta,
} from '../types/geo';
import { stationOrder, getWorldOrigin } from './geoSearch';

// ── CSV 파싱 헬퍼 (geoMatch.ts:162-173 포팅) ──────────────────────────

export function parseCsvLine(line: string): string[] {
  const result: string[] = [];
  let current = '';
  let inQuotes = false;
  for (const ch of line) {
    if (ch === '"') {
      inQuotes = !inQuotes;
    } else if (ch === ',' && !inQuotes) {
      result.push(current.trim());
      current = '';
    } else {
      current += ch;
    }
  }
  result.push(current.trim());
  return result;
}

/** ArrayBuffer → 문자열. UTF-8 BOM 제거, EUC-KR 는 TextDecoder('euc-kr'). */
export function decodeBytes(buf: ArrayBuffer, encoding: 'utf-8' | 'euc-kr'): string {
  const text = new TextDecoder(encoding).decode(buf);
  // UTF-8/EUC-KR 디코딩 후 남을 수 있는 BOM(U+FEFF) 제거
  return text.replace(/^﻿/, '');
}

/** File → 파싱된 행 배열. */
export async function readCsv(
  file: File,
  encoding: 'utf-8' | 'euc-kr',
): Promise<string[][]> {
  const buf = await file.arrayBuffer();
  const text = decodeBytes(buf, encoding);
  return text
    .split(/\r?\n/)
    .filter(Boolean)
    .map(parseCsvLine);
}

// ── 파일 식별 헬퍼 ────────────────────────────────────────────────────

/** File 의 폴더 내 상대경로 (webkitRelativePath 우선, 없으면 name). */
function relPath(f: File): string {
  return f.webkitRelativePath || f.name;
}

/** 경로의 마지막 세그먼트(파일명). */
function baseNameOf(p: string): string {
  const parts = p.split('/');
  return parts[parts.length - 1];
}

/** building/ 하위 파일 여부 (마지막 디렉토리 세그먼트가 building). */
function isInBuilding(f: File): boolean {
  const parts = relPath(f).split('/');
  return parts.length >= 2 && parts[parts.length - 2].toLowerCase() === 'building';
}

const VIDEO_EXT = /\.(mp4|webm)$/i;

/** 영상 파일 찾기 (mp4/webm, building 제외). */
export function findVideoFile(files: File[]): File | null {
  return (
    files.find((f) => !isInBuilding(f) && VIDEO_EXT.test(baseNameOf(relPath(f)))) ?? null
  );
}

/** 영상 파일명에서 base(확장자 제거) 추출. */
export function deriveBaseName(videoFile: File | null): string | null {
  if (!videoFile) return null;
  return baseNameOf(relPath(videoFile)).replace(VIDEO_EXT, '');
}

// ── 파서 (geoMatch.ts loader 포팅) ────────────────────────────────────

/**
 * 드론 프레임 CSV 파싱 (UTF-8 BOM, 헤더 이름 인덱스). geoMatch.ts:289-302
 * 루트(building 제외)의 base.csv 를 식별한다. base 가 없으면 루트 .csv 중
 * POI/측점/center 가 아닌 첫 파일을 사용한다(영상명 비의존 폴백).
 */
export async function parseDroneFrames(
  files: File[],
  baseName: string | null,
): Promise<DroneFrame[]> {
  const rootCsv = files.filter((f) => {
    if (isInBuilding(f)) return false;
    const name = baseNameOf(relPath(f));
    if (!/\.csv$/i.test(name)) return false;
    if (/POI|측점/i.test(name)) return false;
    if (name.toLowerCase() === 'center.csv') return false;
    return true;
  });
  if (!rootCsv.length) return [];

  // base 일치 우선, 없으면 첫 루트 csv
  const droneFile =
    (baseName && rootCsv.find((f) => baseNameOf(relPath(f)) === `${baseName}.csv`)) ||
    rootCsv[0];

  const rows = await readCsv(droneFile, 'utf-8');
  if (rows.length < 2) return [];

  const header = rows[0].map((h) => h.trim().replace(/^﻿/, ''));
  const fi = (name: string) => header.indexOf(name);

  return rows
    .slice(1)
    .map((r) => ({
      frame: parseInt(r[fi('frame_cnt')], 10),
      lat: parseFloat(r[fi('latitude')]),
      lon: parseFloat(r[fi('longitude')]),
      altitude: parseFloat(r[fi('altitude')]),
      yaw: parseFloat(r[fi('yaw')]),
      pitch: parseFloat(r[fi('pitch')]),
      roll: parseFloat(r[fi('roll')]),
      focalLen: parseFloat(r[fi('focal_len')]),
    }))
    .filter((f) => !isNaN(f.lat));
}

/**
 * POI + 측점 CSV 파싱 (building/, EUC-KR, 헤더 이름 인덱스). geoMatch.ts:321-357
 * POI: category = category_clean || '건물', type='poi'
 * 측점: category = '측점'(강제), type='station'
 * 타원체고 버전 우선 규칙 유지.
 */
export async function parsePois(files: File[]): Promise<GeoPoint[]> {
  const buildingFiles = files.filter((f) => isInBuilding(f));
  const result: GeoPoint[] = [];

  const nameOf = (f: File) => baseNameOf(relPath(f));

  // POI 위경도 파일 (_타원체고 버전 우선)
  const allPoiFiles = buildingFiles.filter(
    (f) => nameOf(f).includes('POI') && nameOf(f).includes('위경도'),
  );
  const poiFiles =
    allPoiFiles.filter((f) => nameOf(f).includes('타원체고')).length > 0
      ? allPoiFiles.filter((f) => nameOf(f).includes('타원체고'))
      : allPoiFiles.filter((f) => !nameOf(f).includes('타원체고'));

  for (const f of poiFiles) {
    const rows = await readCsv(f, 'euc-kr');
    if (rows.length < 2) continue;
    const header = rows[0].map((h) => h.trim().replace(/^﻿/, ''));
    const fi = (name: string) => header.indexOf(name);
    for (const r of rows.slice(1)) {
      const lat = parseFloat(r[fi('lat')]);
      const lon = parseFloat(r[fi('lon')]);
      if (isNaN(lat) || isNaN(lon)) continue;
      result.push({
        title: r[fi('title')] || '',
        category: r[fi('category_clean')] || '건물',
        lat,
        lon,
        z: parseFloat(r[fi('z')]) || 0,
        type: 'poi',
      });
    }
  }

  // 측점 위경도 파일
  const stationFiles = buildingFiles.filter(
    (f) => nameOf(f).includes('측점') && nameOf(f).includes('위경도'),
  );
  for (const f of stationFiles) {
    const rows = await readCsv(f, 'euc-kr');
    if (rows.length < 2) continue;
    const header = rows[0].map((h) => h.trim().replace(/^﻿/, ''));
    const fi = (name: string) => header.indexOf(name);
    for (const r of rows.slice(1)) {
      const lat = parseFloat(r[fi('lat')]);
      const lon = parseFloat(r[fi('lon')]);
      if (isNaN(lat) || isNaN(lon)) continue;
      result.push({
        title: r[fi('title')] || '',
        category: '측점',
        lat,
        lon,
        z: parseFloat(r[fi('z')]) || 0,
        type: 'station',
      });
    }
  }

  return result;
}

/**
 * center.csv 파싱 (EUC-KR, 위치 인덱스 1=lat, 2=lon, 5=z). geoMatch.ts:253-257
 */
export async function parseCenterline(files: File[]): Promise<CenterlinePoint[]> {
  const file = files.find(
    (f) => !isInBuilding(f) && baseNameOf(relPath(f)).toLowerCase() === 'center.csv',
  );
  if (!file) return [];

  const rows = await readCsv(file, 'euc-kr');
  if (rows.length < 2) return [];

  return rows
    .slice(1)
    .map((r) => ({
      lat: parseFloat(r[1]),
      lon: parseFloat(r[2]),
      z: parseFloat(r[5]),
    }))
    .filter((p) => !isNaN(p.lat) && !isNaN(p.lon) && !isNaN(p.z));
}

/**
 * 폴더 보조 파일 route.json 파싱 (UTF-8, JSON).
 * `<base>.route.json` 우선(case-insensitive), 없으면 `route.json`.
 * building/ 제외, 루트 파일만. 파싱 실패 시 null.
 */
export async function parseRouteMeta(
  files: File[],
  baseName: string | null,
): Promise<RouteMeta | null> {
  const rootJson = files.filter(
    (f) => !isInBuilding(f) && /\.json$/i.test(baseNameOf(relPath(f))),
  );
  if (!rootJson.length) return null;

  const wantBase = baseName ? `${baseName}.route.json`.toLowerCase() : null;
  const file =
    (wantBase &&
      rootJson.find((f) => baseNameOf(relPath(f)).toLowerCase() === wantBase)) ||
    rootJson.find((f) => baseNameOf(relPath(f)).toLowerCase() === 'route.json');
  if (!file) return null;

  try {
    const text = await file.text();
    return JSON.parse(text) as RouteMeta;
  } catch {
    return null;
  }
}

// ── 통합 로더 ─────────────────────────────────────────────────────────

/**
 * 폴더 내 파일에서 영상 + 지리정보를 모두 파싱한다.
 * 반환값을 geoStore.loadFromFolder 가 스토어에 적재한다.
 */
export async function loadFolderGeoData(
  input: FileList | File[],
): Promise<FolderGeoData> {
  const files = Array.from(input);

  const videoFile = findVideoFile(files);
  const baseName = deriveBaseName(videoFile);

  const [frames, allPoints, centerline, routeMeta] = await Promise.all([
    parseDroneFrames(files, baseName),
    parsePois(files),
    parseCenterline(files),
    parseRouteMeta(files, baseName),
  ]);

  const pois = allPoints.filter((p) => p.type === 'poi');
  const stations = allPoints
    .filter((p) => p.type === 'station')
    .sort((a, b) => stationOrder(a.title) - stationOrder(b.title));

  let origin: GeoOrigin | null = null;
  if (stations.length || frames.length) {
    origin = getWorldOrigin(frames, allPoints);
  } else if (centerline.length) {
    origin = { lat: centerline[0].lat, lon: centerline[0].lon, alt: centerline[0].z };
  }

  return { videoFile, baseName, frames, pois, stations, centerline, origin, routeMeta };
}
