/**
 * Day-grouped flight list for the schedule results page.
 *
 * Mirrors Angular's `schedule-search-result-day` block: flights are
 * grouped by their scheduled departure date and rendered under a
 * `Воскресенье 19 Апреля`-style header. Within each group, the
 * `FlightList` component handles per-flight rendering.
 *
 * Falls back to the plain `FlightList` when only one date is in scope
 * (e.g. single-day search) — no header noise.
 */

import { type FC, useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "@/i18n/provider.js";
import { FlightList } from "@/ui/flights/FlightList.js";
import { FlightListSkeleton } from "@/ui/flights/FlightListSkeleton.js";
import { useLocale } from "@/i18n/useLocale.js";
import type { ISimpleFlight, IFlightLeg } from "@/features/online-board/types.js";
import { ScheduleFlightBody } from "./ScheduleFlightBody.js";
import type { ScheduleSortMode } from "./ScheduleColumnHeaders.js";
import "./DayGroupedFlightList.scss";

type SortMode = ScheduleSortMode;

export interface DayGroupedFlightListProps {
  flights: ISimpleFlight[];
  loading?: boolean;
  onFlightClick?: (flight: ISimpleFlight) => void;
  /** Aeroflot booking URL for each flight. Returning null hides the Buy link. */
  buyUrlFor?: (flight: ISimpleFlight) => string | null;
  initialCurrentFlightId?: string | null;
  /** Lifted sort state — when set, the caller (sticky `ScheduleColumnHeaders`)
   *  drives sort, and the internal header sort buttons sync to it. */
  sortMode?: SortMode;
  onSortChange?: (mode: SortMode) => void;
  /** Suppress the internal column-header row — used when the page renders
   *  its own sticky `ScheduleColumnHeaders` above the list. */
  hideColumnHeaders?: boolean;
}

interface DayGroup {
  /** ISO yyyy-MM-dd of the day. */
  date: string;
  /** Year/month/day extracted once for header rendering. */
  parsed: Date;
  flights: ISimpleFlight[];
}

function getPrimaryLeg(flight: ISimpleFlight): IFlightLeg {
  if (flight.routeType === "Direct") return flight.leg;
  const first = flight.legs[0];
  if (!first) throw new Error("MultiLeg flight has no legs");
  return first;
}

/** Pull the local-time wall-clock date (yyyy-MM-dd) from the first leg. */
function getDepartureDate(flight: ISimpleFlight): string {
  const leg = getPrimaryLeg(flight);
  const iso = leg.departure.times.scheduledDeparture.local ?? "";
  return iso.slice(0, 10);
}

function groupFlightsByDay(flights: ISimpleFlight[]): DayGroup[] {
  const buckets = new Map<string, ISimpleFlight[]>();
  for (const f of flights) {
    const date = getDepartureDate(f);
    if (!date) continue;
    const arr = buckets.get(date) ?? [];
    arr.push(f);
    buckets.set(date, arr);
  }
  return [...buckets.entries()]
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([date, list]) => {
      const [y, m, d] = date.split("-").map((s) => parseInt(s, 10));
      return {
        date,
        parsed: new Date(y ?? 1970, (m ?? 1) - 1, d ?? 1),
        flights: list,
      };
    });
}

/** Departure-time sort key from the primary leg (used by `departureUp/Down`). */
function depMinutes(f: ISimpleFlight): number {
  const iso = getPrimaryLeg(f).departure.times.scheduledDeparture.local ?? "";
  const t = iso.slice(11, 16); // 'HH:mm'
  if (!t) return 0;
  const [h, m] = t.split(":").map((s) => parseInt(s, 10));
  return (h ?? 0) * 60 + (m ?? 0);
}
function arrMinutes(f: ISimpleFlight): number {
  let last: IFlightLeg;
  if (f.routeType === "Direct") {
    last = f.leg;
  } else {
    const tail = f.legs[f.legs.length - 1];
    if (!tail) return 0;
    last = tail;
  }
  const iso = last.arrival.times.scheduledArrival.local ?? "";
  const t = iso.slice(11, 16);
  if (!t) return 0;
  const [h, m] = t.split(":").map((s) => parseInt(s, 10));
  return (h ?? 0) * 60 + (m ?? 0);
}
function flyingMinutes(f: ISimpleFlight): number {
  const ft = f.flyingTime ?? "";
  const m = /^(\d+):(\d+)/.exec(ft);
  if (!m) return 0;
  return parseInt(m[1] ?? "0", 10) * 60 + parseInt(m[2] ?? "0", 10);
}

