import type { HistoryFrame } from '../components/korea/fleetClusterTypes';
import type { CorrelationVesselTrack } from '../services/vesselAnalysis';

export interface TripsLayerDatum {
  id: string;
  path: [number, number][];       // [lon, lat][]
  timestamps: number[];           // relative ms from startTime (TripsLayer requirement)
  color: [number, number, number, number];
}

export interface MemberPosition {
  mmsi: string;
  name: string;
  lon: number;
  lat: number;
  cog: number;
  role: string;
  isParent: boolean;
  isGear: boolean;
  stale: boolean;
}

export interface CenterTrailSegment {
  path: [number, number][];
  isInterpolated: boolean;
}

/**
 * Walk all frames and collect per-MMSI tracks for TripsLayer rendering.
 * timestamps are relative ms from startTime (deck.gl TripsLayer requirement).
 */
export function buildMemberTripsData(frames: HistoryFrame[], startTime: number): TripsLayerDatum[] {
  const memberMap = new Map<string, { path: [number, number][]; timestamps: number[] }>();

  for (const frame of frames) {
    const t = new Date(frame.snapshotTime).getTime() - startTime;
    for (const member of frame.members) {
      const entry = memberMap.get(member.mmsi) ?? { path: [], timestamps: [] };
      entry.path.push([member.lon, member.lat]);
      entry.timestamps.push(t);
      memberMap.set(member.mmsi, entry);
    }
  }

  const result: TripsLayerDatum[] = [];
  for (const [mmsi, data] of memberMap) {
    if (data.path.length >= 2) {
      result.push({
        id: mmsi,
        path: data.path,
        timestamps: data.timestamps,
        color: [200, 200, 200, 180],
      });
    }
  }
  return result;
}

/**
 * Convert correlation vessel tracks to TripsLayer format.
 * timestamps are relative ms from startTime (deck.gl TripsLayer requirement).
 */
export function buildCorrelationTripsData(
  tracks: CorrelationVesselTrack[],
  startTime: number,
): TripsLayerDatum[] {
  const result: TripsLayerDatum[] = [];
  for (const vt of tracks) {
    if (vt.track.length >= 2) {
      result.push({
        id: vt.mmsi,
        path: vt.track.map(pt => [pt.lon, pt.lat]),
        timestamps: vt.track.map(pt => pt.ts - startTime),
        color: [96, 165, 250, 150],
      });
    }
  }
  return result;
}

/**
 * Split center trail into real/interpolated segments and collect real-data dot positions.
 * Consecutive frames with the same _longGap flag form one segment.
 */
export function buildCenterTrailData(
  frames: HistoryFrame[],
): { segments: CenterTrailSegment[]; dots: [number, number][] } {
  const segments: CenterTrailSegment[] = [];
  const dots: [number, number][] = [];

  if (frames.length === 0) return { segments, dots };

  let segStart = 0;

  for (let i = 1; i <= frames.length; i++) {
    const curInterp = i < frames.length ? !!frames[i]._longGap : null;
    const startInterp = !!frames[segStart]._longGap;

    if (i < frames.length && curInterp === startInterp) continue;

    const from = segStart > 0 ? segStart - 1 : segStart;
    const seg = frames.slice(from, i);
    if (seg.length >= 2) {
      segments.push({
        path: seg.map(s => [s.centerLon, s.centerLat]),
        isInterpolated: startInterp,
      });
    }
    segStart = i;
  }

  for (const frame of frames) {
    if (!frame._longGap && !frame._interp) {
      dots.push([frame.centerLon, frame.centerLat]);
    }
  }

  return { segments, dots };
}

/**
 * Map real (non-interpolated) frames to normalized [0, 1] positions
 * along the timeline, for progress bar gap indicators.
 */
export function buildSnapshotRanges(
  frames: HistoryFrame[],
  startTime: number,
  endTime: number,
): number[] {
  const duration = endTime - startTime;
  if (duration <= 0) return [];
  return frames
    .filter(h => !h._interp)
    .map(h => (new Date(h.snapshotTime).getTime() - startTime) / duration);
}

/**
 * Cursor-based frame index lookup.
 * Uses forward linear scan from cursorHint during normal playback (O(1–2)),
 * falls back to binary search when time goes backward or hint is invalid.
 * Returns { index: -1 } when the closest frame is more than 30 minutes away.
 */
export function findFrameAtTime(
  frameTimes: number[],
  timeMs: number,
  cursorHint: number,
): { index: number; cursor: number } {
  if (frameTimes.length === 0) return { index: -1, cursor: 0 };

  // Forward linear scan from cursor
  if (cursorHint >= 0 && cursorHint < frameTimes.length) {
    if (frameTimes[cursorHint] <= timeMs) {
      let i = cursorHint;
      while (i < frameTimes.length - 1 && frameTimes[i + 1] <= timeMs) {
        i++;
      }
      return { index: i, cursor: i };
    }
    // Time went backward — fall through to binary search
  }

  // Binary search fallback
  let lo = 0;
  let hi = frameTimes.length - 1;
  while (lo < hi) {
    const mid = (lo + hi + 1) >> 1;
    if (frameTimes[mid] <= timeMs) {
      lo = mid;
    } else {
      hi = mid - 1;
    }
  }

  if (Math.abs(frameTimes[lo] - timeMs) > 1_800_000) {
    return { index: -1, cursor: lo };
  }
  return { index: lo, cursor: lo };
}

/**
 * Interpolate member positions between frameIdx and frameIdx+1 at timeMs.
 * Returns stale=true for frames marked as _longGap or _interp.
 */
export function interpolateMemberPositions(
  frames: HistoryFrame[],
  frameIdx: number,
  timeMs: number,
): MemberPosition[] {
  if (frameIdx < 0 || frameIdx >= frames.length) return [];

  const frame = frames[frameIdx];
  const isStale = !!frame._longGap || !!frame._interp;

  const toPosition = (
    m: { mmsi: string; name: string; lon: number; lat: number; cog: number; role: string; isParent: boolean },
    lon: number,
    lat: number,
    cog: number,
  ): MemberPosition => ({
    mmsi: m.mmsi,
    name: m.name,
    lon,
    lat,
    cog,
    role: m.role,
    isParent: m.isParent,
    isGear: m.role === 'GEAR' || !m.isParent,
    stale: isStale,
  });

  // No next frame — return current positions as-is
  if (frameIdx >= frames.length - 1) {
    return frame.members.map(m => toPosition(m, m.lon, m.lat, m.cog));
  }

  const nextFrame = frames[frameIdx + 1];
  const t0 = new Date(frame.snapshotTime).getTime();
  const t1 = new Date(nextFrame.snapshotTime).getTime();
  const ratio = t1 > t0 ? Math.max(0, Math.min(1, (timeMs - t0) / (t1 - t0))) : 0;

  const nextMap = new Map(nextFrame.members.map(m => [m.mmsi, m]));

  return frame.members.map(m => {
    const nm = nextMap.get(m.mmsi);
    if (!nm) {
      return toPosition(m, m.lon, m.lat, m.cog);
    }
    return toPosition(
      m,
      m.lon + (nm.lon - m.lon) * ratio,
      m.lat + (nm.lat - m.lat) * ratio,
      nm.cog,
    );
  });
}
