/**
 * Flights Map filter panel.
 *
 * Provides departure/arrival inputs, connections toggle, domestic/international
 * filter toggles, and date picker. Calls back to the parent via onChange.
 *
 * @module
 */

import { type FC, useCallback, useEffect, useMemo, type FormEvent } from "react";
import { Calendar } from "primereact/calendar";
import { useTranslation } from "@/i18n/provider.js";
import { useLocale } from "@/i18n/useLocale.js";
import { CityAutocomplete, SwapCityButton } from "@/ui/city-autocomplete/index.js";
import { DayQuickPick } from "@/ui/calendar/DayQuickPick.js";
import { useDictionaries, findCityByCoord } from "@/shared/dictionaries/index.js";
import {
  getMinDate,
  getMaxDate,
  buildDisabledDates,
  findNextEnabledDate,
} from "../calendarRange.js";
import type { IFlightsMapFilterState } from "../types.js";

export interface FlightsMapFilterProps {
  value: IFlightsMapFilterState;
  availableDays?: string[];
  today?: string;
  onChange: (state: IFlightsMapFilterState) => void;
}

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

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 addDays(base: Date, days: number): Date {
  const date = new Date(base);
  date.setDate(date.getDate() + days);
  date.setHours(0, 0, 0, 0);
  return date;
}

function isSameDay(a: Date, b: Date): boolean {
  return (
    a.getFullYear() === b.getFullYear() &&
    a.getMonth() === b.getMonth() &&
    a.getDate() === b.getDate()
  );
}

