import type { IAirport, ICity, IDictionaries } from "@/shared/dictionaries/types.js";
import { ruKeyboardLayout } from "./keyboardLayoutConverter.js";

export type CityAutocompleteItem =
  | ({ kind: "city" } & ICity)
  | ({ kind: "airport" } & IAirport);

const MAX_ITEMS_COUNT = 10;
const CODE_LENGTH = 3;

const byName = <T extends { name: string }>(a: T, b: T): number =>
  a.name.localeCompare(b.name, "ru");

function findCitiesStartingWith(dict: IDictionaries, q: string): ICity[] {
  const lc = q.toLowerCase();
  return dict.cities.filter((c) => c.name.toLowerCase().startsWith(lc)).sort(byName);
}

function findCitiesIncluding(dict: IDictionaries, q: string): ICity[] {
  const lc = q.toLowerCase();
  return dict.cities.filter((c) => c.name.toLowerCase().includes(lc)).sort(byName);
}

function findCitiesByAirportName(dict: IDictionaries, q: string): ICity[] {
  const lc = q.toLowerCase();
  const airports = dict.airports
    .filter((a) => a.name.toLowerCase().startsWith(lc))
    .sort(byName);
  const result: ICity[] = [];
  for (const a of airports) {
    const city = dict.cityByCode.get(a.city_code.toUpperCase());
    if (city) result.push(city);
  }
  return result;
}

function dedupeByCode<T extends { code: string }>(xs: T[]): T[] {
  const seen = new Set<string>();
  const out: T[] = [];
  for (const x of xs) {
    if (seen.has(x.code)) continue;
    seen.add(x.code);
    out.push(x);
  }
  return out;
}

function addAirportsToCities(cities: ICity[]): CityAutocompleteItem[] {
  const out: CityAutocompleteItem[] = [];
  for (const city of cities) {
    out.push({ kind: "city", ...city });
    const airports = city.airports
      .filter((a) => a.code !== city.code)
      .sort(byName);
    for (const a of airports) out.push({ kind: "airport", ...a });
  }
  return out;
}

function findCitiesByName(dict: IDictionaries, q: string): ICity[] {
  const startsWith = findCitiesStartingWith(dict, q);
  if (startsWith.length > MAX_ITEMS_COUNT) return startsWith;

  const includes = findCitiesIncluding(dict, q);
  const merged = dedupeByCode([...startsWith, ...includes]);
  if (merged.length > MAX_ITEMS_COUNT) return merged;

  const byAirports = findCitiesByAirportName(dict, q);
  const notStartsWith = [...includes, ...byAirports].sort(byName);
  return dedupeByCode([...startsWith, ...notStartsWith]);
}

/**
 * Mirrors Angular's CitiesSearchService.findCitiesByName (top 10), then inserts
 * each city's airports (excluding the airport whose code matches the city code)
 * directly after it. Discriminated union lets the renderer draw cities vs
 * airports differently.
 *
 * TZ §4.1.9.1: if no match is found for the raw query, retry with the query
 * converted from EN keyboard layout to RU (e.g. "vjc" → "мос").
 */
export function searchCities(dict: IDictionaries, query: string): CityAutocompleteItem[] {
  const q = query.trim();
  if (!q) return [];

  const upper = q.toUpperCase();
  if (upper.length === CODE_LENGTH) {
    const byCity = dict.cityByCode.get(upper);
    if (byCity) return addAirportsToCities([byCity]);
    const byAirport = dict.airportByCode.get(upper);
    if (byAirport) {
      const city = dict.cityByCode.get(byAirport.city_code.toUpperCase());
      if (city) return addAirportsToCities([city]);
    }
  }

  const cities = findCitiesByName(dict, q).slice(0, MAX_ITEMS_COUNT);
  if (cities.length > 0) return addAirportsToCities(cities);

  // TZ §4.1.9.1: retry with EN→RU keyboard layout conversion (e.g. "vjc" → "мос")
  const translitQ = ruKeyboardLayout.fromEn(q);
  if (translitQ !== q) {
    const translitCities = findCitiesByName(dict, translitQ).slice(0, MAX_ITEMS_COUNT);
    if (translitCities.length > 0) return addAirportsToCities(translitCities);
  }

  return [];
}
