// Группировка слотов в «витки» (проходы) для пошаговой навигации курсором ◀/▶.
//
// Виток (проход над районом) = слоты ОДНОГО КА на ОДНОМ обороте: ключ (satelliteId, cycle,
// revolution, revolutionSign) — ровно тот кортеж, что показан в колонке «Виток» таблицы. В отличие
// от полосы (slotStrips.ts: один КА + один крен + непрерывность по времени) виток НЕ дробится по
// крену — внутри одного прохода «веер» нацеливаний на разных кренах принадлежит одному витку.
//
// Это identity-группировка для навигации (встать на проход, шагнуть на следующий), НЕ проверка
// выполнимости брони. Порядок витков — хронологический по началу прохода (по всем КА вперемешку),
// чтобы «от первого к последнему» работало при нескольких КА.

import type { RevolutionSign, SlotDTO } from "../../model/slotTypes"
import { parseLocal } from "../../utils/localTime"

/** Минимальный набор полей слота, нужный для группировки в витки. */
type Passable = Pick<SlotDTO, "satelliteId" | "revolution" | "revolutionSign" | "cycle" | "tn">

/** Один виток (проход): кортеж витка + время начала и ключи слотов (упорядочены по времени). */
export type Pass = {
  /** Стабильный id витка: `${sat}:${cycle}:${revolution}:${sign}`. */
  id: string
  satelliteId: number
  revolution: number
  revolutionSign: RevolutionSign
  cycle: number
  /** Начало прохода — минимальный tn среди слотов витка (мс, локально). */
  startMs: number
  /** Ключи слотов витка, отсортированы по времени начала. */
  keys: string[]
}

/** Ключ витка из полей слота. */
function passId(slot: Passable): string {
  return `${slot.satelliteId}:${slot.cycle}:${slot.revolution}:${slot.revolutionSign}`
}

/**
 * Размечает слоты по виткам и возвращает витки в хронологическом порядке (по началу прохода).
 *
 * Детерминизм: порядок не зависит от входного массива — внутри сортируем ключи витка по времени,
 * сами витки — по (startMs, id). Слоты без валидного времени игнорируем для startMs (берут +∞ при
 * сравнении внутри витка), но в keys попадают, чтобы виток не «потерял» слот.
 *
 * @param slots Слоты для разметки (любой порядок).
 * @param keyOf Аксессор ключа слота (как slotKey в TguSlotsTab) — чтобы не дублировать формулу.
 */
export function groupPasses<T extends Passable>(slots: readonly T[], keyOf: (slot: T) => string): Pass[] {
  type Bucket = { slot: Passable; entries: { key: string; tnMs: number }[] }
  const buckets = new Map<string, Bucket>()
  for (const slot of slots) {
    const id = passId(slot)
    let bucket = buckets.get(id)
    if (!bucket) {
      bucket = { slot, entries: [] }
      buckets.set(id, bucket)
    }
    bucket.entries.push({ key: keyOf(slot), tnMs: parseLocal(slot.tn) })
  }

  const passes: Pass[] = []
  for (const [id, bucket] of buckets) {
    bucket.entries.sort((a, b) => a.tnMs - b.tnMs || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))
    const startMs = bucket.entries.reduce(
      (min, e) => (Number.isFinite(e.tnMs) ? Math.min(min, e.tnMs) : min),
      Number.POSITIVE_INFINITY,
    )
    passes.push({
      id,
      satelliteId: bucket.slot.satelliteId,
      revolution: bucket.slot.revolution,
      revolutionSign: bucket.slot.revolutionSign,
      cycle: bucket.slot.cycle,
      startMs,
      keys: bucket.entries.map((e) => e.key),
    })
  }

  passes.sort((a, b) => a.startMs - b.startMs || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
  return passes
}
