/**
 * Online Board URL serializer/parser.
 *
 * Pure functions — no side effects, no Angular imports, no Date objects.
 * Byte-exact parity with Angular's OnlineBoardUrlBuilderService /
 * OnlineBoardUrlParserService.
 *
 * @module
 */

import type { IParsedFlightId } from "./types";

// ---------------------------------------------------------------------------
// Public types
// ---------------------------------------------------------------------------

/** Discriminated union of all parsed online board URL parameter shapes */
export type OnlineBoardParams =
  | { type: "start" }
  | { type: "flight"; carrier: string; flightNumber: string; suffix?: string; date: string }
  | { type: "departure"; station: string; date: string; timeFrom?: string; timeTo?: string }
  | { type: "arrival"; station: string; date: string; timeFrom?: string; timeTo?: string }
  | {
      type: "route";
      departure: string;
      arrival: string;
      date: string;
      timeFrom?: string;
      timeTo?: string;
    }
  | { type: "details"; carrier: string; flightNumber: string; suffix?: string; date: string };

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

const ONLINE_BOARD_PREFIX = "onlineboard";
const TIME_RANGE_LENGTH = 8;

// ---------------------------------------------------------------------------
// Validation helpers
// ---------------------------------------------------------------------------

function isValidDate(date: string): boolean {
  return /^\d{8}$/.test(date);
}

function isValidTimeRange(timeRange: string): boolean {
  return /^\d{8}$/.test(timeRange);
}

function isLetter(ch: string): boolean {
  return /^[A-Za-z]$/.test(ch);
}

// ---------------------------------------------------------------------------
// Low-level parsers
// ---------------------------------------------------------------------------

/**
 * Parse a flight URL parameter string into its constituent parts.
 * Handles format: {carrier}{flightNumber}{suffix?}-{yyyyMMdd}
 *
 * Carrier detection logic (matching Angular):
 * - First 2 chars are always carrier.
 * - If the 3rd char exists and is a letter, it's part of the carrier (3-char carrier code).
 * - Last char of the flight info (before the dash) that is a letter is the suffix.
 */
export function parseFlightUrlParams(raw: string): IParsedFlightId | null {
  if (!raw) return null;

  const dashIdx = raw.indexOf("-");
  if (dashIdx < 0) return null;

  const flightInfo = raw.slice(0, dashIdx);
  const dateStr = raw.slice(dashIdx + 1);

  if (!flightInfo || !isValidDate(dateStr)) return null;

  // Carrier: first 2 chars + optional 3rd letter
  let carrierLen = 2;
  const thirdChar = flightInfo[2];
  if (flightInfo.length > 2 && thirdChar !== undefined && isLetter(thirdChar)) {
    carrierLen = 3;
  }

  if (flightInfo.length <= carrierLen) return null;

  const carrier = flightInfo.slice(0, carrierLen);

  // Suffix: last char if it's a letter
  const lastChar = flightInfo.at(-1);
  if (lastChar === undefined) return null;
  const hasSuffix = isLetter(lastChar);
  const suffix = hasSuffix ? lastChar : undefined;

  // Flight number: everything between carrier and optional suffix
  const numberEnd = hasSuffix ? flightInfo.length - 1 : flightInfo.length;
  const flightNumber = flightInfo.slice(carrierLen, numberEnd);

  if (!flightNumber) return null;

  const result: IParsedFlightId = {
    carrier,
    flightNumber,
    date: dateStr,
  };

  if (suffix !== undefined) {
    result.suffix = suffix;
  }

  return result;
}

/**
 * Build a flight URL parameter string from parts.
 * Output format: {carrier}{paddedFlightNumber}{suffix?}-{yyyyMMdd}
 *
 * Flight number is zero-padded so that flightNumber+suffix is at least 5 chars
 * (matching Angular's `.slice(-5)` logic).
 */
export function buildFlightUrlParams(id: IParsedFlightId): string {
  const suffix = id.suffix ?? "";
  // Pad flight number to 4 digits (standard IATA width).
  // Angular's `.slice(-5)` on `flightNumber+suffix` is a truncation guard,
  // not padding — the API already supplies zero-padded numbers. We pad here
  // to handle cases where the caller passes unpadded numbers (e.g. "100").
  const paddedNumber = id.flightNumber.padStart(4, "0");
  // Angular truncation: take last 5 chars of (number + suffix) to cap length
  const combined = `${paddedNumber}${suffix}`.slice(-5);

  return `${id.carrier}${combined}-${id.date}`;
}

