/**
 * Schedule search results page for one-way and round-trip searches.
 *
 * Receives parsed route params, fetches flights via useScheduleSearch,
 * renders results with calendar navigation.
 * No SignalR -- schedule data is static.
 *
 * @module
 */

import type { FC } from "react";
import { useCallback, useEffect, useState } from "react";
import { ApiHttpError, ApiTimeoutError } from "@/shared/api/errors.js";
import { useNavigate } from "@modern-js/runtime/router";
import { useLocale } from "@/i18n/useLocale.js";
import { useTranslation } from "@/i18n/provider.js";
import { DayGroupedFlightList } from "./DayGroupedFlightList.js";
import {
  ScheduleColumnHeaders,
  type ScheduleSortMode,
} from "./ScheduleColumnHeaders.js";
import { WeekTabs } from "./WeekTabs.js";
import { PageLayout } from "@/ui/layout/PageLayout.js";
import { PageTabs } from "@/ui/layout/PageTabs.js";
import { ScheduleFilter } from "./ScheduleFilter.js";
import { SearchHistory } from "@/ui/layout/SearchHistory.js";
import { useDictionaries } from "@/shared/dictionaries/index.js";
import { useSearchHistory } from "@/shared/hooks/useSearchHistory.js";
import "./ScheduleSearchPage.scss";
import { JsonLdRenderer } from "@/shared/seo/json-ld.js";
import { useScheduleSearch } from "../hooks/useScheduleSearch.js";
import { buildScheduleUrl } from "../url.js";
import { buildFlightUrlParams } from "../../online-board/url.js";
import { buildDetailsRequestParam } from "@/shared/detailsRequestParam.js";
import {
  buildAeroflotSbSearchUrl,
  formatAeroflotRouteDate,
} from "@/shared/booking/aeroflot.js";
import { buildScheduleFlightListJsonLd } from "../json-ld.js";
import type { ScheduleParams } from "../url.js";
import type { IScheduleSearchRequest, ISimpleFlight } from "../types.js";
import type { IScheduleRouteDirectionParams } from "../types.js";

export interface ScheduleSearchPageProps {
  params: ScheduleParams & { type: "route" | "roundtrip" };
}

/**
 * Convert parsed schedule params to API search params.
 */
function toSearchRequest(
  direction: IScheduleRouteDirectionParams,
  attribute?: 1 | 2,
): IScheduleSearchRequest {
  const request: IScheduleSearchRequest = {
    departure: direction.departure,
    arrival: direction.arrival,
    dateFrom: formatApiDate(direction.dateFrom),
    dateTo: formatApiDate(direction.dateTo),
  };

  if (direction.timeFrom) request.timeFrom = direction.timeFrom;
  if (direction.timeTo) request.timeTo = direction.timeTo;
  if (direction.connections !== undefined) request.connections = direction.connections;
  if (attribute) request.attribute = attribute;

  return request;
}

/**
 * Convert yyyyMMdd to yyyy-MM-dd for the API.
 */
function formatApiDate(yyyymmdd: string): string {
  if (yyyymmdd.length !== 8) return yyyymmdd;
  return `${yyyymmdd.slice(0, 4)}-${yyyymmdd.slice(4, 6)}-${yyyymmdd.slice(6, 8)}`;
}

import { extractSimpleFlights } from "../extractSimpleFlights.js";

