/**
 * Calendar helpers for the flights-map filter date picker.
 *
 * All dates are treated as date-only (time zero'd) for day-level comparisons.
 * minDate/maxDate form the selectable window; `buildDisabledDates` produces
 * the complement of `availableDays` over that window; `findNextEnabledDate`
 * advances a current selection forward until it lands on an enabled day.
 *
 * Matches Angular `FlightsMapFiltersStateService` calendar behavior.
 */

import { mapWindowBounds } from "@/shared/dateWindow.js";

/** Today with time set to 00:00:00 local. */
export function today(baseYyyymmdd?: string): Date {
  if (baseYyyymmdd) {
    const parsed = parseYyyymmdd(baseYyyymmdd);
    if (parsed) return parsed;
  }
  const d = new Date();
  d.setHours(0, 0, 0, 0);
  return d;
}

/** minDate = today - 1 day (Angular parity). */
export function getMinDate(baseYyyymmdd?: string): Date {
  if (baseYyyymmdd) {
    const d = today(baseYyyymmdd);
    d.setDate(d.getDate() - 1);
    return d;
  }
  return mapWindowBounds()[0];
}

/** maxDate = today + 6 months (Angular parity). */
export function getMaxDate(baseYyyymmdd?: string): Date {
  if (baseYyyymmdd) {
    const d = today(baseYyyymmdd);
    d.setMonth(d.getMonth() + 6);
    return d;
  }
  return mapWindowBounds()[1];
}

/**
 * Every date in [minDate, maxDate] whose yyyymmdd is NOT in `availableDays`.
 * When `availableDays` is empty, every date in the range is disabled.
 */
export function buildDisabledDates(
  minDate: Date,
  maxDate: Date,
  availableDays: ReadonlyArray<string>,
): Date[] {
  const available = new Set(availableDays);
  const disabled: Date[] = [];
  const cursor = new Date(minDate);
  cursor.setHours(0, 0, 0, 0);
  const end = new Date(maxDate);
  end.setHours(0, 0, 0, 0);
  while (cursor.getTime() <= end.getTime()) {
    const key = toYyyymmdd(cursor);
    if (!available.has(key)) disabled.push(new Date(cursor));
    cursor.setDate(cursor.getDate() + 1);
  }
  return disabled;
}

/**
 * Advances `current` day-by-day until it hits a non-disabled date, staying
 * within [minDate, maxDate]. Returns `null` if no enabled date exists.
 * `current` itself is tested first. If `current < minDate`, starts at minDate.
 */
export function findNextEnabledDate(
  current: Date,
  disabledDates: ReadonlyArray<Date>,
  minDate: Date,
  maxDate: Date,
): Date | null {
  const disabledSet = new Set(
    disabledDates.map((d) => {
      const x = new Date(d);
      x.setHours(0, 0, 0, 0);
      return x.getTime();
    }),
  );
  const min = new Date(minDate);
  min.setHours(0, 0, 0, 0);
  const max = new Date(maxDate);
  max.setHours(0, 0, 0, 0);

  const cursor = new Date(current);
  cursor.setHours(0, 0, 0, 0);

  if (cursor.getTime() < min.getTime()) cursor.setTime(min.getTime());

  while (cursor.getTime() <= max.getTime()) {
    if (!disabledSet.has(cursor.getTime())) return new Date(cursor);
    cursor.setDate(cursor.getDate() + 1);
  }
  return null;
}

function toYyyymmdd(d: Date): string {
  const y = d.getFullYear().toString();
  const m = (d.getMonth() + 1).toString().padStart(2, "0");
  const day = d.getDate().toString().padStart(2, "0");
  return `${y}${m}${day}`;
}

function parseYyyymmdd(value: string): Date | null {
  if (!/^\d{8}$/.test(value)) return null;
  const y = Number(value.slice(0, 4));
  const m = Number(value.slice(4, 6));
  const d = Number(value.slice(6, 8));
  const parsed = new Date(y, m - 1, d);
  parsed.setHours(0, 0, 0, 0);
  if (
    parsed.getFullYear() !== y ||
    parsed.getMonth() !== m - 1 ||
    parsed.getDate() !== d
  ) {
    return null;
  }
  return parsed;
}
