import type { GeoJSON } from 'geojson';
import type { GroupPolygonDto } from '../../services/vesselAnalysis';
import type { HistoryFrame } from './fleetClusterTypes';
import { GEAR_BUFFER_DEG, CIRCLE_SEGMENTS } from './fleetClusterTypes';

/** 트랙 포인트에서 주어진 시각의 보간 위치 반환 */
export function interpolateTrackPosition(
  track: { ts: number; lat: number; lon: number; cog: number }[],
  timeMs: number,
): { lat: number; lon: number; cog: number } | null {
  if (track.length === 0) return null;
  if (track.length === 1) return { lat: track[0].lat, lon: track[0].lon, cog: track[0].cog };
  if (timeMs <= track[0].ts) return { lat: track[0].lat, lon: track[0].lon, cog: track[0].cog };
  if (timeMs >= track[track.length - 1].ts) {
    const last = track[track.length - 1];
    return { lat: last.lat, lon: last.lon, cog: last.cog };
  }
  // Binary search for surrounding points
  let lo = 0, hi = track.length - 1;
  while (lo < hi - 1) {
    const mid = (lo + hi) >> 1;
    if (track[mid].ts <= timeMs) lo = mid; else hi = mid;
  }
  const a = track[lo], b = track[hi];
  const t = (timeMs - a.ts) / (b.ts - a.ts);
  return {
    lat: a.lat + t * (b.lat - a.lat),
    lon: a.lon + t * (b.lon - a.lon),
    cog: b.cog,
  };
}

/**
 * Convex hull + buffer 기반 폴리곤 생성 (Python polygon_builder.py 동일 로직)
 * - 1점: 원형 버퍼 (GEAR_BUFFER_DEG=0.01 ~ 1.1km)
 * - 2점: 두 점 잇는 직선 양쪽 버퍼
 * - 3점+: convex hull + 버퍼
 */
export function buildInterpPolygon(points: [number, number][]): GeoJSON.Polygon | null {
  if (points.length === 0) return null;

  if (points.length === 1) {
    const [cx, cy] = points[0];
    const ring: [number, number][] = [];
    for (let i = 0; i <= CIRCLE_SEGMENTS; i++) {
      const angle = (2 * Math.PI * i) / CIRCLE_SEGMENTS;
      ring.push([cx + GEAR_BUFFER_DEG * Math.cos(angle), cy + GEAR_BUFFER_DEG * Math.sin(angle)]);
    }
    return { type: 'Polygon', coordinates: [ring] };
  }

  if (points.length === 2) {
    const [p1, p2] = points;
    const dx = p2[0] - p1[0];
    const dy = p2[1] - p1[1];
    const len = Math.sqrt(dx * dx + dy * dy) || 1e-10;
    const nx = (-dy / len) * GEAR_BUFFER_DEG;
    const ny = (dx / len) * GEAR_BUFFER_DEG;
    const ring: [number, number][] = [];
    const half = CIRCLE_SEGMENTS / 2;
    ring.push([p1[0] + nx, p1[1] + ny]);
    ring.push([p2[0] + nx, p2[1] + ny]);
    const a2 = Math.atan2(ny, nx);
    for (let i = 0; i <= half; i++) {
      const angle = a2 - Math.PI * i / half;
      ring.push([p2[0] + GEAR_BUFFER_DEG * Math.cos(angle), p2[1] + GEAR_BUFFER_DEG * Math.sin(angle)]);
    }
    ring.push([p1[0] - nx, p1[1] - ny]);
    const a1 = Math.atan2(-ny, -nx);
    for (let i = 0; i <= half; i++) {
      const angle = a1 - Math.PI * i / half;
      ring.push([p1[0] + GEAR_BUFFER_DEG * Math.cos(angle), p1[1] + GEAR_BUFFER_DEG * Math.sin(angle)]);
    }
    ring.push(ring[0]);
    return { type: 'Polygon', coordinates: [ring] };
  }

  const hull = convexHull(points);
  return bufferPolygon(hull, GEAR_BUFFER_DEG);
}

/** 단순 convex hull (Graham scan) */
export function convexHull(points: [number, number][]): [number, number][] {
  const pts = [...points].sort((a, b) => a[0] - b[0] || a[1] - b[1]);
  if (pts.length <= 2) return pts;

  const cross = (o: [number, number], a: [number, number], b: [number, number]) =>
    (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);

  const lower: [number, number][] = [];
  for (const p of pts) {
    while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) lower.pop();
    lower.push(p);
  }
  const upper: [number, number][] = [];
  for (let i = pts.length - 1; i >= 0; i--) {
    while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], pts[i]) <= 0) upper.pop();
    upper.push(pts[i]);
  }
  lower.pop();
  upper.pop();
  return lower.concat(upper);
}