/**
 * Parse time range from an 8-char continuous string "HHmmHHmm".
 * Returns undefined fields if the input is not a valid time range.
 */
function parseTimeRange(
  raw: string | undefined,
): { timeFrom: string; timeTo: string } | undefined {
  if (!raw || !isValidTimeRange(raw)) return undefined;
  return {
    timeFrom: raw.slice(0, 4),
    timeTo: raw.slice(4, TIME_RANGE_LENGTH),
  };
}

/**
 * Build time range string from optional from/to fields.
 * Returns undefined if either field is missing.
 */
function buildTimeRange(
  timeFrom: string | undefined,
  timeTo: string | undefined,
): string | undefined {
  if (!timeFrom || !timeTo) return undefined;
  return `${timeFrom}${timeTo}`;
}

/**
 * Parse a station URL parameter string.
 * Handles format: {station}-{yyyyMMdd}[-{timeFrom}{timeTo}]
 */
export function parseStationUrlParams(
  raw: string,
): { station: string; date: string; timeFrom?: string; timeTo?: string } | null {
  if (!raw) return null;

  const parts = raw.split("-");
  const station = parts[0];
  const dateStr = parts[1];
  if (station === undefined || dateStr === undefined) return null;

  if (!isValidDate(dateStr)) return null;

  const timeRaw = parts[2];
  const timeRange = parseTimeRange(timeRaw);

  // If there's a 3rd segment but it's not a valid time range, reject
  if (timeRaw !== undefined && !timeRange) return null;

  const result: { station: string; date: string; timeFrom?: string; timeTo?: string } = {
    station,
    date: dateStr,
  };

  if (timeRange) {
    result.timeFrom = timeRange.timeFrom;
    result.timeTo = timeRange.timeTo;
  }

  return result;
}

/**
 * Parse a route URL parameter string.
 * Handles format: {dep}-{arr}-{yyyyMMdd}[-{timeFrom}{timeTo}]
 */
export function parseRouteUrlParams(
  raw: string,
): {
  departure: string;
  arrival: string;
  date: string;
  timeFrom?: string;
  timeTo?: string;
} | null {
  if (!raw) return null;

  const parts = raw.split("-");
  const departure = parts[0];
  const arrival = parts[1];
  const dateStr = parts[2];
  if (departure === undefined || arrival === undefined || dateStr === undefined) return null;

  if (!isValidDate(dateStr)) return null;

  const timeRaw = parts[3];
  const timeRange = parseTimeRange(timeRaw);

  const result: {
    departure: string;
    arrival: string;
    date: string;
    timeFrom?: string;
    timeTo?: string;
  } = {
    departure,
    arrival,
    date: dateStr,
  };

  if (timeRange) {
    result.timeFrom = timeRange.timeFrom;
    result.timeTo = timeRange.timeTo;
  }

  return result;
}

// ---------------------------------------------------------------------------
// Helpers for constructing OnlineBoardParams with exactOptionalPropertyTypes
// ---------------------------------------------------------------------------

function toFlightParams(
  type: "flight" | "details",
  parsed: IParsedFlightId,
): OnlineBoardParams {
  if (parsed.suffix !== undefined) {
    return { type, carrier: parsed.carrier, flightNumber: parsed.flightNumber, suffix: parsed.suffix, date: parsed.date };
  }
  return { type, carrier: parsed.carrier, flightNumber: parsed.flightNumber, date: parsed.date };
}

function toStationParams(
  type: "departure" | "arrival",
  parsed: { station: string; date: string; timeFrom?: string; timeTo?: string },
): OnlineBoardParams {
  if (parsed.timeFrom !== undefined && parsed.timeTo !== undefined) {
    return { type, station: parsed.station, date: parsed.date, timeFrom: parsed.timeFrom, timeTo: parsed.timeTo };
  }
  return { type, station: parsed.station, date: parsed.date };
}

// ---------------------------------------------------------------------------
// Top-level parse / build
// ---------------------------------------------------------------------------

/**
 * Parse a raw URL path into typed online board params.
 * Returns null if the path does not match any known online board URL shape.
 */
