/**
 * Schedule-specific filter sidebar.
 *
 * Mirrors Angular's `schedule-filter` (one-way) layout: city pickers,
 * date-range, departure-time slider, two checkboxes
 * (`Только прямые рейсы`, `Показать обратные рейсы`), and the
 * `Показать расписание` submit button. No flight-number tab — schedule
 * search is route-only.
 */

import { type FC, useState, useCallback, useRef, useEffect, useMemo, type FormEvent } from "react";
import { useNavigate } from "@modern-js/runtime/router";
import { Calendar } from "primereact/calendar";
import { Slider, type SliderChangeEvent } from "primereact/slider";
import { useTranslation } from "@/i18n/provider.js";
import { useLocale } from "@/i18n/useLocale.js";
import { CityAutocomplete, SwapCityButton } from "@/ui/city-autocomplete/index.js";
import { useDictionaries } from "@/shared/dictionaries/index.js";
import { useScheduleCalendar } from "../hooks/useScheduleCalendar.js";
import type { IScheduleCalendarParams } from "../types.js";
import { buildScheduleUrl } from "../url.js";
import { setScheduleFilter } from "@/shared/state/crossSectionNavigation.js";
import type { ScheduleParams } from "../url.js";
import { formatScheduleDateRangeWithCurrentWeek } from "../dateLabels.js";
import { scheduleWindowBounds } from "@/shared/dateWindow.js";
import "./ScheduleFilter.scss";

/**
 * Snap a date to the Mon-Sun week containing it (Angular parity:
 * the schedule filter selects whole weeks only). Returned tuple is
 * [Monday 00:00, Sunday 23:59:59.999].
 */
function snapToWeek(date: Date): [Date, Date] {
  const d = new Date(date);
  d.setHours(0, 0, 0, 0);
  // JS getDay: Sun=0, Mon=1 … Sat=6. Treat Mon as start-of-week.
  const day = d.getDay();
  const offsetToMonday = day === 0 ? -6 : 1 - day;
  const monday = new Date(d);
  monday.setDate(d.getDate() + offsetToMonday);
  const sunday = new Date(monday);
  sunday.setDate(monday.getDate() + 6);
  sunday.setHours(23, 59, 59, 999);
  return [monday, sunday];
}

function minutesToTime(minutes: number): string {
  const h = Math.floor(minutes / 60);
  const m = minutes % 60;
  return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
}

function minutesToHhmm(minutes: number): string {
  const h = Math.floor(minutes / 60);
  const m = minutes % 60;
  return `${String(h).padStart(2, "0")}${String(m).padStart(2, "0")}`;
}

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

function yyyymmddToDate(yyyymmdd?: string): Date | null {
  if (!yyyymmdd || yyyymmdd.length !== 8) return null;
  const y = parseInt(yyyymmdd.slice(0, 4), 10);
  const m = parseInt(yyyymmdd.slice(4, 6), 10) - 1;
  const d = parseInt(yyyymmdd.slice(6, 8), 10);
  return new Date(y, m, d);
}

/**
 * Format a Date as `yyyy-MM-dd` — the shape returned by the schedule
 * `/calendar` API (`bitmaskToDates` in api.ts). Used only for the
 * disabled-dates set lookup; the URL builder uses the dashless
 * `dateToYyyymmdd` form above.
 */
function dateToIsoYmd(value: Date): string {
  const y = value.getFullYear().toString();
  const m = (value.getMonth() + 1).toString().padStart(2, "0");
  const d = value.getDate().toString().padStart(2, "0");
  return `${y}-${m}-${d}`;
}

/** Inverse of the API's enabled-days list: every date inside
 *  [minDate, maxDate] that isn't in `availableYmd` (yyyy-MM-dd) gets
 *  disabled so PrimeReact's Calendar greys them out (TIRREDESIGN-12). */
function computeDisabledDates(
  availableYmd: string[],
  minDate: Date,
  maxDate: Date,
): Date[] {
  const available = new Set(availableYmd);
  const out: Date[] = [];
  const cursor = new Date(minDate);
  cursor.setHours(0, 0, 0, 0);
  while (cursor.getTime() <= maxDate.getTime()) {
    const ymd = dateToIsoYmd(cursor);
    if (!available.has(ymd)) {
      out.push(new Date(cursor));
    }
    cursor.setDate(cursor.getDate() + 1);
  }
  return out;
}

