/**
 * 해양환경관리법 제22조 기반 오염물 배출 구역 데이터 및 유틸리티
 *
 * 법률 근거:
 * https://lbox.kr/v2/statute/%ED%95%B4%EC%96%91%ED%99%98%EA%B2%BD%EA%B4%80%EB%A6%AC%EB%B2%95/%EB%B3%B8%EB%AC%B8%20%3E%20%EC%A0%9C3%EC%9E%A5%20%3E%20%EC%A0%9C1%EC%A0%88%20%3E%20%EC%A0%9C22%EC%A1%B0
 * 선박에서의 오염방지에 관한 규칙 제8조[별표 2] 및 제14조
 *
 * 구역 경계선: 국립해양조사원 영해기선(TB_ZN_TRTSEA) 버퍼 GeoJSON
 * 영해기선 데이터: 국립해양조사원 TB_ZN_TRTSEA (EPSG:5179 → WGS84 변환)
 */

// ── GeoJSON 타입 ──

interface GeoJSONFeature {
  geometry: {
    type: string;
    coordinates: number[][][][] | number[][][];
  };
}

// ── 영해기선 폴리곤 (거리 계산용) ──

let cachedBaselineRings: [number, number][][] | null = null;
let baselineLoadingPromise: Promise<[number, number][][]> | null = null;

function extractOuterRings(geojson: { features: GeoJSONFeature[] }): [number, number][][] {
  const rings: [number, number][][] = [];
  for (const feature of geojson.features) {
    const geom = feature.geometry;
    if (geom.type === 'MultiPolygon') {
      const polygons = geom.coordinates as [number, number][][][];
      for (const polygon of polygons) {
        rings.push(polygon[0]);
      }
    } else if (geom.type === 'Polygon') {
      const polygon = geom.coordinates as [number, number][][];
      rings.push(polygon[0]);
    }
  }
  return rings;
}

export async function loadTerritorialBaseline(): Promise<[number, number][][]> {
  if (cachedBaselineRings) return cachedBaselineRings;
  if (baselineLoadingPromise) return baselineLoadingPromise;

  baselineLoadingPromise = fetch('/data/대한민국.geojson')
    .then((res) => res.json())
    .then((data: { features: GeoJSONFeature[] }) => {
      cachedBaselineRings = extractOuterRings(data);
      return cachedBaselineRings;
    });

  return baselineLoadingPromise;
}

export function getCachedBaseline(): [number, number][][] | null {
  return cachedBaselineRings;
}

// ── 구역 경계선 GeoJSON (런타임 로드) ──

interface ZoneGeoJSON {
  nm: number;
  rings: [number, number][][];
}

let cachedZones: ZoneGeoJSON[] | null = null;
let zoneLoadingPromise: Promise<ZoneGeoJSON[]> | null = null;

const ZONE_FILES = [
  { nm: 3, file: '/data/대한민국_3해리.geojson' },
  { nm: 12, file: '/data/대한민국_12해리.geojson' },
  { nm: 25, file: '/data/대한민국_25해리.geojson' },
  { nm: 50, file: '/data/대한민국_50해리.geojson' },
];

export async function loadZoneGeoJSON(): Promise<ZoneGeoJSON[]> {
  if (cachedZones) return cachedZones;
  if (zoneLoadingPromise) return zoneLoadingPromise;

  zoneLoadingPromise = Promise.all(
    ZONE_FILES.map(async ({ nm, file }) => {
      const res = await fetch(file);
      const geojson = await res.json();
      return { nm, rings: extractOuterRings(geojson) };
    }),
  ).then((zones) => {
    cachedZones = zones;
    return zones;
  });

  return zoneLoadingPromise;
}

export function getCachedZones(): ZoneGeoJSON[] | null {
  return cachedZones;
}

// ── 거리 계산 ──

/** 두 좌표 간 해리(NM) 계산 (Haversine) */
function haversineNm(lat1: number, lon1: number, lat2: number, lon2: number): number {
  const R = 3440.065;
  const dLat = ((lat2 - lat1) * Math.PI) / 180;
  const dLon = ((lon2 - lon1) * Math.PI) / 180;
  const a =
    Math.sin(dLat / 2) ** 2 +
    Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLon / 2) ** 2;
  return 2 * R * Math.asin(Math.sqrt(a));
}

