import { useState, useRef, useCallback, useEffect, type FC } from "react";
import { AutoComplete, type AutoCompleteCompleteEvent } from "primereact/autocomplete";
import { useTranslation } from "@/i18n/provider.js";
import { CityPickerPopup } from "./CityPickerPopup.js";
import { searchCities, type CityAutocompleteItem } from "./searchCities.js";
import type { IDictionaries } from "@/shared/dictionaries/index.js";
import "./CityAutocomplete.scss";

export interface CityAutocompleteProps {
  label: string;
  placeholder: string;
  value: string;
  onChange: (code: string) => void;
  dictionaries: IDictionaries | null;
  onLocate?: () => void | Promise<void>;
  error?: string;
  testIdPrefix?: string;
}

export const CityAutocomplete: FC<CityAutocompleteProps> = ({
  label,
  placeholder,
  value,
  onChange,
  dictionaries,
  onLocate,
  error,
  testIdPrefix = "city-autocomplete",
}) => {
  const { t } = useTranslation();
  const [inputValue, setInputValue] = useState<CityAutocompleteItem | string>(value);
  const [suggestions, setSuggestions] = useState<CityAutocompleteItem[]>([]);
  const [popupOpen, setPopupOpen] = useState(false);
  const rootRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // `value` is the city/airport CODE held by the parent form. Display the
    // human-readable name (matches Angular, where the input shows
    // "Шереметьево" rather than "SVO"). Fall back to the code itself when
    // the dictionary hasn't loaded yet or the code is unknown.
    if (!value) {
      setInputValue("");
      return;
    }
    const upper = value.toUpperCase();
    const city = dictionaries?.cityByCode.get(upper);
    if (city) {
      setInputValue({ kind: "city", ...city });
      return;
    }
    const airport = dictionaries?.airportByCode.get(upper);
    if (airport) {
      setInputValue({ kind: "airport", ...airport });
      return;
    }
    setInputValue(value);
  }, [value, dictionaries]);

  useEffect(() => {
    const handler = (e: MouseEvent) => {
      if (rootRef.current && !rootRef.current.contains(e.target as Node)) {
        setPopupOpen(false);
      }
    };
    document.addEventListener("mousedown", handler);
    return () => document.removeEventListener("mousedown", handler);
  }, []);

  // Auto-commit free-typed text that exactly matches a city or airport
  // name. Without this, a user who types "Москва" and clicks the search
  // button without picking from the dropdown silently fails: the
  // parent-held `value` (city code) stays empty while the AutoComplete's
  // local input holds the typed string. Exact name match (ru + en) is
  // safe — partial text is ignored so the picker still wins for
  // disambiguation.
  useEffect(() => {
    if (!dictionaries || value) return;
    if (typeof inputValue !== "string") return;
    const trimmed = inputValue.trim();
    if (!trimmed) return;
    const lc = trimmed.toLowerCase();
    const cityMatch = dictionaries.cities.find(
      (c) => c.name.toLowerCase() === lc,
    );
    if (cityMatch) {
      onChange(cityMatch.code);
      return;
    }
    const airportMatch = dictionaries.airports.find(
      (a) => a.name.toLowerCase() === lc,
    );
    if (airportMatch) {
      onChange(airportMatch.city_code.toUpperCase());
    }
  }, [inputValue, dictionaries, value, onChange]);

  const handleComplete = useCallback(
    (event: AutoCompleteCompleteEvent) => {
      if (!dictionaries) {
        setSuggestions([]);
        return;
      }
      setSuggestions(searchCities(dictionaries, event.query));
    },
    [dictionaries],
  );

  const handleSelect = useCallback(
    (code: string) => {
      onChange(code);
      setPopupOpen(false);
    },
    [onChange],
  );

  const handleClear = useCallback(() => {
    onChange("");
    setInputValue("");
  }, [onChange]);

  // TZ §4.1.9.1+§4.1.9.2: ESC closes the dictionary popup when open;
  // otherwise cancels manual entry — discard the typed text and restore
  // the last committed value's display (or clear when nothing was committed).
  const handleKeyDown = useCallback(
    (e: React.KeyboardEvent<HTMLDivElement>) => {
      if (e.key !== "Escape") return;
      e.preventDefault();
      e.stopPropagation();
      if (popupOpen) {
        setPopupOpen(false);
        return;
      }
      // Restore display to match the committed `value` prop
      if (!value) {
        setInputValue("");
        return;
      }
      const upper = value.toUpperCase();
      const city = dictionaries?.cityByCode.get(upper);
      if (city) {
        setInputValue({ kind: "city", ...city });
        return;
      }
      const airport = dictionaries?.airportByCode.get(upper);
      if (airport) {
        setInputValue({ kind: "airport", ...airport });
        return;
      }
      setInputValue(value);
    },
    [value, dictionaries, popupOpen],
  );

  const renderSuggestion = useCallback((item: CityAutocompleteItem) => {
    if (item.kind === "city") {
      return (
        <div className="city-autocomplete__item" data-testid={`city-suggestion-${item.code}`}>
          <span className="city">{item.name},</span>
          <span className="country">&nbsp;{item.countryName}</span>
        </div>
      );
    }
    return (
      <div
        className="city-autocomplete__item city-autocomplete__item--airport"
        data-testid={`airport-suggestion-${item.code}`}
      >
        <span className="airport">{item.name}</span>
      </div>
    );
  }, []);

  // Resolve the code to the owning city. The API (popular-requests,
  // deep links, etc.) hands us either a city code like "MOW" or an
  // airport code like "SVO"; in the latter case we look up the airport
  // and follow its city_code so the label reads "MOW" instead of blank.
  const selectedCity = (() => {
    if (!value || !dictionaries) return null;
    const upper = value.toUpperCase();
    const direct = dictionaries.cityByCode.get(upper);
    if (direct) return direct;
    const airport = dictionaries.airportByCode.get(upper);
    if (airport) {
      return dictionaries.cityByCode.get(airport.city_code.toUpperCase()) ?? null;
    }
    return null;
  })();
  // Mirror Angular's `[ngClass]="{ 'has-value': city }"` — any truthy
  // model value (resolved city OR a free-typed string OR the raw code
  // before dictionaries load) should reveal the clear button. Otherwise
  // users can't wipe a partial input when the dictionary hasn't caught
  // up yet.
  const hasValue = Boolean(
    selectedCity ||
      value ||
      (typeof inputValue === "string" ? inputValue : inputValue?.name),
  );

  return (
    <div ref={rootRef} className="city-autocomplete" onKeyDown={handleKeyDown}>
      <div className="city-autocomplete__labels-container">
        <label className="city-autocomplete__label">{label}</label>
        <label className="city-autocomplete__label" data-testid={`${testIdPrefix}-code`}>
          {selectedCity?.code ?? ""}
        </label>
      </div>

      {error && (
        <div className="tooltip" data-testid={`${testIdPrefix}-error`}>
          {error}
        </div>
      )}

      <div
        className={`city-autocomplete__input${
          hasValue ? " city-autocomplete__input--has-value" : ""
        }${error ? " city-autocomplete__input--has-error" : ""}`}
      >
        <AutoComplete
          value={inputValue}
          suggestions={suggestions}
          completeMethod={handleComplete}
          field="name"
          minLength={1}
          placeholder={placeholder}
          itemTemplate={renderSuggestion}
          onChange={(e) => setInputValue(e.value as CityAutocompleteItem | string)}
          onSelect={(e) => {
            const sel = e.value as CityAutocompleteItem;
            if (!sel) return;
            const code =
              sel.kind === "airport" ? sel.city_code.toUpperCase() : sel.code;
            handleSelect(code);
          }}
          data-testid={`${testIdPrefix}-input`}
          inputClassName="input--filter"
          className="input--filter"
        />
        <button
          type="button"
          className="button-clear"
          onClick={handleClear}
          data-testid={`${testIdPrefix}-clear-button`}
          aria-label={t("SHARED.A11Y-CLEAR")}
        />
        <button
          type="button"
          className={`city-autocomplete__search-button${
            popupOpen ? " city-autocomplete__search-button--opened" : ""
          }`}
          onClick={() => {
            if (!dictionaries) return;
            setPopupOpen((v) => !v);
          }}
          data-testid={`${testIdPrefix}-popup-button`}
          aria-label={t("SHARED.A11Y-OPEN-PICKER")}
        />
      </div>

      {popupOpen && dictionaries && (
        <div className="city-autocomplete-popup-wrapper">
          <CityPickerPopup
            dictionaries={dictionaries}
            selectedCode={value}
            onSelect={handleSelect}
            onClose={() => setPopupOpen(false)}
            {...(onLocate ? { onLocate } : {})}
          />
        </div>
      )}
    </div>
  );
};