export const ScheduleSearchPage: FC<ScheduleSearchPageProps> = ({ params }) => {
  const navigate = useNavigate();
  const { t } = useTranslation();
  const { locale, language } = useLocale();

  const { dictionaries } = useDictionaries(language);
  const { add: addHistory } = useSearchHistory(language);
  const outbound = params.outbound;
  const inbound = params.type === "roundtrip" ? params.inbound : undefined;

  // Mirrors Angular `ScheduleNavigationService.toDetailsPage` →
  // `UrlBuilder.getDetailsPageUrl` for direct/multi-leg flights:
  //   /{lang}/schedule/{flightSegment}
  // where flightSegment is `{carrier}{flightNumber}-{yyyyMMdd}`. The
  // details route's catch-all parser skips 3-char airport codes, so
  // we don't need to insert them for the segment to resolve.
  const handleFlightClick = useCallback(
    (flight: ISimpleFlight) => {
      // Connecting itineraries are folded into a synthetic MultiLeg with
      // `_childFlightIds` carrying each leg's original flightId. We must
      // emit ALL of them in the URL so the details page fetches every
      // leg (matches Angular's `/{depAir}/{flight1}/{midAir}/{flight2}/{arrAir}`).
      const childIds = (flight as ISimpleFlight & {
        _childFlightIds?: typeof flight.flightId[];
      })._childFlightIds;
      const allLegs = flight.routeType === "Direct" ? [flight.leg] : flight.legs;
      const flightIds =
        childIds && childIds.length > 1
          ? childIds
          : [flight.flightId];
      const buildSeg = (id: typeof flight.flightId): string =>
        buildFlightUrlParams({
          carrier: id.carrier,
          flightNumber: id.flightNumber,
          ...(id.suffix ? { suffix: id.suffix } : {}),
          date: id.date,
        });
      // Interleave airport codes between flight segments so the URL
      // round-trips through `parseFlightSegments` (3-char codes are
      // skipped) AND keeps Angular-compatible structure for SEO.
      const parts: string[] = [];
      flightIds.forEach((id, i) => {
        const leg = allLegs[i] ?? allLegs[allLegs.length - 1];
        if (i === 0 && leg) parts.push(leg.departure.scheduled.airportCode);
        parts.push(buildSeg(id));
        if (leg) parts.push(leg.arrival.scheduled.airportCode);
      });
      const segment = parts.join("/");
      const requestParam = buildDetailsRequestParam({
        area: "schedule",
        kind: "route",
        departure: outbound.departure,
        arrival: outbound.arrival,
        dateFrom: outbound.dateFrom,
        dateTo: outbound.dateTo,
        ...(outbound.timeFrom && outbound.timeTo
          ? { timeFrom: outbound.timeFrom, timeTo: outbound.timeTo }
          : {}),
        ...(outbound.connections !== undefined
          ? { connections: outbound.connections }
          : {}),
        ...(inbound
          ? {
              returnTrip: {
                departure: inbound.departure,
                arrival: inbound.arrival,
                dateFrom: inbound.dateFrom,
                dateTo: inbound.dateTo,
                ...(inbound.timeFrom && inbound.timeTo
                  ? { timeFrom: inbound.timeFrom, timeTo: inbound.timeTo }
                  : {}),
                ...(inbound.connections !== undefined
                  ? { connections: inbound.connections }
                  : {}),
              },
            }
          : {}),
      });
      void navigate(`/${locale}/schedule/${segment}?request=${encodeURIComponent(requestParam)}`);
    },
    [locale, navigate, outbound, inbound],
  );

  // Builds the Aeroflot booking URL for a flight. Angular's
  // BuyTicketLogic hardcodes the SB path to ru-ru for every UI locale.
  // Returns null when the flight lacks the data we need to assemble it.
  const buyUrlFor = useCallback(
    (flight: ISimpleFlight): string | null => {
      const legs = flight.routeType === "Direct" ? [flight.leg] : flight.legs;
      const firstLeg = legs[0];
      const lastLeg = legs[legs.length - 1];
      if (!firstLeg || !lastLeg) return null;
      const dep = firstLeg.departure.scheduled.airportCode;
      const arr = lastLeg.arrival.scheduled.airportCode;
      const date = formatAeroflotRouteDate(
        firstLeg.departure.times.scheduledDeparture.local ||
          firstLeg.departure.times.scheduledDeparture.utc,
      );
      return buildAeroflotSbSearchUrl({ departure: dep, arrival: arr, date });
    },
    [],
  );

  // Round-trip schedules render only one direction at a time, with a
  // `Туда / Обратно` switcher in the sticky header — matches Angular's
  // `schedule-direction-switch` (one-of-two button group). Default to
  // outbound; selection is local UI state, not URL-bound.
  const [direction, setDirection] = useState<"outbound" | "inbound">("outbound");
  // Lifted here so `ScheduleColumnHeaders` (inside stickyContent) and
  // `DayGroupedFlightList` (main content) stay in sync. Mirrors Angular's
  // `schedule-search-view` where both consume the same `sortBy` signal.
  const [sortMode, setSortMode] = useState<ScheduleSortMode>("none");
  const activeDirection = direction === "inbound" && inbound ? inbound : outbound;
  const activeKind: "outbound" | "inbound" =
    direction === "inbound" && inbound ? "inbound" : "outbound";

  // Persist this search into the `Ранее искали` sidebar history. The hook
  // dedupes by URL so re-renders / week-tab clicks won't bloat storage.
  useEffect(() => {
    const url = `/${locale}/${buildScheduleUrl(params)}`;
    addHistory({
      type: "schedule-route",
      url,
      label: `${outbound.departure} — ${outbound.arrival}`,
      params: {
        departure: outbound.departure,
        arrival: outbound.arrival,
        dateFrom: outbound.dateFrom,
        dateTo: outbound.dateTo,
        ...(outbound.connections === 0 ? { directOnly: true } : {}),
        ...(inbound
          ? {
              inboundDateFrom: inbound.dateFrom,
              inboundDateTo: inbound.dateTo,
            }
          : {}),
      },
    });
    // Only re-record when the URL changes — otherwise React StrictMode
    // double-effects + dictionary reloads would bombard the hook.
  }, [
    locale,
    outbound.departure,
    outbound.arrival,
    outbound.dateFrom,
    outbound.dateTo,
    outbound.connections,
    inbound?.dateFrom,
    inbound?.dateTo,
  ]);

  // Resolve IATA codes to human city/airport names so the heading reads
  // 'Маршрут: Шереметьево - Санкт-Петербург' instead of 'SVO - LED'.
  // City wins over airport when the code resolves to both (Angular
  // parity — LED is both codes; arrivals render the city name).
  const describeStation = (code?: string): string => {
    if (!code || !dictionaries) return code ?? "";
    const upper = code.toUpperCase();
    const city = dictionaries.cityByCode.get(upper);
    if (city) return city.name;
    const airport = dictionaries.airportByCode.get(upper);
    if (airport) return airport.name;
    return code;
  };
  const depName = describeStation(outbound.departure);
  const arrName = describeStation(outbound.arrival);
  // Angular's schedule pages render the heading as
  // `Расписание по маршруту: Москва - Самара` (SCHEDULE.SCHEDULE-BY-ROUTE);
  // the onlineboard variant uses the shorter `Маршрут:` (BOARD.ROUTE-TEXT).
  const routeHeading = `${t("SCHEDULE.SCHEDULE-BY-ROUTE")}: ${depName} - ${arrName}`;

  // Fetch outbound flights
  const outboundRequest = toSearchRequest(outbound);
  const { flights: outboundFlights, loading: outboundLoading, error: outboundError, refresh, cancel: cancelOutbound } =
    useScheduleSearch(outboundRequest);

  // Fetch inbound flights (if round-trip)
  const inboundRequest = inbound ? toSearchRequest(inbound, 2) : outboundRequest;
  const {
    flights: inboundFlights,
    loading: inboundLoading,
    cancel: cancelInbound,
  } = useScheduleSearch(inboundRequest);

  const isLoading = outboundLoading || (inbound ? inboundLoading : false);

  // §4.1.12 — cancel both directions at once
  const cancel = useCallback(() => {
    cancelOutbound();
    cancelInbound();
  }, [cancelOutbound, cancelInbound]);

  // §4.1.12 — Escape cancels while loader is showing
  useEffect(() => {
    if (!isLoading) return;
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === "Escape") cancel();
    };
    document.addEventListener("keydown", handleKeyDown);
    return () => document.removeEventListener("keydown", handleKeyDown);
  }, [isLoading, cancel]);

  // §4.1.11.1 — per-status error message key
  const errorMessageKey = (() => {
    if (!outboundError) return null;
    if (outboundError instanceof ApiTimeoutError) return "BOARD.ERROR-TIMEOUT";
    if (outboundError instanceof ApiHttpError) {
      return outboundError.status >= 500 ? "BOARD.ERROR-5XX" : "BOARD.ERROR-4XX";
    }
    return "BOARD.LOAD-FAILED";
  })();

  /** Navigate to a different week (Monday → Sunday range). Updates the
   *  active direction's params and preserves the other direction. */
  const handleWeekChange = useCallback(
    (mondayYmd: string) => {
      const monday = new Date(mondayYmd);
      const sunday = new Date(monday);
      sunday.setDate(monday.getDate() + 6);
      const ymd = (d: Date): string => {
        const y = d.getFullYear();
        const m = String(d.getMonth() + 1).padStart(2, "0");
        const day = String(d.getDate()).padStart(2, "0");
        return `${y}${m}${day}`;
      };
      const next = { dateFrom: ymd(monday), dateTo: ymd(sunday) };
      let newParams: ScheduleParams;
      if (inbound && activeKind === "inbound") {
        newParams = {
          type: "roundtrip",
          outbound,
          inbound: { ...inbound, ...next },
        };
      } else if (inbound) {
        newParams = {
          type: "roundtrip",
          outbound: { ...outbound, ...next },
          inbound,
        };
      } else {
        newParams = { type: "route", outbound: { ...outbound, ...next } };
      }
      const url = buildScheduleUrl(newParams);
      void navigate(`/${locale}/${url}`);
    },
    [navigate, locale, outbound, inbound, activeKind],
  );

  /** Convert the active direction's `dateFrom` (yyyyMMdd) → Monday
   *  yyyy-MM-dd so the WeekTabs highlights the right tab. */
  const selectedMonday = (() => {
    const f = activeDirection.dateFrom;
    if (!f || f.length !== 8) return "";
    const date = new Date(
      parseInt(f.slice(0, 4), 10),
      parseInt(f.slice(4, 6), 10) - 1,
      parseInt(f.slice(6, 8), 10),
    );
    const day = date.getDay();
    const diff = (day + 6) % 7;
    date.setDate(date.getDate() - diff);
    const y = date.getFullYear();
    const m = String(date.getMonth() + 1).padStart(2, "0");
    const d = String(date.getDate()).padStart(2, "0");
    return `${y}-${m}-${d}`;
  })();

  const outboundSimple = extractSimpleFlights(outboundFlights);
  const inboundSimple = inbound ? extractSimpleFlights(inboundFlights) : [];

  // JSON-LD
  const searchDescription = `Schedule ${outbound.departure} to ${outbound.arrival}`;
  const jsonLd = outboundSimple.length > 0
    ? buildScheduleFlightListJsonLd(outboundSimple, searchDescription)
    : undefined;

  return (
    <div
      className="schedule-search"
      data-testid="schedule-search"
      // §4.1.11 — block filter/tabs/breadcrumbs while searching
      data-searching={isLoading ? "true" : undefined}
    >
      {jsonLd && <JsonLdRenderer data={jsonLd} />}
      <PageLayout
        headerLeft={<PageTabs viewType="schedule" />}
        title={
          <h1 className="text--white page-title">
            {routeHeading}
          </h1>
        }
        breadcrumbs={[
          // Angular stops the trail at 'Расписание рейсов'; the route
          // heading already lives in the h1, don't repeat it as a crumb.
          { label: t("SCHEDULE.TITLE"), url: `/${locale}/schedule` },
        ]}
        contentLeft={
          <>
            <ScheduleFilter
              initialDeparture={outbound.departure}
              initialArrival={outbound.arrival}
              initialDateFrom={outbound.dateFrom}
              initialDateTo={outbound.dateTo}
              {...(outbound.timeFrom ? { initialTimeFrom: outbound.timeFrom } : {})}
              {...(outbound.timeTo ? { initialTimeTo: outbound.timeTo } : {})}
              {...(inbound?.dateFrom ? { initialReturnDateFrom: inbound.dateFrom } : {})}
              {...(inbound?.dateTo ? { initialReturnDateTo: inbound.dateTo } : {})}
              {...(inbound?.timeFrom ? { initialReturnTimeFrom: inbound.timeFrom } : {})}
              {...(inbound?.timeTo ? { initialReturnTimeTo: inbound.timeTo } : {})}
              initialDirectOnly={outbound.connections === 0}
              initialReturnFlights={Boolean(inbound)}
            />
            <SearchHistory />
          </>
        }
        stickyContent={
          <>
            <WeekTabs
              selectedMonday={selectedMonday}
              onNavigate={handleWeekChange}
            />
            {inbound && (
              <div
                className="schedule-direction-switch"
                data-testid="schedule-direction-switch"
                role="tablist"
              >
                <button
                  type="button"
                  className={`schedule-direction-switch__btn${
                    activeKind === "outbound"
                      ? " schedule-direction-switch__btn--active"
                      : ""
                  }`}
                  onClick={() => setDirection("outbound")}
                  role="tab"
                  aria-selected={activeKind === "outbound"}
                  data-testid="direction-switch-outbound"
                  title={t("SHARED.DIRECT_FLIGHTS")}
                >
                  <svg
                    className="schedule-direction-switch__icon"
                    width="16"
                    height="16"
                    aria-hidden="true"
                  >
                    <use xlinkHref="/assets/img/sprite.svg#plane" />
                  </svg>
                  {t("SHARED.DIRECT_FLIGHTS")}
                </button>
                <button
                  type="button"
                  className={`schedule-direction-switch__btn${
                    activeKind === "inbound"
                      ? " schedule-direction-switch__btn--active"
                      : ""
                  }`}
                  onClick={() => setDirection("inbound")}
                  role="tab"
                  aria-selected={activeKind === "inbound"}
                  data-testid="direction-switch-inbound"
                  title={t("SHARED.RETURN_FLIGHTS")}
                >
                  <svg
                    className="schedule-direction-switch__icon schedule-direction-switch__icon--flipped"
                    width="16"
                    height="16"
                    aria-hidden="true"
                  >
                    <use xlinkHref="/assets/img/sprite.svg#plane" />
                  </svg>
                  {t("SHARED.RETURN_FLIGHTS")}
                </button>
              </div>
            )}
            {/* Column headers live inside the sticky card — same slot as
                Angular's `schedule-search-result-header` child of the
                `<card sticky-content>` — so there is zero gap between
                the week tabs and the table header. */}
            <ScheduleColumnHeaders
              sortMode={sortMode}
              onSortChange={setSortMode}
            />
          </>
        }
      >
        {/* §4.1.12 — cancel button visible while loading */}
        {isLoading && (
          <div className="schedule-search__loader-bar" data-testid="loader-bar">
            <button
              type="button"
              className="schedule-search__cancel-btn"
              data-testid="cancel-search-btn"
              onClick={cancel}
            >
              {t("SHARED.SEARCH-CANCEL")}
            </button>
          </div>
        )}

        {/* §4.1.11.1 — per-status error message */}
        {outboundError && (
          <section className="frame" data-testid="search-error" role="alert">
            <div className="schedule-search__error">
              <p>{t(errorMessageKey ?? "BOARD.LOAD-FAILED")}</p>
              <button type="button" onClick={refresh}>{t("SHARED.RETRY")}</button>
            </div>
          </section>
        )}

        <section className="frame">
          {activeKind === "outbound" ? (
            <div className="schedule-search__outbound" data-testid="outbound-results">
              <DayGroupedFlightList
                flights={outboundSimple}
                loading={outboundLoading}
                onFlightClick={handleFlightClick}
                buyUrlFor={buyUrlFor}
                sortMode={sortMode}
                onSortChange={setSortMode}
                hideColumnHeaders
              />
            </div>
          ) : inbound ? (
            <div className="schedule-search__inbound" data-testid="inbound-results">
              <DayGroupedFlightList
                flights={inboundSimple}
                loading={inboundLoading}
                onFlightClick={handleFlightClick}
                buyUrlFor={buyUrlFor}
                sortMode={sortMode}
                onSortChange={setSortMode}
                hideColumnHeaders
              />
            </div>
          ) : null}

          {/* Footer note mirrors Angular `page-footer-notes` — a
              blue-extra-light bar attached to the flight-list frame
              with the `*` sort-note + localized system-time note. */}
          {!outboundError && !outboundLoading && (
            <div className="page-footer-note" data-testid="footer-notes">
              <div className="page-footer-note--container">
                <div className="line line1">
                  <div className="sort-note">*</div>
                  <div>{t("SCHEDULE.NOTE-LINE1")}</div>
                </div>
              </div>
            </div>
          )}
        </section>
      </PageLayout>
    </div>
  );
};