function formatDateInputValue(value: Date, t: (key: string) => string): string {
  const today = new Date();
  today.setHours(0, 0, 0, 0);

  if (isSameDay(value, today)) return t("SHARED.TODAY");
  if (isSameDay(value, addDays(today, 1))) return t("SHARED.TOMORROW");

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

function parseDateInputValue(value: string, t: (key: string) => string): Date | null {
  const text = value.trim();
  const today = new Date();
  today.setHours(0, 0, 0, 0);

  if (text.localeCompare(t("SHARED.TODAY"), undefined, { sensitivity: "accent" }) === 0) {
    return today;
  }

  if (
    text.localeCompare(t("SHARED.TOMORROW"), undefined, { sensitivity: "accent" }) === 0
  ) {
    return addDays(today, 1);
  }

  const match = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/.exec(text);
  if (!match) return null;

  const day = Number(match[1]);
  const month = Number(match[2]) - 1;
  const year = Number(match[3]);
  const date = new Date(year, month, day);
  date.setHours(0, 0, 0, 0);

  return date.getFullYear() === year &&
    date.getMonth() === month &&
    date.getDate() === day
    ? date
    : null;
}

/**
 * Filter component for the flights map. Controls departure, arrival,
 * connections, domestic/international toggles, and date selection.
 */
export const FlightsMapFilter: FC<FlightsMapFilterProps> = ({
  value,
  availableDays,
  today: todayYmd,
  onChange,
}) => {
  const { t } = useTranslation();
  const { language } = useLocale();
  const { dictionaries } = useDictionaries(language);

  const handleLocate = useCallback(async () => {
    if (!dictionaries || typeof navigator === "undefined" || !navigator.geolocation) return;
    navigator.geolocation.getCurrentPosition(
      (pos) => {
        const city = findCityByCoord(
          dictionaries,
          pos.coords.latitude,
          pos.coords.longitude,
        );
        if (city) {
          onChange({ ...value, departure: city.code });
        }
      },
      () => {
        // silent
      },
      { enableHighAccuracy: false, timeout: 5000 },
    );
  }, [dictionaries, onChange, value]);

  const minDate = useMemo(() => getMinDate(todayYmd), [todayYmd]);
  const maxDate = useMemo(() => getMaxDate(todayYmd), [todayYmd]);

  const disabledDates = useMemo(
    () => buildDisabledDates(minDate, maxDate, availableDays ?? []),
    [minDate, maxDate, availableDays],
  );

  useEffect(() => {
    // Mirrors Angular FlightsMapFiltersStateService.snapToNearestAvailable.
    // With a date set, re-anchor it forward if it lands on a disabled day.
    // With no date but a departure set, auto-fill to today (or next enabled
    // day) — Angular runs snap from setDisabledDates after the calendar API
    // responds, which seeds the input on first route selection.
    if (!value.date && !value.departure) return;

    const current = value.date ? yyyymmddToDate(value.date) : new Date();
    if (!current) return;

    const snapped = findNextEnabledDate(current, disabledDates, minDate, maxDate);
    const snappedYyyymmdd = snapped ? dateToYyyymmdd(snapped) : undefined;

    if (snappedYyyymmdd !== value.date) {
      onChange({ ...value, date: snappedYyyymmdd });
    }
  }, [disabledDates, minDate, maxDate, value, onChange]);

  const handleExchange = useCallback(() => {
    const newDep = value.arrival ?? "";
    const newArr = value.departure ?? "";
    onChange({
      ...value,
      departure: newDep || undefined,
      arrival: newArr || undefined,
    });
  }, [value, onChange]);

  const handleConnectionsChange = useCallback(
    (e: FormEvent<HTMLInputElement>) => {
      onChange({ ...value, connections: (e.target as HTMLInputElement).checked });
    },
    [value, onChange],
  );

  const handleDomesticChange = useCallback(
    (e: FormEvent<HTMLInputElement>) => {
      const checked = (e.target as HTMLInputElement).checked;
      onChange({
        ...value,
        domestic: checked,
        international: checked ? false : value.international,
      });
    },
    [value, onChange],
  );

  const handleInternationalChange = useCallback(
    (e: FormEvent<HTMLInputElement>) => {
      const checked = (e.target as HTMLInputElement).checked;
      onChange({
        ...value,
        international: checked,
        domestic: checked ? false : value.domestic,
      });
    },
    [value, onChange],
  );

  const handleDateChange = useCallback(
    (newDate: Date | null) => {
      onChange({ ...value, date: newDate ? dateToYyyymmdd(newDate) : undefined });
    },
    [value, onChange],
  );

  const formatDateTime = useCallback(
    (date: Date) => formatDateInputValue(date, t),
    [t],
  );

  const parseDateTime = useCallback(
    (text: string) => parseDateInputValue(text, t) ?? new Date(Number.NaN),
    [t],
  );

  return (
    <div className="flights-map-filter" data-testid="flights-map-filter">
      <div className="flights-map-filter-header">
        <h3>{t("FLIGHTS-MAP.ROUTE")}</h3>
      </div>

      <CityAutocomplete
        label={t("SHARED.DEPARTURE_CITY")}
        placeholder={t("FLIGHTS-MAP.FILTER_DEPARTURE_PLACEHOLDER")}
        value={value.departure ?? ""}
        onChange={(code) => {
          // Mirrors Angular FlightsMapFiltersStateService.setDeparture:
          // clearing departure also resets the three toggles (domestic,
          // international, connections) back to false since none of them
          // apply without a departure anchor. Keeping them on would leave
          // the disabled chip in an "on" visual state after re-opening.
          if (!code) {
            onChange({
              ...value,
              departure: undefined,
              arrival: undefined,
              domestic: false,
              international: false,
              connections: false,
            });
            return;
          }
          onChange({
            ...value,
            departure: code,
            arrival: undefined,
            connections: false,
          });
        }}
        dictionaries={dictionaries}
        onLocate={handleLocate}
        testIdPrefix="fm-departure"
      />

      <SwapCityButton
        onClick={handleExchange}
        ariaLabel={t("SHARED.CITY_CHANGE")}
        testId="fm-exchange-btn"
      />

      <CityAutocomplete
        label={t("SHARED.ARRIVAL_CITY")}
        placeholder={t("FLIGHTS-MAP.FILTER_ARRIVAL_PLACEHOLDER")}
        value={value.arrival ?? ""}
        onChange={(code) => {
          onChange({
            ...value,
            arrival: code || undefined,
            connections: code ? value.connections : false,
          });
        }}
        dictionaries={dictionaries}
        testIdPrefix="fm-arrival"
      />

      <div className="flights-map-filter__info">
        <p>{t("FLIGHTS-MAP.FILTER_INFO")}</p>
      </div>

      <div className="flights-map-filter__toggles">
        {/* Mirrors Angular: domestic/international toggles require a
            departure city; the connections toggle also requires an
            arrival city (you can only route-search with both cities).
            We add `title` hints so disabled toggles explain why they
            can't be toggled — Angular's toggle-switch doesn't surface
            this, but the hint is a pure UX win at no parity cost. */}
        <label
          className={`flights-map-filter__toggle${!value.departure ? " flights-map-filter__toggle--disabled" : ""}`}
          title={!value.departure ? t("FLIGHTS-MAP.FILTER_DEPARTURE_PLACEHOLDER") : undefined}
        >
          <input
            type="checkbox"
            checked={value.domestic}
            onChange={handleDomesticChange}
            disabled={!value.departure}
            data-testid="fm-domestic-toggle"
          />
          <span className="flights-map-filter__switch" aria-hidden="true" />
          {t("FLIGHTS-MAP.DOMESTIC_FLIGHTS")}
        </label>

        <label
          className={`flights-map-filter__toggle${!value.departure ? " flights-map-filter__toggle--disabled" : ""}`}
          title={!value.departure ? t("FLIGHTS-MAP.FILTER_DEPARTURE_PLACEHOLDER") : undefined}
        >
          <input
            type="checkbox"
            checked={value.international}
            onChange={handleInternationalChange}
            disabled={!value.departure}
            data-testid="fm-international-toggle"
          />
          <span className="flights-map-filter__switch" aria-hidden="true" />
          {t("FLIGHTS-MAP.INTERNATIONAL_FLIGHTS")}
        </label>

        <label
          className={`flights-map-filter__toggle${!(value.departure && value.arrival) ? " flights-map-filter__toggle--disabled" : ""}`}
          title={
            !value.departure
              ? t("FLIGHTS-MAP.FILTER_DEPARTURE_PLACEHOLDER")
              : !value.arrival
                ? t("FLIGHTS-MAP.FILTER_ARRIVAL_PLACEHOLDER")
                : undefined
          }
        >
          <input
            type="checkbox"
            checked={value.connections}
            onChange={handleConnectionsChange}
            disabled={!(value.departure && value.arrival)}
            data-testid="fm-connections-toggle"
          />
          <span className="flights-map-filter__switch" aria-hidden="true" />
          {t("FLIGHTS-MAP.CONNECTING_FLIGHTS")}
        </label>
      </div>

      <div className="flights-map-filter__field">
        <label htmlFor="fm-date">{t("SHARED.FLIGHT_DATE")}</label>
        {/* TZ §4.1.24.2 Table 69 R16: calendar days must not be selectable
            until Город вылета is filled. Pass `disabled` to both the quick-
            pick shortcuts and the full calendar picker. */}
        <DayQuickPick
          value={value.date ? yyyymmddToDate(value.date) : null}
          locale={language}
          onChange={handleDateChange}
          disabled={!value.departure}
        />
        <Calendar
          value={value.date ? yyyymmddToDate(value.date) : null}
          onChange={(e) => handleDateChange(e.value as Date | null)}
          minDate={minDate}
          maxDate={maxDate}
          disabledDates={disabledDates}
          dateFormat="dd.mm.yy"
          formatDateTime={formatDateTime}
          parseDateTime={parseDateTime}
          placeholder={t("SHARED.DATE_FORMAT")}
          showIcon
          disabled={!value.departure}
          className="input--filter"
          inputId="fm-date"
          data-testid="fm-date-input"
        />
      </div>
    </div>
  );
};