/** 점 P에서 선분 AB까지의 최단거리 (NM) — 위도 보정 평면 투영 */
function pointToSegmentNm(
  pLat: number,
  pLon: number,
  aLon: number,
  aLat: number,
  bLon: number,
  bLat: number,
): number {
  const cosLat = Math.cos((pLat * Math.PI) / 180);
  const ax = (aLon - pLon) * cosLat;
  const ay = aLat - pLat;
  const bx = (bLon - pLon) * cosLat;
  const by = bLat - pLat;
  const dx = bx - ax;
  const dy = by - ay;
  const lenSq = dx * dx + dy * dy;

  let closeLon: number;
  let closeLat: number;

  if (lenSq < 1e-20) {
    closeLon = aLon;
    closeLat = aLat;
  } else {
    const t = Math.max(0, Math.min(1, (-ax * dx + -ay * dy) / lenSq));
    closeLon = aLon + (bLon - aLon) * t;
    closeLat = aLat + (bLat - aLat) * t;
  }

  return haversineNm(pLat, pLon, closeLat, closeLon);
}

/** 클릭 지점에서 영해기선 폴리곤까지의 최단거리 (NM) — 폴리곤 내부이면 0 */
export function estimateDistanceFromCoast(lat: number, lon: number): number {
  if (!cachedBaselineRings) return 0;

  // 영해기선 폴리곤 내부이면 거리 0
  if (cachedBaselineRings.some((ring) => pointInRing(lon, lat, ring))) return 0;

  let minDist = Infinity;
  for (const ring of cachedBaselineRings) {
    for (let i = 0; i < ring.length - 1; i++) {
      const dist = pointToSegmentNm(
        lat,
        lon,
        ring[i][0],
        ring[i][1],
        ring[i + 1][0],
        ring[i + 1][1],
      );
      if (dist < minDist) minDist = dist;
    }
  }
  return minDist;
}

// ── 구역 판별 (Point-in-Polygon) ──

/** Ray casting 알고리즘으로 점이 폴리곤 내부인지 판별 */
function pointInRing(lon: number, lat: number, ring: [number, number][]): boolean {
  let inside = false;
  for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
    const xi = ring[i][0];
    const yi = ring[i][1];
    const xj = ring[j][0];
    const yj = ring[j][1];

    if (yi > lat !== yj > lat && lon < ((xj - xi) * (lat - yi)) / (yj - yi) + xi) {
      inside = !inside;
    }
  }
  return inside;
}

/** 점이 MultiPolygon의 어느 폴리곤에든 포함되는지 */
function pointInZone(lon: number, lat: number, rings: [number, number][][]): boolean {
  return rings.some((ring) => pointInRing(lon, lat, ring));
}

/**
 * 클릭 위치가 어느 구역에 포함되는지 판별
 * @returns 0=~3해리, 1=3~12해리, 2=12~25해리, 3=25~50해리, 4=50해리+
 */
export function determineZone(lat: number, lon: number): number {
  if (!cachedZones) return 4;

  // 작은 구역부터 검사 (3 → 12 → 25 → 50)
  const sortedZones = [...cachedZones].sort((a, b) => a.nm - b.nm);

  for (let i = 0; i < sortedZones.length; i++) {
    if (pointInZone(lon, lat, sortedZones[i].rings)) {
      return i; // 0=3nm 내, 1=12nm 내, 2=25nm 내, 3=50nm 내
    }
  }
  return 4; // 50해리+
}

// ── 구역 경계선 생성 ──

export interface ZoneLine {
  path: [number, number][]; // [lon, lat]
  color: [number, number, number, number];
  label: string;
  distanceNm: number;
}

const ZONE_STYLES: { nm: number; color: [number, number, number, number]; label: string }[] = [
  { nm: 3, color: [239, 68, 68, 180], label: '3해리' },
  { nm: 12, color: [249, 115, 22, 160], label: '12해리' },
  { nm: 25, color: [234, 179, 8, 140], label: '25해리' },
  { nm: 50, color: [100, 116, 139, 120], label: '50해리' },
];

export function getDischargeZoneLines(): ZoneLine[] {
  if (!cachedZones) return [];

  const lines: ZoneLine[] = [];
  for (const zone of cachedZones) {
    const style = ZONE_STYLES.find((s) => s.nm === zone.nm);
    if (!style) continue;

    for (let i = 0; i < zone.rings.length; i++) {
      lines.push({
        path: zone.rings[i],
        color: style.color,
        label: zone.rings.length > 1 ? `${style.label} (${i + 1})` : style.label,
        distanceNm: style.nm,
      });
    }
  }
  return lines;
}