/** 폴리곤 외곽에 buffer 적용 (각 변의 오프셋 + 꼭짓점 라운드) */
export function bufferPolygon(hull: [number, number][], buf: number): GeoJSON.Polygon {
  const ring: [number, number][] = [];
  const n = hull.length;
  for (let i = 0; i < n; i++) {
    const p = hull[i];
    const prev = hull[(i - 1 + n) % n];
    const next = hull[(i + 1) % n];
    const a1 = Math.atan2(p[1] - prev[1], p[0] - prev[0]) - Math.PI / 2;
    const a2 = Math.atan2(next[1] - p[1], next[0] - p[0]) - Math.PI / 2;
    const startA = a1;
    let endA = a2;
    if (endA < startA) endA += 2 * Math.PI;
    const steps = Math.max(2, Math.round((endA - startA) / (Math.PI / 8)));
    for (let s = 0; s <= steps; s++) {
      const a = startA + (endA - startA) * s / steps;
      ring.push([p[0] + buf * Math.cos(a), p[1] + buf * Math.sin(a)]);
    }
  }
  ring.push(ring[0]);
  return { type: 'Polygon', coordinates: [ring] };
}

/**
 * 히스토리 데이터의 gap 구간에 보간 프레임을 삽입하여 완성 데이터셋 반환.
 * - gap <= 30분: 5분 간격 직선 보간 (이전 폴리곤 유지, 중심만 직선 이동)
 * - gap > 30분: 30분 간격으로 멤버 위치 보간 + 가상 폴리곤 생성
 */
export function fillGapFrames(snapshots: GroupPolygonDto[]): HistoryFrame[] {
  if (snapshots.length < 2) return snapshots;
  const STEP_SHORT_MS = 300_000;
  const STEP_LONG_MS = 1_800_000;
  const THRESHOLD_MS = 1_800_000;
  const result: GroupPolygonDto[] = [];

  for (let i = 0; i < snapshots.length; i++) {
    result.push(snapshots[i]);
    if (i >= snapshots.length - 1) continue;

    const prev = snapshots[i];
    const next = snapshots[i + 1];
    const t0 = new Date(prev.snapshotTime).getTime();
    const t1 = new Date(next.snapshotTime).getTime();
    const gap = t1 - t0;
    if (gap <= STEP_SHORT_MS) continue;

    const nextMap = new Map(next.members.map(m => [m.mmsi, m]));
    const common = prev.members.filter(m => nextMap.has(m.mmsi));
    if (common.length === 0) continue;

    if (gap <= THRESHOLD_MS) {
      for (let t = t0 + STEP_SHORT_MS; t < t1; t += STEP_SHORT_MS) {
        const ratio = (t - t0) / gap;
        const cLon = prev.centerLon + (next.centerLon - prev.centerLon) * ratio;
        const cLat = prev.centerLat + (next.centerLat - prev.centerLat) * ratio;
        result.push({
          ...prev,
          snapshotTime: new Date(t).toISOString(),
          centerLon: cLon,
          centerLat: cLat,
          _interp: true,
        });
      }
    } else {
      for (let t = t0 + STEP_LONG_MS; t < t1; t += STEP_LONG_MS) {
        const ratio = (t - t0) / gap;
        const positions: [number, number][] = [];
        const members: typeof prev.members = [];

        for (const pm of common) {
          const nm = nextMap.get(pm.mmsi)!;
          const lon = pm.lon + (nm.lon - pm.lon) * ratio;
          const lat = pm.lat + (nm.lat - pm.lat) * ratio;
          const dLon = nm.lon - pm.lon;
          const dLat = nm.lat - pm.lat;
          const cog = (dLon === 0 && dLat === 0) ? pm.cog : (Math.atan2(dLon, dLat) * 180 / Math.PI + 360) % 360;
          members.push({ mmsi: pm.mmsi, name: pm.name, lon, lat, sog: pm.sog, cog, role: pm.role, isParent: pm.isParent });
          positions.push([lon, lat]);
        }

        const cLon = positions.reduce((s, p) => s + p[0], 0) / positions.length;
        const cLat = positions.reduce((s, p) => s + p[1], 0) / positions.length;
        const polygon = buildInterpPolygon(positions);

        result.push({
          ...prev,
          snapshotTime: new Date(t).toISOString(),
          polygon,
          centerLon: cLon,
          centerLat: cLat,
          memberCount: members.length,
          members,
          _interp: true,
          _longGap: true,
        });
      }
    }
  }
  return result;
}