function sortFlights(flights: ISimpleFlight[], mode: SortMode): ISimpleFlight[] {
  if (mode === "none") return flights;
  const arr = flights.slice();
  switch (mode) {
    case "departureUp": return arr.sort((a, b) => depMinutes(a) - depMinutes(b));
    case "departureDown": return arr.sort((a, b) => depMinutes(b) - depMinutes(a));
    case "timeUp": return arr.sort((a, b) => flyingMinutes(a) - flyingMinutes(b));
    case "timeDown": return arr.sort((a, b) => flyingMinutes(b) - flyingMinutes(a));
    case "arrivalUp": return arr.sort((a, b) => arrMinutes(a) - arrMinutes(b));
    case "arrivalDown": return arr.sort((a, b) => arrMinutes(b) - arrMinutes(a));
  }
}

export const DayGroupedFlightList: FC<DayGroupedFlightListProps> = ({
  flights,
  loading,
  onFlightClick,
  buyUrlFor,
  initialCurrentFlightId,
  sortMode: sortModeProp,
  onSortChange,
  hideColumnHeaders,
}) => {
  const { locale, language } = useLocale();
  const { t } = useTranslation();
  const [internalSortMode, setInternalSortMode] = useState<SortMode>("none");
  const sortMode = sortModeProp ?? internalSortMode;
  const setSortMode = (next: SortMode | ((prev: SortMode) => SortMode)) => {
    const value = typeof next === "function" ? next(sortMode) : next;
    if (onSortChange) onSortChange(value);
    if (sortModeProp === undefined) setInternalSortMode(value);
  };
  const handleFlightDetails = useCallback(
    (flight: ISimpleFlight) => {
      if (onFlightClick) {
        onFlightClick(flight);
      }
    },
    [onFlightClick],
  );
  // Track which days the user has expanded. Default: today's day group
  // (if it's in scope). Angular's `p-accordion` is `[multiple]="true"`
  // and `[activeIndex]` defaults to the index of today's date when
  // present; only the user's clicks deviate after that.
  const [expandedDays, setExpandedDays] = useState<Set<string>>(() => new Set());
  const groups = useMemo(() => {
    const g = groupFlightsByDay(flights);
    return g.map((day) => ({ ...day, flights: sortFlights(day.flights, sortMode) }));
  }, [flights, sortMode]);

  // Auto-open the default day per TZ §4.1.14: current week expands today;
  // future weeks expand the first valid day (week where only Sunday is
  // valid → Sunday). The user can collapse it; we never re-open after
  // that for the same date.
  const [autoOpenedFor, setAutoOpenedFor] = useState<string | null>(null);
  useEffect(() => {
    if (groups.length === 0) return;
    const now = new Date();
    const todayIso = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
    const defaultDate = groups.some((g) => g.date === todayIso)
      ? todayIso
      : (groups[0]?.date ?? null);
    if (!defaultDate || autoOpenedFor === defaultDate) return;
    setExpandedDays((prev) => {
      const next = new Set(prev);
      next.add(defaultDate);
      return next;
    });
    setAutoOpenedFor(defaultDate);
  }, [groups, autoOpenedFor]);

  // Mirror Angular `ScheduleDaysComponent.expandDefaultFlight`: once a
  // day group auto-expands, the first flight inside it should also
  // expand. Only fills in when the caller didn't pass an explicit
  // initialCurrentFlightId — that props value wins for deep links to a
  // specific flight.
  const autoInitialFlightId = useMemo(() => {
    if (initialCurrentFlightId) return undefined;
    if (!autoOpenedFor) return undefined;
    const group = groups.find((g) => g.date === autoOpenedFor);
    return group?.flights[0]?.id;
  }, [initialCurrentFlightId, autoOpenedFor, groups]);
  const resolvedInitialFlightId = initialCurrentFlightId ?? autoInitialFlightId;

  const setSort = (mode: SortMode) => {
    setSortMode((cur) => (cur === mode ? "none" : mode));
  };

  /** Angular's schedule renders a sortable column header row above the
   *  list. Click an arrow to toggle ascending/descending; clicking the
   *  same arrow again clears the sort. */
  const sortBtn = (mode: SortMode, dir: "up" | "down") => (
    <button
      type="button"
      className={`day-grouped-flight-list__sort day-grouped-flight-list__sort--${dir}${
        sortMode === mode ? " day-grouped-flight-list__sort--active" : ""
      }`}
      onClick={() => setSort(mode)}
      aria-label={`${dir === "up" ? "↑" : "↓"} ${mode}`}
      data-testid={`schedule-sort-${mode}`}
    >
      <svg viewBox="0 0 8 6" width="8" height="6" aria-hidden="true">
        {dir === "up" ? (
          <path d="M4 0L8 6L0 6Z" fill="currentColor" />
        ) : (
          <path d="M4 6L0 0L8 0Z" fill="currentColor" />
        )}
      </svg>
    </button>
  );

  const headerRow = (
    <div
      className="day-grouped-flight-list__column-headers"
      data-testid="schedule-column-headers"
    >
      <span>{t("SCHEDULE.COL-FLIGHT") || "РЕЙС"}</span>
      <span>{t("SCHEDULE.COL-AIRLINE") || "АВИАКОМПАНИЯ, БОРТ"}</span>
      <span className="day-grouped-flight-list__col-sortable">
        {t("SCHEDULE.COL-DEPARTURE") || "ВЫЛЕТ"}
        <span className="day-grouped-flight-list__sort-group">
          {sortBtn("departureUp", "up")}
          {sortBtn("departureDown", "down")}
        </span>
      </span>
      <span className="day-grouped-flight-list__col-sortable">
        {t("SCHEDULE.COL-DURATION") || "ВРЕМЯ В ПУТИ"}
        <span className="day-grouped-flight-list__sort-group">
          {sortBtn("timeUp", "up")}
          {sortBtn("timeDown", "down")}
        </span>
      </span>
      <span className="day-grouped-flight-list__col-sortable">
        {t("SCHEDULE.COL-ARRIVAL") || "ПРИЛЕТ"}
        <span className="day-grouped-flight-list__sort-group">
          {sortBtn("arrivalUp", "up")}
          {sortBtn("arrivalDown", "down")}
        </span>
      </span>
    </div>
  );

  // The schedule expanded body — a per-leg route diagram with transfer
  // boxes — replaces the default time/transition rows. Mirrors Angular's
  // `<flight-details-body-actions>` strip (share + buy + register + status)
  // via `showActions`. The collapsed-row inline buy link wired through
  // FlightList's `buyUrlFor` prop is independent of this strip.
  const renderScheduleBody = useCallback(
    (f: ISimpleFlight) => (
      <ScheduleFlightBody
        flight={f}
        showActions
        locale={locale}
        onFlightDetails={handleFlightDetails}
      />
    ),
    [locale, handleFlightDetails],
  );

  if (loading) return <FlightListSkeleton count={5} />;

  if (groups.length === 0) {
    return <FlightList flights={[]} loading={false} />;
  }

  // Single-day result: skip grouping noise and render the flat list.
  if (groups.length === 1) {
    return (
      <div className="day-grouped-flight-list">
        {hideColumnHeaders ? null : headerRow}
        <FlightList
          flights={flights}
          loading={false}
          direction="schedule"
          renderExpandedBody={renderScheduleBody}
          {...(onFlightClick ? { onFlightClick } : {})}
          {...(buyUrlFor ? { buyUrlFor } : {})}
          {...(resolvedInitialFlightId
            ? { initialCurrentFlightId: resolvedInitialFlightId }
            : {})}
        />
      </div>
    );
  }

  // Build day-of-week + month formatters once per locale so the
  // genitive month form is used for Russian (`19 апреля`, not `апрель`).
  const weekdayFmt = new Intl.DateTimeFormat(language, { weekday: "long" });
  const dayMonthFmt = new Intl.DateTimeFormat(language, {
    day: "numeric",
    month: "long",
  });

  const toggleDay = (date: string): void => {
    setExpandedDays((prev) => {
      const next = new Set(prev);
      if (next.has(date)) next.delete(date);
      else next.add(date);
      return next;
    });
  };

  return (
    <div className="day-grouped-flight-list" data-testid="day-grouped-flight-list">
      {hideColumnHeaders ? null : headerRow}
      {groups.map((g) => {
        const weekday = weekdayFmt.format(g.parsed);
        const dayMonth = dayMonthFmt.format(g.parsed);
        const collapsed = !expandedDays.has(g.date);
        return (
          <section
            key={g.date}
            className={`day-grouped-flight-list__group${
              collapsed ? " day-grouped-flight-list__group--collapsed" : ""
            }`}
            data-day={g.date}
          >
            <header
              className="day-grouped-flight-list__header"
              role="button"
              tabIndex={0}
              aria-expanded={!collapsed}
              onClick={() => toggleDay(g.date)}
              onKeyDown={(e) => {
                if (e.key === "Enter" || e.key === " ") {
                  e.preventDefault();
                  toggleDay(g.date);
                }
              }}
            >
              <span className="day-grouped-flight-list__weekday">
                {weekday.charAt(0).toUpperCase() + weekday.slice(1)}
              </span>
              <h3 className="day-grouped-flight-list__date">{dayMonth}</h3>
              <span
                className={`day-grouped-flight-list__chevron${
                  collapsed ? " day-grouped-flight-list__chevron--collapsed" : ""
                }`}
                aria-hidden="true"
              >
                <svg viewBox="0 0 12 8" width="12" height="8">
                  <path d="M1 1L6 6L11 1" stroke="currentColor" strokeWidth="1.5" fill="none" />
                </svg>
              </span>
            </header>
            {!collapsed && (
              <FlightList
                flights={g.flights}
                loading={false}
                direction="schedule"
                renderExpandedBody={renderScheduleBody}
                {...(onFlightClick ? { onFlightClick } : {})}
                {...(buyUrlFor ? { buyUrlFor } : {})}
                {...(resolvedInitialFlightId
                  ? { initialCurrentFlightId: resolvedInitialFlightId }
                  : {})}
              />
            )}
          </section>
        );
      })}
    </div>
  );
};
