/**
 * Codec for the `?request=...` query parameter on flight-details URLs.
 *
 * Online-Board forms per TZ §4.1.2 Table 5 row 6:
 *   onlineboard-flight-{flightNumber}-{yyyyMMdd}
 *   onlineboard-departure-{iata}-{yyyyMMdd}[-{HHmmHHmm}]
 *   onlineboard-arrival-{iata}-{yyyyMMdd}[-{HHmmHHmm}]
 *   onlineboard-route-{depIata}-{arrIata}-{yyyyMMdd}[-{HHmmHHmm}]
 *
 * Schedule forms per TZ §4.1.2 Table 5 row 11:
 *   schedule-route-{dep}-{arr}-{dateFrom}-{dateTo}[-{HHmmHHmm}][-C{n}]
 *   schedule-route-{dep}-{arr}-{dateFrom}-{dateTo}[-{HHmmHHmm}][-C{n}]-{arr}-{dep}-{returnDateFrom}-{returnDateTo}[-{HHmmHHmm}][-C{n}]
 */

export type DetailsRequest =
  | { area: "onlineboard"; kind: "flight"; flightNumber: string; date: string }
  | {
      area: "onlineboard";
      kind: "departure" | "arrival";
      station: string;
      date: string;
      timeFrom?: string;
      timeTo?: string;
    }
  | {
      area: "onlineboard";
      kind: "route";
      departure: string;
      arrival: string;
      date: string;
      timeFrom?: string;
      timeTo?: string;
    }
  | {
      area: "schedule";
      kind: "route";
      departure: string;
      arrival: string;
      dateFrom: string;   // yyyyMMdd (outbound dateFrom)
      dateTo: string;     // yyyyMMdd (outbound dateTo)
      timeFrom?: string;  // HHmm
      timeTo?: string;    // HHmm
      connections?: number; // 0 = C0 (direct-only), 1 = C1 (one connection)
      returnTrip?: {
        departure: string;
        arrival: string;
        dateFrom: string;
        dateTo: string;
        timeFrom?: string;
        timeTo?: string;
        connections?: number;
      };
    };

const ONLINEBOARD_AREA = "onlineboard";
const SCHEDULE_AREA = "schedule";

function isDate(s: string | undefined): s is string {
  return typeof s === "string" && /^\d{8}$/.test(s);
}

function isTimeRange(s: string | undefined): s is string {
  return typeof s === "string" && /^\d{8}$/.test(s);
}

function isConnectionToken(s: string | undefined): boolean {
  // Latin C or Cyrillic С (U+0421)
  return typeof s === "string" && (s.startsWith("C") || s.startsWith("С")) && s.length >= 2;
}

function parseConnectionToken(s: string): number | undefined {
  const numStr = s.slice(1);
  const num = Number(numStr);
  return Number.isNaN(num) ? undefined : num;
}

// ---------------------------------------------------------------------------
// Schedule leg serialization helpers
// ---------------------------------------------------------------------------

interface ScheduleLeg {
  departure: string;
  arrival: string;
  dateFrom: string;
  dateTo: string;
  timeFrom?: string;
  timeTo?: string;
  connections?: number;
}

function buildScheduleLegTokens(leg: ScheduleLeg): string[] {
  const tokens: string[] = [leg.departure, leg.arrival, leg.dateFrom, leg.dateTo];
  if (leg.timeFrom && leg.timeTo) {
    tokens.push(`${leg.timeFrom}${leg.timeTo}`);
  }
  if (leg.connections !== undefined) {
    tokens.push(`C${leg.connections}`);
  }
  return tokens;
}

/**
 * Try to consume one schedule leg from `parts` starting at `offset`.
 * A leg starts with: {iata}-{iata}-{yyyyMMdd}-{yyyyMMdd} then optional time/conn tokens.
 * Returns the parsed leg and the index of the next unused part, or null if invalid.
 */