export interface ScheduleFilterProps {
  initialDeparture?: string;
  initialArrival?: string;
  initialDateFrom?: string;
  initialDateTo?: string;
  initialTimeFrom?: string;
  initialTimeTo?: string;
  initialReturnDateFrom?: string;
  initialReturnDateTo?: string;
  initialReturnTimeFrom?: string;
  initialReturnTimeTo?: string;
  initialDirectOnly?: boolean;
  initialReturnFlights?: boolean;
}

function hhmmToMinutes(value: string | undefined, fallback: number): number {
  if (!value || value.length !== 4) return fallback;
  const h = Number(value.slice(0, 2));
  const m = Number(value.slice(2, 4));
  if (Number.isNaN(h) || Number.isNaN(m)) return fallback;
  return h * 60 + m;
}

// Mirrors Angular AppSettings.scheduleSearchFrom (1 day back) and
// scheduleSearchTo (330 days forward). Keeps the schedule-filter calendar
// to the same range Angular uses via `settings.scheduleMinDate`/`Max`.
function getScheduleMinDate(): Date {
  return scheduleWindowBounds()[0];
}

function getScheduleMaxDate(): Date {
  return scheduleWindowBounds()[1];
}

export const ScheduleFilter: FC<ScheduleFilterProps> = ({
  initialDeparture,
  initialArrival,
  initialDateFrom,
  initialDateTo,
  initialTimeFrom,
  initialTimeTo,
  initialReturnDateFrom,
  initialReturnDateTo,
  initialReturnTimeFrom,
  initialReturnTimeTo,
  initialDirectOnly = false,
  initialReturnFlights = false,
}) => {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const { locale, language } = useLocale();
  const { dictionaries } = useDictionaries(language);

  const [departure, setDeparture] = useState(initialDeparture ?? "");
  const [arrival, setArrival] = useState(initialArrival ?? "");
  const initFrom = yyyymmddToDate(initialDateFrom);
  const initTo = yyyymmddToDate(initialDateTo);
  const [dateRange, setDateRange] = useState<(Date | null)[]>(
    initFrom && initTo ? [initFrom, initTo] : [null, null],
  );
  const [timeRange, setTimeRange] = useState<[number, number]>([
    hhmmToMinutes(initialTimeFrom, 0),
    hhmmToMinutes(initialTimeTo, 1440),
  ]);
  const [directOnly, setDirectOnly] = useState(initialDirectOnly);
  const [returnFlights, setReturnFlights] = useState(initialReturnFlights);
  const initRetFrom = yyyymmddToDate(initialReturnDateFrom);
  const initRetTo = yyyymmddToDate(initialReturnDateTo);
  const [returnDateRange, setReturnDateRange] = useState<(Date | null)[]>(
    initRetFrom && initRetTo ? [initRetFrom, initRetTo] : [null, null],
  );
  const [returnTimeRange, setReturnTimeRange] = useState<[number, number]>([
    hhmmToMinutes(initialReturnTimeFrom, 0),
    hhmmToMinutes(initialReturnTimeTo, 1440),
  ]);
  // Mirrors Angular ScheduleFilterValidationService: a single inline
  // error shown below the arrival input when the user submits with
  // departure === arrival. Cleared whenever either city changes.
  const [sameCitiesError, setSameCitiesError] = useState<string | null>(null);
  // TZ §4.1.9.4: outbound range must be ≤ 7 days.
  const [rangeError, setRangeError] = useState<string | null>(null);
  // TZ §4.1.9.4: return week must not start before outbound dateTo.
  const [returnBeforeOutboundError, setReturnBeforeOutboundError] = useState<string | null>(null);

  const scheduleMinDate = useRef(getScheduleMinDate()).current;
  const scheduleMaxDate = useRef(getScheduleMaxDate()).current;
  const scheduleCalendarBaseDate = useMemo(
    () => dateToIsoYmd(scheduleMinDate),
    [scheduleMinDate],
  );

  // TIRREDESIGN-12: fetch the 31-day operating-days bitmask for the
  // selected route so PrimeReact greys out days without flights. The
  // outbound calendar looks at (departure, arrival); the inbound swaps
  // them. Both queries are skipped until both city codes are set so we
  // don't hit the API with half-filled inputs.
  const scheduleCalendarParams = useMemo<IScheduleCalendarParams | null>(() => {
    const dep = departure.trim().toUpperCase();
    const arr = arrival.trim().toUpperCase();
    if (!dep || !arr || dep === arr) return null;
    return {
      date: scheduleCalendarBaseDate,
      departure: dep,
      arrival: arr,
      connections: !directOnly,
    };
  }, [departure, arrival, directOnly, scheduleCalendarBaseDate]);

  const returnCalendarParams = useMemo<IScheduleCalendarParams | null>(() => {
    if (!returnFlights) return null;
    const dep = departure.trim().toUpperCase();
    const arr = arrival.trim().toUpperCase();
    if (!dep || !arr || dep === arr) return null;
    return {
      date: scheduleCalendarBaseDate,
      departure: arr,
      arrival: dep,
      connections: !directOnly,
    };
  }, [departure, arrival, directOnly, returnFlights, scheduleCalendarBaseDate]);

  const {
    days: scheduleAvailableDays,
    loaded: scheduleCalendarLoaded,
  } = useScheduleCalendar(scheduleCalendarParams);
  const {
    days: returnAvailableDays,
    loaded: returnCalendarLoaded,
  } = useScheduleCalendar(returnCalendarParams);

  const scheduleDisabledDates = useMemo(
    () =>
      !scheduleCalendarLoaded
        ? []
        : computeDisabledDates(scheduleAvailableDays, scheduleMinDate, scheduleMaxDate),
    [scheduleAvailableDays, scheduleCalendarLoaded, scheduleMinDate, scheduleMaxDate],
  );
  const returnDisabledDates = useMemo(
    () =>
      !returnCalendarLoaded
        ? []
        : computeDisabledDates(returnAvailableDays, scheduleMinDate, scheduleMaxDate),
    [returnAvailableDays, returnCalendarLoaded, scheduleMinDate, scheduleMaxDate],
  );

  // §4.1.11 — submit button locked for 30 seconds after each search.
  // The 30-second constant is intentionally hardcoded (not configurable).
  const [submitLockedUntil, setSubmitLockedUntil] = useState(0);
  const [nowTs, setNowTs] = useState(() => Date.now());
  useEffect(() => {
    if (submitLockedUntil === 0 || nowTs >= submitLockedUntil) return;
    const id = setTimeout(() => setNowTs(Date.now()), 1000);
    return () => clearTimeout(id);
  }, [submitLockedUntil, nowTs]);
  const isSubmitLocked = useMemo(
    () => submitLockedUntil > 0 && nowTs < submitLockedUntil,
    [submitLockedUntil, nowTs],
  );

  // TZ §4.1.9.4 Table 16: when the outbound range moves forward such
  // that the already-chosen return starts before the new outbound
  // dateTo, blank the return picker and any coupled error so the user
  // isn't left with an invalid combo. Intentionally does not fire when
  // outbound is cleared — that's a separate "user actively cleared" flow
  // handled on the outbound X button (see the clear handler below).
  useEffect(() => {
    if (!returnFlights) return;
    const [, outTo] = dateRange;
    const [retFrom] = returnDateRange;
    if (!retFrom || !outTo) return;
    const retFromDay = new Date(retFrom);
    retFromDay.setHours(0, 0, 0, 0);
    const outToDay = new Date(outTo);
    outToDay.setHours(0, 0, 0, 0);
    if (retFromDay.getTime() < outToDay.getTime()) {
      setReturnDateRange([null, null]);
      setReturnBeforeOutboundError(null);
    }
  }, [dateRange, returnFlights, returnDateRange]);

  // Swap the Calendar input's displayed text to "Текущая неделя" per
  // TZ §4.1.9 Table 14 when the selected range equals Mon-Sun of the
  // current week. Uses inputRef + useEffect to override PrimeReact's
  // own dd.mm.yy rendering without touching the state value.
  const dateRangeInputRef = useRef<HTMLInputElement>(null);
  // Refs to the PrimeReact Calendar component instances so we can call
  // `hide()` after a single click commits the snap-to-week range
  // (matches Angular which closes the picker on selection).
  const outboundCalendarRef = useRef<Calendar | null>(null);
  const returnCalendarRef = useRef<Calendar | null>(null);

  // Single-click handler for both outbound and return pickers: snap
  // the clicked day to its Mon-Sun calendar week, commit as a range,
  // and dismiss the panel. Angular parity (TZ §4.1.9.4 — the schedule
  // filter is week-granular).
  const onOutboundSelect = useCallback((e: { value: unknown }): void => {
    const v = e.value;
    if (!v) return;
    const picked = Array.isArray(v) ? (v[0] ?? v[1]) : v;
    if (!(picked instanceof Date)) return;
    const [from, to] = snapToWeek(picked);
    setDateRange([from, to]);
    if (rangeError) setRangeError(null);
    outboundCalendarRef.current?.hide();
  }, [rangeError]);

  const onReturnSelect = useCallback((e: { value: unknown }): void => {
    const v = e.value;
    if (!v) return;
    const picked = Array.isArray(v) ? (v[0] ?? v[1]) : v;
    if (!(picked instanceof Date)) return;
    const [from, to] = snapToWeek(picked);
    setReturnDateRange([from, to]);
    setReturnBeforeOutboundError(null);
    returnCalendarRef.current?.hide();
  }, []);
  useEffect(() => {
    const [from, to] = dateRange;
    if (!dateRangeInputRef.current || !from || !to) return;
    const label = formatScheduleDateRangeWithCurrentWeek(from, to, t);
    const currentWeekLabel = t("SCHEDULE.CURRENT-WEEK");
    if (label === currentWeekLabel) {
      dateRangeInputRef.current.value = currentWeekLabel;
    }
  }, [dateRange, t]);

  const returnDateRangeInputRef = useRef<HTMLInputElement>(null);

  // PrimeReact's `readOnlyInput=true` Calendar suppresses the auto-show
  // on input click; only the trigger icon opens the panel. Forward
  // input clicks to the icon so users can tap the field itself to open
  // the picker (matching Angular's calendar behaviour). Same logic
  // applied to both outbound and return date pickers.
  useEffect(() => {
    const cleanups: Array<() => void> = [];
    for (const ref of [dateRangeInputRef, returnDateRangeInputRef]) {
      const input = ref.current;
      if (!input) continue;
      const handler = (): void => {
        const wrapper = input.closest(".p-calendar");
        const trigger = wrapper?.querySelector<HTMLButtonElement>(
          ".p-datepicker-trigger",
        );
        trigger?.click();
      };
      input.addEventListener("click", handler);
      input.style.cursor = "pointer";
      cleanups.push(() => input.removeEventListener("click", handler));
    }
    return () => cleanups.forEach((c) => c());
  }, []);

  const handleSwap = useCallback(() => {
    setDeparture(arrival);
    setArrival(departure);
  }, [arrival, departure]);

  const handleSubmit = useCallback(
    (e: FormEvent<HTMLFormElement>) => {
      e.preventDefault();
      if (isSubmitLocked) return;
      const dep = departure.trim().toUpperCase();
      const arr = arrival.trim().toUpperCase();
      if (!dep || !arr) return;

      // Angular rejects same-code routes with SHARED.ARRIVAL-EQUALS-DEPARTURE-ERROR.
      if (dep === arr) {
        setSameCitiesError("SHARED.ARRIVAL-EQUALS-DEPARTURE-ERROR");
        return;
      }
      setSameCitiesError(null);

      // TZ §4.1.9.4: outbound range must be ≤ 7 days.
      if (dateRange[0] && dateRange[1]) {
        const diffMs = dateRange[1].getTime() - dateRange[0].getTime();
        const diffDays = Math.round(diffMs / 86_400_000);
        if (diffDays > 7) {
          setRangeError("SHARED.SCHEDULE-RANGE-MAX-7-DAYS");
          return;
        }
      }
      setRangeError(null);

      // TZ §4.1.9.4: return week must not start before outbound dateTo.
      if (returnFlights && returnDateRange[0] && dateRange[1]) {
        const retFrom = returnDateRange[0];
        const outTo = dateRange[1];
        // retFrom must be >= outTo (same week or later)
        const retFromDay = new Date(retFrom);
        retFromDay.setHours(0, 0, 0, 0);
        const outToDay = new Date(outTo);
        outToDay.setHours(0, 0, 0, 0);
        if (retFromDay.getTime() < outToDay.getTime()) {
          setReturnBeforeOutboundError("SHARED.RETURN-DATE-BEFORE-OUTBOUND");
          return;
        }
      }
      setReturnBeforeOutboundError(null);

      // Default to current week if no range provided.
      const now = new Date();
      const day = now.getDay();
      const monday = new Date(now);
      monday.setDate(now.getDate() - ((day + 6) % 7));
      const sunday = new Date(monday);
      sunday.setDate(monday.getDate() + 6);

      const from = dateRange[0] ?? monday;
      const to = dateRange[1] ?? sunday;

      const outbound = {
        departure: dep,
        arrival: arr,
        dateFrom: dateToYyyymmdd(from),
        dateTo: dateToYyyymmdd(to),
        // Only include time range when it's been narrowed from the full
        // day — Angular spreads `...this.timeRange` which is undefined
        // until the user interacts with the slider, so the URL stays
        // clean for the default 00:00–24:00 case.
        ...(timeRange[0] !== 0 || timeRange[1] !== 1440
          ? {
              timeFrom: minutesToHhmm(timeRange[0]),
              timeTo: minutesToHhmm(timeRange[1]),
            }
          : {}),
        ...(directOnly ? { connections: 0 } : {}),
      };

      let params: ScheduleParams;
      if (returnFlights) {
        // Default return range to week-after-outbound if the user
        // turned the checkbox on but hasn't picked dates.
        const retFromDate = returnDateRange[0] ?? (() => {
          const d = new Date(to);
          d.setDate(d.getDate() + 1);
          return d;
        })();
        const retToDate = returnDateRange[1] ?? (() => {
          const d = new Date(to);
          d.setDate(d.getDate() + 7);
          return d;
        })();
        const inbound = {
          departure: arr,
          arrival: dep,
          dateFrom: dateToYyyymmdd(retFromDate),
          dateTo: dateToYyyymmdd(retToDate),
          ...(returnTimeRange[0] !== 0 || returnTimeRange[1] !== 1440
            ? {
                timeFrom: minutesToHhmm(returnTimeRange[0]),
                timeTo: minutesToHhmm(returnTimeRange[1]),
              }
            : {}),
          ...(directOnly ? { connections: 0 } : {}),
        };
        params = { type: "roundtrip", outbound, inbound };
      } else {
        params = { type: "route", outbound };
      }
      const url = buildScheduleUrl(params);
      // TZ §4.1.8: persist Schedule filter into the cross-section
      // store so a subsequent visit to Board/Schedule within the same
      // session can read/project it.
      setScheduleFilter({
        mode: "route",
        ...(dep ? { departure: dep } : {}),
        ...(arr ? { arrival: arr } : {}),
        dateFrom: outbound.dateFrom,
        dateTo: outbound.dateTo,
        timeFrom: outbound.timeFrom ?? "0000",
        timeTo: outbound.timeTo ?? "2400",
        onlyDirect: directOnly,
        showReturn: returnFlights,
        ...(returnFlights && params.type === "roundtrip"
          ? {
              returnDateFrom: params.inbound.dateFrom,
              returnDateTo: params.inbound.dateTo,
              returnTimeFrom: params.inbound.timeFrom ?? "0000",
              returnTimeTo: params.inbound.timeTo ?? "2400",
            }
          : {}),
        searchExecuted: true,
      });
      // Lock submit for 30 seconds (§4.1.11 — hardcoded, not configurable)
      setSubmitLockedUntil(Date.now() + 30_000);
      setNowTs(Date.now());
      void navigate(`/${locale}/${url}`);
    },
    [
      departure,
      arrival,
      dateRange,
      timeRange,
      directOnly,
      returnFlights,
      returnDateRange,
      returnTimeRange,
      navigate,
      locale,
      isSubmitLocked,
    ],
  );

  return (
    <div className="schedule-filter" data-testid="schedule-filter">
      <section className="frame schedule-filter__frame">
        <form
          onSubmit={handleSubmit}
          className="schedule-filter__form"
          data-testid="search-form"
          aria-label={t("SCHEDULE.TITLE")}
        >
          <div className="filter-content">
            <CityAutocomplete
              label={t("SHARED.DEPARTURE_CITY")}
              placeholder={t("SHARED.CITY_PLACEHOLDER")}
              value={departure}
              onChange={(code) => {
                setDeparture(code);
                if (sameCitiesError) setSameCitiesError(null);
              }}
              dictionaries={dictionaries}
              testIdPrefix="schedule-departure"
            />

            <SwapCityButton
              onClick={handleSwap}
              testId="swap-cities-button"
            />

            <CityAutocomplete
              label={t("SHARED.ARRIVAL_CITY")}
              placeholder={t("SHARED.CITY_PLACEHOLDER")}
              value={arrival}
              onChange={(code) => {
                setArrival(code);
                if (sameCitiesError) setSameCitiesError(null);
              }}
              dictionaries={dictionaries}
              testIdPrefix="schedule-arrival"
            />
            {sameCitiesError && (
              <div
                className="validation-tooltip"
                data-testid="schedule-same-cities-error"
                role="alert"
              >
                {t(sameCitiesError)}
              </div>
            )}

            <div className="calendar">
              <label className="label--filter">
                {t("SHARED.SCHEDULES_DATE")}
              </label>
              <div className="calendar-input-wrapper">
                <Calendar
                  ref={outboundCalendarRef}
                  value={dateRange}
                  onChange={onOutboundSelect}
                  selectionMode="range"
                  minDate={scheduleMinDate}
                  maxDate={scheduleMaxDate}
                  disabledDates={scheduleDisabledDates}
                  selectOtherMonths
                  dateFormat="dd.mm.yy"
                  placeholder={`${t("SHARED.DATE_FORMAT")} - ${t("SHARED.DATE_FORMAT")}`}
                  showIcon
                  className="input--filter"
                  data-testid="schedule-date-input"
                  inputId="schedule-date-input"
                  inputRef={dateRangeInputRef}
                  readOnlyInput
                />
                {(dateRange[0] || dateRange[1]) && (
                  <button
                    type="button"
                    className="calendar-clear-btn"
                    aria-label={t("SHARED.A11Y-CLEAR")}
                    data-testid="schedule-date-clear"
                    onClick={() => {
                      // TZ §4.1.9.4 Table 16: clearing outbound also
                      // clears return (return picker is only meaningful
                      // relative to the outbound range).
                      setDateRange([null, null]);
                      setRangeError(null);
                      setReturnDateRange([null, null]);
                      setReturnBeforeOutboundError(null);
                    }}
                  >
                    &times;
                  </button>
                )}
              </div>
              {rangeError && (
                <div
                  className="validation-tooltip"
                  data-testid="schedule-range-error"
                  role="alert"
                >
                  {t(rangeError)}
                </div>
              )}
            </div>

            <div className="wrapper--time-selector compact-view" data-testid="time-selector">
              <div className="time-selector__label-value">
                <div className="time-selector__label">
                  {t("SHARED.DEPARTURE_TIME")}
                </div>
                <div className="time-selector__value">
                  {minutesToTime(timeRange[0])} — {minutesToTime(timeRange[1])}
                </div>
              </div>
              <div className="time-selector">
                <Slider
                  value={timeRange}
                  onChange={(e: SliderChangeEvent) => {
                    const raw = e.value as [number, number];
                    const MIN_GAP = 60;
                    let from = Math.min(raw[0], raw[1]);
                    let to = Math.max(raw[0], raw[1]);
                    if (to - from < MIN_GAP) {
                      if (from + MIN_GAP <= 1440) {
                        to = from + MIN_GAP;
                      } else {
                        to = 1440;
                        from = to - MIN_GAP;
                      }
                    }
                    setTimeRange([from, to]);
                  }}
                  range
                  min={0}
                  max={1440}
                  step={60}
                />
              </div>
            </div>

            <label className="schedule-filter__checkbox" data-testid="schedule-direct-only">
              <input
                type="checkbox"
                checked={directOnly}
                onChange={(e) => setDirectOnly(e.target.checked)}
              />
              <span>{t("SHARED.DIRECT_FLIGHT_ONLY")}</span>
            </label>

            <label
              className="schedule-filter__checkbox"
              data-testid="schedule-return-flights"
            >
              <input
                type="checkbox"
                checked={returnFlights}
                onChange={(e) => setReturnFlights(e.target.checked)}
              />
              <span>{t("SHARED.RETURN_FLIGHT_VIEW")}</span>
            </label>

            {returnFlights && (
              <>
                <div className="calendar">
                  <label className="label--filter">
                    {t("SHARED.RETURN_FLIGHT_DATE")}
                  </label>
                  <div className="calendar-input-wrapper">
                    <Calendar
                      ref={returnCalendarRef}
                      value={returnDateRange}
                      onChange={onReturnSelect}
                      selectionMode="range"
                      // TZ §4.1.9.4: return cannot start before outbound's
                      // dateTo. Tie the return picker's minDate to it so
                      // earlier days grey out in the calendar UI. Same
                      // week (retFrom = outTo) is allowed.
                      minDate={dateRange[1] ?? scheduleMinDate}
                      maxDate={scheduleMaxDate}
                      disabledDates={returnDisabledDates}
                      selectOtherMonths
                      dateFormat="dd.mm.yy"
                      placeholder={`${t("SHARED.DATE_FORMAT")} - ${t("SHARED.DATE_FORMAT")}`}
                      showIcon
                      className="input--filter"
                      data-testid="schedule-return-date-input"
                      inputId="schedule-return-date-input"
                      inputRef={returnDateRangeInputRef}
                      readOnlyInput
                    />
                    {(returnDateRange[0] || returnDateRange[1]) && (
                      <button
                        type="button"
                        className="calendar-clear-btn"
                        aria-label={t("SHARED.A11Y-CLEAR")}
                        data-testid="schedule-return-date-clear"
                        onClick={() => {
                          setReturnDateRange([null, null]);
                          setReturnBeforeOutboundError(null);
                        }}
                      >
                        &times;
                      </button>
                    )}
                  </div>
                  {returnBeforeOutboundError && (
                    <div
                      className="validation-tooltip"
                      data-testid="schedule-return-before-outbound-error"
                      role="alert"
                    >
                      {t(returnBeforeOutboundError)}
                    </div>
                  )}
                </div>

                <div className="wrapper--time-selector compact-view" data-testid="return-time-selector">
                  <div className="time-selector__label-value">
                    <div className="time-selector__label">
                      {t("SHARED.RETURN_FLIGHT_TIME")}
                    </div>
                    <div className="time-selector__value">
                      {minutesToTime(returnTimeRange[0])} — {minutesToTime(returnTimeRange[1])}
                    </div>
                  </div>
                  <div className="time-selector">
                    <Slider
                      value={returnTimeRange}
                      onChange={(e: SliderChangeEvent) => {
                        const raw = e.value as [number, number];
                        const MIN_GAP = 60;
                        let from = Math.min(raw[0], raw[1]);
                        let to = Math.max(raw[0], raw[1]);
                        if (to - from < MIN_GAP) {
                          if (from + MIN_GAP <= 1440) {
                            to = from + MIN_GAP;
                          } else {
                            to = 1440;
                            from = to - MIN_GAP;
                          }
                        }
                        setReturnTimeRange([from, to]);
                      }}
                      range
                      min={0}
                      max={1440}
                      step={60}
                    />
                  </div>
                </div>
              </>
            )}
          </div>

          <div className="filter-button">
            <button
              type="submit"
              className="search-button"
              data-testid="search-submit"
              disabled={isSubmitLocked}
              aria-disabled={isSubmitLocked}
            >
              <span>{t("SHARED.SCHEDULES_VIEW")}</span>
            </button>
          </div>
        </form>
      </section>
    </div>
  );
};