export function parseOnlineBoardUrl(path: string): OnlineBoardParams | null {
  if (!path) return null;

  // Strip leading slash
  const normalized = path.startsWith("/") ? path.slice(1) : path;

  // Must start with "onlineboard"
  if (!normalized.startsWith(ONLINE_BOARD_PREFIX)) return null;

  // Get the rest after "onlineboard"
  const rest = normalized.slice(ONLINE_BOARD_PREFIX.length);

  // Start page: empty or just "/"
  if (!rest || rest === "/") return { type: "start" };

  // Must have "/" after "onlineboard"
  if (!rest.startsWith("/")) return null;

  const afterPrefix = rest.slice(1); // Remove leading "/"

  // Split into route type and params
  const slashIdx = afterPrefix.indexOf("/");

  if (slashIdx >= 0) {
    const routeType = afterPrefix.slice(0, slashIdx);
    const params = afterPrefix.slice(slashIdx + 1);

    switch (routeType) {
      case "flight": {
        const parsed = parseFlightUrlParams(params);
        if (!parsed) return null;
        return toFlightParams("flight", parsed);
      }

      case "departure": {
        const parsed = parseStationUrlParams(params);
        if (!parsed) return null;
        return toStationParams("departure", parsed);
      }

      case "arrival": {
        const parsed = parseStationUrlParams(params);
        if (!parsed) return null;
        return toStationParams("arrival", parsed);
      }

      case "route": {
        const parsed = parseRouteUrlParams(params);
        if (!parsed) return null;
        return parsed.timeFrom !== undefined && parsed.timeTo !== undefined
          ? { type: "route", departure: parsed.departure, arrival: parsed.arrival, date: parsed.date, timeFrom: parsed.timeFrom, timeTo: parsed.timeTo }
          : { type: "route", departure: parsed.departure, arrival: parsed.arrival, date: parsed.date };
      }

      default:
        return null;
    }
  }

  // No sub-slash: must be a details URL (flight ID directly after /onlineboard/)
  const parsed = parseFlightUrlParams(afterPrefix);
  if (!parsed) return null;
  return toFlightParams("details", parsed);
}

/**
 * Build a URL path segment from typed online board params.
 * Output is byte-exact match with Angular's URL builder.
 *
 * @returns the path segment without leading slash (e.g. "onlineboard/flight/SU0100-20250115")
 */
export function buildOnlineBoardUrl(params: OnlineBoardParams): string {
  switch (params.type) {
    case "start":
      return ONLINE_BOARD_PREFIX;

    case "flight": {
      const id: IParsedFlightId = {
        carrier: params.carrier,
        flightNumber: params.flightNumber,
        date: params.date,
      };
      if (params.suffix !== undefined) id.suffix = params.suffix;
      return `${ONLINE_BOARD_PREFIX}/flight/${buildFlightUrlParams(id)}`;
    }

    case "departure": {
      const timeRange = buildTimeRange(params.timeFrom, params.timeTo);
      const locationDate = `${params.station}-${params.date}`;
      return timeRange
        ? `${ONLINE_BOARD_PREFIX}/departure/${locationDate}-${timeRange}`
        : `${ONLINE_BOARD_PREFIX}/departure/${locationDate}`;
    }

    case "arrival": {
      const timeRange = buildTimeRange(params.timeFrom, params.timeTo);
      const locationDate = `${params.station}-${params.date}`;
      return timeRange
        ? `${ONLINE_BOARD_PREFIX}/arrival/${locationDate}-${timeRange}`
        : `${ONLINE_BOARD_PREFIX}/arrival/${locationDate}`;
    }

    case "route": {
      const timeRange = buildTimeRange(params.timeFrom, params.timeTo);
      const locationDate = `${params.departure}-${params.arrival}-${params.date}`;
      return timeRange
        ? `${ONLINE_BOARD_PREFIX}/route/${locationDate}-${timeRange}`
        : `${ONLINE_BOARD_PREFIX}/route/${locationDate}`;
    }

    case "details": {
      const id: IParsedFlightId = {
        carrier: params.carrier,
        flightNumber: params.flightNumber,
        date: params.date,
      };
      if (params.suffix !== undefined) id.suffix = params.suffix;
      return `${ONLINE_BOARD_PREFIX}/${buildFlightUrlParams(id)}`;
    }
  }
}