function parseScheduleLeg(
  parts: string[],
  offset: number,
): { leg: ScheduleLeg; nextOffset: number } | null {
  // Minimum: dep, arr, dateFrom, dateTo = 4 tokens
  if (offset + 3 >= parts.length) return null;

  const departure = parts[offset];
  const arrival = parts[offset + 1];
  const dateFrom = parts[offset + 2];
  const dateTo = parts[offset + 3];

  if (!departure || !arrival || !isDate(dateFrom) || !isDate(dateTo)) return null;

  let nextOffset = offset + 4;
  const leg: ScheduleLeg = { departure, arrival, dateFrom, dateTo };

  // Consume optional timeRange
  const maybeTime = parts[nextOffset];
  if (maybeTime && isTimeRange(maybeTime) && !isConnectionToken(maybeTime)) {
    leg.timeFrom = maybeTime.slice(0, 4);
    leg.timeTo = maybeTime.slice(4, 8);
    nextOffset++;
  }

  // Consume optional connections token
  const maybeConn = parts[nextOffset];
  if (maybeConn && isConnectionToken(maybeConn)) {
    const conn = parseConnectionToken(maybeConn);
    if (conn !== undefined) {
      leg.connections = conn;
    }
    nextOffset++;
  }

  return { leg, nextOffset };
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

export function buildDetailsRequestParam(r: DetailsRequest): string {
  if (r.area === SCHEDULE_AREA) {
    const outboundTokens = buildScheduleLegTokens(r);
    const allTokens = [SCHEDULE_AREA, "route", ...outboundTokens];
    if (r.returnTrip) {
      allTokens.push(...buildScheduleLegTokens(r.returnTrip));
    }
    return allTokens.join("-");
  }

  switch (r.kind) {
    case "flight":
      return `${r.area}-flight-${r.flightNumber}-${r.date}`;
    case "departure":
    case "arrival": {
      const base = `${r.area}-${r.kind}-${r.station}-${r.date}`;
      return r.timeFrom && r.timeTo ? `${base}-${r.timeFrom}${r.timeTo}` : base;
    }
    case "route": {
      const base = `${r.area}-route-${r.departure}-${r.arrival}-${r.date}`;
      return r.timeFrom && r.timeTo ? `${base}-${r.timeFrom}${r.timeTo}` : base;
    }
  }
}

export function parseDetailsRequestParam(raw: string): DetailsRequest | null {
  if (!raw) return null;
  const parts = raw.split("-");
  if (parts.length < 4) return null;

  const area = parts[0];

  // ---------------------------------------------------------------------------
  // Schedule area
  // ---------------------------------------------------------------------------
  if (area === SCHEDULE_AREA) {
    const kind = parts[1];
    if (kind !== "route") return null;

    // parts[2..] are leg tokens starting from index 2
    const outboundResult = parseScheduleLeg(parts, 2);
    if (!outboundResult) return null;

    const { leg: outbound, nextOffset } = outboundResult;

    // Detect whether a second leg follows. A second leg starts with two IATA
    // codes followed by two dates. Check that parts[nextOffset] and
    // parts[nextOffset+1] are 3-letter IATA-ish codes and parts[nextOffset+2]
    // and [nextOffset+3] are dates. This disambiguates a return trip from
    // trailing garbage.
    let returnTrip: ScheduleLeg | undefined;
    if (nextOffset < parts.length) {
      const maybeSecond = parseScheduleLeg(parts, nextOffset);
      if (maybeSecond) {
        returnTrip = maybeSecond.leg;
      }
    }

    const result: DetailsRequest = {
      area: "schedule",
      kind: "route",
      departure: outbound.departure,
      arrival: outbound.arrival,
      dateFrom: outbound.dateFrom,
      dateTo: outbound.dateTo,
    };

    if (outbound.timeFrom !== undefined && outbound.timeTo !== undefined) {
      result.timeFrom = outbound.timeFrom;
      result.timeTo = outbound.timeTo;
    }
    if (outbound.connections !== undefined) {
      result.connections = outbound.connections;
    }
    if (returnTrip) {
      const rt: NonNullable<typeof result.returnTrip> = {
        departure: returnTrip.departure,
        arrival: returnTrip.arrival,
        dateFrom: returnTrip.dateFrom,
        dateTo: returnTrip.dateTo,
      };
      if (returnTrip.timeFrom !== undefined && returnTrip.timeTo !== undefined) {
        rt.timeFrom = returnTrip.timeFrom;
        rt.timeTo = returnTrip.timeTo;
      }
      if (returnTrip.connections !== undefined) {
        rt.connections = returnTrip.connections;
      }
      result.returnTrip = rt;
    }

    return result;
  }

  // ---------------------------------------------------------------------------
  // Online-Board area
  // ---------------------------------------------------------------------------
  if (area !== ONLINEBOARD_AREA) return null;

  const kind = parts[1];

  if (kind === "flight") {
    const [, , flightNumber, date] = parts;
    if (!flightNumber || !isDate(date) || parts.length !== 4) return null;
    return { area: "onlineboard", kind: "flight", flightNumber, date };
  }

  if (kind === "departure" || kind === "arrival") {
    const [, , station, date, maybeTime] = parts;
    if (!station || !isDate(date)) return null;
    if (parts.length === 4) {
      return { area: "onlineboard", kind, station, date };
    }
    if (parts.length === 5 && isTimeRange(maybeTime)) {
      return {
        area: "onlineboard",
        kind,
        station,
        date,
        timeFrom: maybeTime.slice(0, 4),
        timeTo: maybeTime.slice(4, 8),
      };
    }
    return null;
  }

  if (kind === "route") {
    const [, , departure, arrival, date, maybeTime] = parts;
    if (!departure || !arrival || !isDate(date)) return null;
    if (parts.length === 5) {
      return { area: "onlineboard", kind: "route", departure, arrival, date };
    }
    if (parts.length === 6 && isTimeRange(maybeTime)) {
      return {
        area: "onlineboard",
        kind: "route",
        departure,
        arrival,
        date,
        timeFrom: maybeTime.slice(0, 4),
        timeTo: maybeTime.slice(4, 8),
      };
    }
    return null;
  }

  return null;
}
