/**
 * @vitest-environment jsdom
 */

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, act } from "@testing-library/react";
import { FlightsMapStartPage } from "./FlightsMapStartPage.js";
import type * as DictionariesModuleNS from "@/shared/dictionaries/index.js";
import { transformDictionaries } from "@/shared/dictionaries/index.js";
import type { IDictionaries, IRawDictionaries } from "@/shared/dictionaries/index.js";
import type {
  FlightsMapCalendarParams,
  FlightsMapSearchParams,
} from "../types.js";
import {
  resetCrossSectionStore,
  setMapFilter,
  setBoardFilter,
  setScheduleFilter,
  type MapFilterSnapshot,
  type BoardFilterSnapshot,
  type ScheduleFilterSnapshot,
} from "@/shared/state/crossSectionNavigation.js";

type DictionariesModule = typeof DictionariesModuleNS;

function buildDictionaries(raw?: IRawDictionaries): IDictionaries {
  return transformDictionaries(
    raw ?? { regions: [], countries: [], cities: [], airports: [] },
    "ru",
  );
}

vi.mock("@modern-js/runtime/router", () => ({
  useParams: () => ({ lang: "ru-ru" }),
  Link: ({ children, ...props }: { children: React.ReactNode }) => <a {...props}>{children}</a>,
}));

vi.mock("@/i18n/provider.js", () => ({
  useTranslation: () => ({ t: (key: string) => key, i18n: { language: "ru" } }),
}));

vi.mock("@/ui/layout/PageTabs.js", () => ({
  PageTabs: () => <div data-testid="page-tabs" />,
}));

vi.mock("./ClientOnly.js", () => ({
  ClientOnly: ({ children }: { children: React.ReactNode }) => <>{children}</>,
}));

let lastMapCanvasProps: Record<string, unknown> | null = null;
vi.mock("./MapCanvas.js", () => ({
  MapCanvas: (props: Record<string, unknown>) => {
    lastMapCanvasProps = props;
    return <div data-testid="map-canvas" />;
  },
}));

let lastMapFilterProps: Record<string, unknown> | null = null;
vi.mock("./FlightsMapFilter.js", () => ({
  FlightsMapFilter: (props: Record<string, unknown>) => {
    lastMapFilterProps = props;
    return <div data-testid="flights-map-filter" />;
  },
}));

vi.mock("@/env/index.js", () => ({
  getEnv: () => ({ API_BASE_URL: "https://api.test" }),
}));

const searchState: {
  routes: Array<{ route: string[]; isDirect: boolean }>;
  loading: boolean;
  error: Error | null;
} = {
  routes: [],
  loading: false,
  error: null,
};
const searchCalls: Array<FlightsMapSearchParams | null> = [];
vi.mock("../hooks/useFlightsMapSearch.js", () => ({
  useFlightsMapSearch: (params: FlightsMapSearchParams | null) => {
    searchCalls.push(params);
    return { ...searchState, refresh: vi.fn() };
  },
}));

const calendarCalls: Array<FlightsMapCalendarParams | null> = [];
vi.mock("../hooks/useFlightsMapCalendar.js", () => ({
  useFlightsMapCalendar: (params: FlightsMapCalendarParams | null) => {
    calendarCalls.push(params);
    return { availableDays: [] };
  },
}));

const dictState: {
  dictionaries: IDictionaries | null;
  loading: boolean;
  error: Error | null;
} = {
  dictionaries: null,
  loading: true,
  error: null,
};
vi.mock("@/shared/dictionaries/index.js", async () => {
  const actual = await vi.importActual<DictionariesModule>(
    "@/shared/dictionaries/index.js",
  );
  return {
    ...actual,
    useDictionaries: () => dictState,
  };
});

describe("FlightsMapStartPage — dictionaries integration", () => {
  beforeEach(() => {
    dictState.dictionaries = null;
    dictState.loading = true;
    dictState.error = null;
  });

  it("shows the loader while dictionaries are loading", () => {
    dictState.loading = true;
    render(<FlightsMapStartPage />);
    expect(screen.getByTestId("map-loader")).toBeTruthy();
  });

  it("shows the error banner when dictionaries failed", () => {
    dictState.loading = false;
    dictState.error = new Error("dict boom");
    render(<FlightsMapStartPage />);
    expect(screen.getByTestId("map-error")).toBeTruthy();
  });

  it("does not show the loader once dictionaries resolve", () => {
    dictState.loading = false;
    dictState.dictionaries = buildDictionaries();
    render(<FlightsMapStartPage />);
    expect(screen.queryByTestId("map-loader")).toBeNull();
  });
});

describe("FlightsMapStartPage — markers from dictionaries", () => {
  beforeEach(() => {
    lastMapCanvasProps = null;
    dictState.dictionaries = null;
    dictState.loading = false;
    dictState.error = null;
  });

  it("maps cities to IMapMarker[] with zoomLevel and countryType", () => {
    dictState.dictionaries = buildDictionaries({
      regions: [],
      countries: [],
      cities: [
        {
          code: "MOW",
          title: { ru: "Москва" },
          country_code: "RU",
          has_afl_flights: true,
          location: { lat: 55, lon: 37 },
        },
        {
          code: "PAR",
          title: { ru: "Париж" },
          country_code: "FR",
          has_afl_flights: true,
          location: { lat: 48, lon: 2 },
        },
      ],
      airports: [
        {
          code: "MOW",
          city_code: "MOW",
          title: { ru: "Москва" },
          has_afl_flights: true,
          location: { lat: 55, lon: 37 },
        },
        {
          code: "PAR",
          city_code: "PAR",
          title: { ru: "Париж" },
          has_afl_flights: true,
          location: { lat: 48, lon: 2 },
        },
      ],
    });

    render(<FlightsMapStartPage />);

    const markers = lastMapCanvasProps!["markers"] as Array<Record<string, unknown>>;
    expect(markers).toHaveLength(2);

    const mow = markers.find((m) => m["id"] === "MOW")!;
    expect(mow["countryType"]).toBe("ru");
    expect(mow["zoomLevel"]).toBe(2);
    // Permanent marker label is the localized city name. (The IATA code
    // belongs to a separate hover tooltip per TZ §4.1.24.3 R24 — not to
    // this always-visible label.)
    expect(mow["label"]).toBe("Москва");

    const par = markers.find((m) => m["id"] === "PAR")!;
    expect(par["countryType"]).toBe("other");
    expect(par["zoomLevel"]).toBe(6);
  });

  it("drops cities whose location is missing or invalid", () => {
    dictState.dictionaries = buildDictionaries({
      regions: [],
      countries: [],
      cities: [
        {
          code: "MOW",
          title: { ru: "Москва" },
          country_code: "RU",
          has_afl_flights: true,
          location: { lat: 55, lon: 37 },
        },
        {
          code: "BAD",
          title: { ru: "Плохой" },
          country_code: "RU",
          has_afl_flights: true,
          location: { lat: Number.NaN, lon: 0 },
        },
      ],
      airports: [
        {
          code: "MOW",
          city_code: "MOW",
          title: { ru: "Москва" },
          has_afl_flights: true,
          location: { lat: 55, lon: 37 },
        },
        {
          code: "BAD",
          city_code: "BAD",
          title: { ru: "Плохой" },
          has_afl_flights: true,
          location: { lat: Number.NaN, lon: 0 },
        },
      ],
    });

    render(<FlightsMapStartPage />);

    const markers = lastMapCanvasProps!["markers"] as Array<Record<string, unknown>>;
    expect(markers.map((m) => m["id"])).toEqual(["MOW"]);
  });

  it("passes domestic/international toggles through to MapCanvas", () => {
    dictState.dictionaries = buildDictionaries();

    render(<FlightsMapStartPage />);

    expect(lastMapCanvasProps!["domestic"]).toBe(false);
    expect(lastMapCanvasProps!["international"]).toBe(false);
  });
});

describe("FlightsMapStartPage — polylines from search results (C.3)", () => {
  beforeEach(() => {
    lastMapCanvasProps = null;
    dictState.dictionaries = buildDictionaries({
      regions: [],
      countries: [],
      cities: [
        {
          code: "A",
          title: { ru: "Город A" },
          country_code: "RU",
          has_afl_flights: true,
          location: { lat: 55, lon: 37 },
        },
        {
          code: "B",
          title: { ru: "Город B" },
          country_code: "RU",
          has_afl_flights: true,
          location: { lat: 60, lon: 40 },
        },
        {
          code: "X",
          title: { ru: "Город X" },
          country_code: "RU",
          has_afl_flights: true,
          location: { lat: 58, lon: 38 },
        },
      ],
      airports: [
        {
          code: "A",
          city_code: "A",
          title: { ru: "Аэропорт A" },
          has_afl_flights: true,
          location: { lat: 55, lon: 37 },
        },
        {
          code: "B",
          city_code: "B",
          title: { ru: "Аэропорт B" },
          has_afl_flights: true,
          location: { lat: 60, lon: 40 },
        },
        {
          code: "X",
          city_code: "X",
          title: { ru: "Аэропорт X" },
          has_afl_flights: true,
          location: { lat: 58, lon: 38 },
        },
      ],
    });
    dictState.loading = false;
    dictState.error = null;
    searchState.routes = [];
    searchState.loading = false;
    searchState.error = null;
  });

  it("passes an empty polylines array when no routes", () => {
    render(<FlightsMapStartPage />);
    const polylines = lastMapCanvasProps!["polylines"] as unknown[];
    expect(polylines).toEqual([]);
  });

  it("passes an empty intermediateIds when no routes", () => {
    render(<FlightsMapStartPage />);
    const ids = lastMapCanvasProps!["intermediateIds"] as string[];
    expect(ids).toEqual([]);
  });

  it("flows intermediateIds from a multi-hop route", () => {
    searchState.routes = [{ route: ["A", "X", "B"], isDirect: false }];

    render(<FlightsMapStartPage />);

    const ids = lastMapCanvasProps!["intermediateIds"] as string[];
    expect(ids).toEqual(["X"]);
  });

  it("flows route-mode polylines with correct style flags when search returned routes", () => {
    searchState.routes = [
      { route: ["A", "B"], isDirect: true },
      { route: ["A", "X", "B"], isDirect: false },
    ];

    render(<FlightsMapStartPage />);

    const polylines = lastMapCanvasProps!["polylines"] as Array<{
      style: string;
      cityIds: string[];
    }>;
    expect(polylines).toHaveLength(2);
    expect(polylines[0]!.style).toBe("direct");
    expect(polylines[1]!.style).toBe("connecting");
  });
});

describe("TZ §4.1.4 Table 7 breadcrumbs — Flight-Map pages (rows 1-3)", () => {
  it("4.1.4-R-Map-Crumb: Flight-Map page shows only [Главная] per TZ Table 7 rows 1-3", () => {
    dictState.dictionaries = buildDictionaries();
    dictState.loading = false;
    dictState.error = null;
    searchState.routes = [];
    searchState.loading = false;
    searchState.error = null;

    const { container } = render(<FlightsMapStartPage />);

    // Breadcrumbs component auto-prepends Home (Главная), so passing []
    // means exactly 1 breadcrumb item should render: just Home.
    const crumbItems = container.querySelectorAll('[data-testid="breadcrumbs"] li');
    expect(crumbItems).toHaveLength(1);

    // The single item should not have any child links or extra content
    // (just the Home link auto-prepended, no self-referential "Карта полетов" crumb).
    const homeLink = container.querySelector('[data-testid="breadcrumbs"] a');
    expect(homeLink).toBeTruthy();
  });
});

describe("FlightsMapStartPage — C.4 integration", () => {
  beforeEach(() => {
    lastMapCanvasProps = null;
    searchCalls.length = 0;
    dictState.dictionaries = buildDictionaries({
      regions: [],
      countries: [
        { code: "RU", title: { ru: "Россия" }, world_region_id: 500374 },
        { code: "US", title: { ru: "США" }, world_region_id: 1 },
      ],
      cities: [
        {
          code: "MOW",
          title: { ru: "Москва" },
          country_code: "RU",
          has_afl_flights: true,
          location: { lat: 55, lon: 37 },
        },
        {
          code: "LED",
          title: { ru: "Санкт-Петербург" },
          country_code: "RU",
          has_afl_flights: true,
          location: { lat: 60, lon: 30 },
        },
        {
          code: "NYC",
          title: { ru: "Нью-Йорк" },
          country_code: "US",
          has_afl_flights: true,
          location: { lat: 40, lon: -74 },
        },
      ],
      airports: [
        {
          code: "SVO",
          city_code: "MOW",
          title: { ru: "Шереметьево" },
          has_afl_flights: true,
          location: { lat: 55, lon: 37 },
        },
        {
          code: "LED",
          city_code: "LED",
          title: { ru: "Пулково" },
          has_afl_flights: true,
          location: { lat: 60, lon: 30 },
        },
        {
          code: "JFK",
          city_code: "NYC",
          title: { ru: "Джон Кеннеди" },
          has_afl_flights: true,
          location: { lat: 40, lon: -74 },
        },
      ],
    });
    dictState.loading = false;
    dictState.error = null;
    searchState.routes = [];
    searchState.loading = false;
    searchState.error = null;
  });

  it("renders all routes (no filter toggles) in initial filter state", () => {
    searchState.routes = [
      { route: ["MOW", "LED"], isDirect: true },
      { route: ["MOW", "NYC"], isDirect: true },
    ];
    render(<FlightsMapStartPage />);
    const polylines = lastMapCanvasProps!["polylines"] as Array<{ cityIds: string[] }>;
    expect(polylines).toHaveLength(2);
  });

  it("renders a departure + arrival popup in route mode with a buy-ticket URL", () => {
    searchState.routes = [{ route: ["MOW", "LED"], isDirect: true }];

    render(<FlightsMapStartPage />);
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("MOW");
    });
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("LED");
    });

    const popups = lastMapCanvasProps!["popups"] as Array<{ content: string; lat: number; lng: number }>;
    expect(popups).toHaveLength(2);
    expect(popups[0]!.content).toContain("Москва");
    expect(popups[1]!.content).toContain("Санкт-Петербург");
    expect(popups[1]!.content).toContain("routes=MOW.");
    expect(popups[1]!.content).toContain(".LED");
    expect(popups[1]!.content).toContain("https://www.aeroflot.ru/sb/app/ru-ru");
  });

  it("does not render popups in spider mode (departure only)", () => {
    searchState.routes = [{ route: ["MOW", "LED"], isDirect: true }];
    render(<FlightsMapStartPage />);
    const onClick = lastMapCanvasProps!["onMarkerClick"] as (id: string) => void;
    act(() => {
      onClick("MOW");
    });

    const popups = lastMapCanvasProps!["popups"] as unknown[];
    expect(popups).toEqual([]);
  });

  it("auto-fallback: re-issues the search with connections=1 when direct routes come back empty", () => {
    searchState.routes = [];
    render(<FlightsMapStartPage />);
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("MOW");
    });
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("LED");
    });

    const withOne = searchCalls.filter((p) => p?.connections === 1);
    expect(withOne.length).toBeGreaterThanOrEqual(1);
  });

  it("mirror: last search call uses connections=1 after auto-fallback", () => {
    searchState.routes = [];
    render(<FlightsMapStartPage />);
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("MOW");
    });
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("LED");
    });

    const last = [...searchCalls].reverse().find((p) => p?.departure && p?.arrival);
    expect(last?.connections).toBe(1);
  });
});

// ---------------------------------------------------------------------------
// TZ §4.1.8 / §4.1.1-R26: Map filter cross-section isolation tests
// ---------------------------------------------------------------------------

describe("4.1.8 / 4.1.1-R26: Flight-Map filter cross-section isolation", () => {
  beforeEach(() => {
    lastMapFilterProps = null;
    lastMapCanvasProps = null;
    dictState.dictionaries = buildDictionaries();
    dictState.loading = false;
    dictState.error = null;
    searchState.routes = [];
    searchState.loading = false;
    searchState.error = null;
    resetCrossSectionStore();
  });

  it("4.1.8: hydrates map filter from map snapshot on mount", () => {
    const mapSnap: MapFilterSnapshot = {
      departure: "MOW", arrival: "LED", date: "20260515",
      showInternal: true, showInternational: false, showTransfers: true,
    };
    setMapFilter(mapSnap);
    render(<FlightsMapStartPage />);
    const filterValue = lastMapFilterProps!["value"] as Record<string, unknown>;
    expect(filterValue["departure"]).toBe("MOW");
    expect(filterValue["arrival"]).toBe("LED");
    expect(filterValue["domestic"]).toBe(true);
    expect(filterValue["international"]).toBe(false);
    expect(filterValue["connections"]).toBe(true);
  });

  it("4.1.1-R26: Board filter does NOT hydrate flight map", () => {
    const boardSnap: BoardFilterSnapshot = {
      mode: "route", departure: "SVO", arrival: "VVO",
      date: "20260515", timeFrom: "0000", timeTo: "2400",
      searchExecuted: true,
    };
    setBoardFilter(boardSnap);
    // No map snapshot set
    render(<FlightsMapStartPage />);
    const filterValue = lastMapFilterProps!["value"] as Record<string, unknown>;
    // Map filter should be at defaults, not seeded from board
    expect(filterValue["departure"]).toBeUndefined();
    expect(filterValue["arrival"]).toBeUndefined();
  });

  it("4.1.1-R26: Schedule filter does NOT hydrate flight map", () => {
    const schedSnap: ScheduleFilterSnapshot = {
      mode: "route", departure: "KZN", arrival: "AER",
      dateFrom: "20260511", dateTo: "20260517",
      timeFrom: "0000", timeTo: "2400",
      onlyDirect: false, showReturn: false, searchExecuted: true,
    };
    setScheduleFilter(schedSnap);
    // No map snapshot set
    render(<FlightsMapStartPage />);
    const filterValue = lastMapFilterProps!["value"] as Record<string, unknown>;
    // Map filter should be at defaults, not seeded from schedule
    expect(filterValue["departure"]).toBeUndefined();
    expect(filterValue["arrival"]).toBeUndefined();
  });

  it("4.1.8: renders empty filter when no map snapshot is in store", () => {
    // Store is empty (resetCrossSectionStore called in beforeEach)
    render(<FlightsMapStartPage />);
    const filterValue = lastMapFilterProps!["value"] as Record<string, unknown>;
    expect(filterValue["departure"]).toBeUndefined();
    expect(filterValue["arrival"]).toBeUndefined();
    expect(filterValue["domestic"]).toBe(false);
    expect(filterValue["international"]).toBe(false);
  });
});

// ---------------------------------------------------------------------------
// TZ §4.1.1 ¶4 / ¶9: Flight-Map first-entry toggle defaults
// ---------------------------------------------------------------------------

describe("4.1.1-R14/R21: Flight-Map first-entry toggle defaults per TZ §4.1.1", () => {
  let originalGeolocation: Geolocation | undefined;

  beforeEach(() => {
    lastMapFilterProps = null;
    dictState.dictionaries = buildDictionaries({
      regions: [],
      countries: [],
      cities: [
        {
          code: "MOW",
          title: { ru: "Москва" },
          country_code: "RU",
          has_afl_flights: true,
          location: { lat: 55.75, lon: 37.62 },
        },
      ],
      airports: [
        {
          code: "SVO",
          city_code: "MOW",
          title: { ru: "Шереметьево" },
          has_afl_flights: true,
          location: { lat: 55.75, lon: 37.62 },
        },
      ],
    });
    dictState.loading = false;
    dictState.error = null;
    searchState.routes = [];
    searchState.loading = false;
    searchState.error = null;
    resetCrossSectionStore();

    // Save original geolocation
    originalGeolocation = navigator.geolocation;
  });

  afterEach(() => {
    // Restore geolocation
    Object.defineProperty(navigator, "geolocation", {
      configurable: true,
      writable: true,
      value: originalGeolocation,
    });
  });

  it("4.1.1-R14: with geo consent (success) — domestic ON, international ON, transfers OFF", async () => {
    // Mock geolocation success callback
    Object.defineProperty(navigator, "geolocation", {
      configurable: true,
      writable: true,
      value: {
        getCurrentPosition: (cb: PositionCallback) => {
          // Simulate async position callback on next tick
          setTimeout(() => {
            cb({
              coords: {
                latitude: 55.75,
                longitude: 37.62,
                accuracy: 50,
                altitude: null,
                altitudeAccuracy: null,
                heading: null,
                speed: null,
              },
              timestamp: Date.now(),
            } as GeolocationPosition);
          }, 0);
        },
      },
    });

    const { rerender } = render(<FlightsMapStartPage />);

    // Wait for geolocation callback to fire
    await new Promise((resolve) => setTimeout(resolve, 50));
    rerender(<FlightsMapStartPage />);

    const filterValue = lastMapFilterProps!["value"] as Record<string, unknown>;
    expect(filterValue["domestic"]).toBe(true); // R14: domestic ON
    expect(filterValue["international"]).toBe(true); // R14: international ON
    expect(filterValue["connections"]).toBe(false); // R14: transfers OFF
  });

  it("4.1.1-R21: without geo consent (error/denied) — all three toggles OFF", async () => {
    // Mock geolocation failure (permission denied, etc.)
    Object.defineProperty(navigator, "geolocation", {
      configurable: true,
      writable: true,
      value: {
        getCurrentPosition: (
          _cb: PositionCallback,
          errorCallback: PositionErrorCallback,
        ) => {
          // Simulate async error callback on next tick
          setTimeout(() => {
            errorCallback({
              code: 1, // PERMISSION_DENIED
              message: "User denied geolocation",
              PERMISSION_DENIED: 1,
              POSITION_UNAVAILABLE: 2,
              TIMEOUT: 3,
            } as GeolocationPositionError);
          }, 0);
        },
      },
    });

    const { rerender } = render(<FlightsMapStartPage />);

    // Wait for geolocation error callback to fire
    await new Promise((resolve) => setTimeout(resolve, 50));
    rerender(<FlightsMapStartPage />);

    const filterValue = lastMapFilterProps!["value"] as Record<string, unknown>;
    expect(filterValue["domestic"]).toBe(false); // R21: domestic OFF
    expect(filterValue["international"]).toBe(false); // R21: international OFF
    expect(filterValue["connections"]).toBe(false); // R21: transfers OFF
  });

  it("R14: toggling off domestic when geo succeeds should not re-enable international", async () => {
    // Mock geolocation success
    Object.defineProperty(navigator, "geolocation", {
      configurable: true,
      writable: true,
      value: {
        getCurrentPosition: (cb: PositionCallback) => {
          setTimeout(() => {
            cb({
              coords: {
                latitude: 55.75,
                longitude: 37.62,
                accuracy: 50,
                altitude: null,
                altitudeAccuracy: null,
                heading: null,
                speed: null,
              },
              timestamp: Date.now(),
            } as GeolocationPosition);
          }, 0);
        },
      },
    });

    const { rerender } = render(<FlightsMapStartPage />);
    await new Promise((resolve) => setTimeout(resolve, 50));
    rerender(<FlightsMapStartPage />);

    // At this point, geo should have fired, setting both domestic and international to true
    const filterValue = lastMapFilterProps!["value"] as Record<string, unknown>;
    expect(filterValue["domestic"]).toBe(true);
    expect(filterValue["international"]).toBe(true);
  });

  it("R21: stored map snapshot takes precedence over geo defaults", async () => {
    // Pre-set a map snapshot with specific toggle values
    const mapSnap: MapFilterSnapshot = {
      departure: "MOW",
      arrival: null,
      date: null,
      showInternal: false,
      showInternational: true,
      showTransfers: false,
    };
    setMapFilter(mapSnap);

    // Mock geolocation success (should NOT override the snapshot)
    Object.defineProperty(navigator, "geolocation", {
      configurable: true,
      writable: true,
      value: {
        getCurrentPosition: (cb: PositionCallback) => {
          setTimeout(() => {
            cb({
              coords: {
                latitude: 55.75,
                longitude: 37.62,
                accuracy: 50,
                altitude: null,
                altitudeAccuracy: null,
                heading: null,
                speed: null,
              },
              timestamp: Date.now(),
            } as GeolocationPosition);
          }, 0);
        },
      },
    });

    const { rerender } = render(<FlightsMapStartPage />);
    await new Promise((resolve) => setTimeout(resolve, 50));
    rerender(<FlightsMapStartPage />);

    const filterValue = lastMapFilterProps!["value"] as Record<string, unknown>;
    // Snapshot values should be preserved, not overwritten by geo defaults
    expect(filterValue["domestic"]).toBe(false);
    expect(filterValue["international"]).toBe(true);
  });
});

// ---------------------------------------------------------------------------
// Permanent marker label shows the localized city name. TZ §4.1.24.3 R24
// prescribes the separate hover tooltip (not the always-visible label)
// as showing the IATA code.
// ---------------------------------------------------------------------------

describe("map markers use localized city name as permanent label", () => {
  beforeEach(() => {
    lastMapCanvasProps = null;
    dictState.loading = false;
    dictState.error = null;
    searchState.routes = [];
    searchState.loading = false;
    searchState.error = null;
    resetCrossSectionStore();
  });

  it("marker label equals the localized city name, not the IATA code", () => {
    dictState.dictionaries = buildDictionaries({
      regions: [],
      countries: [],
      cities: [
        {
          code: "MOW",
          title: { ru: "Москва" },
          country_code: "RU",
          has_afl_flights: true,
          location: { lat: 55, lon: 37 },
        },
      ],
      airports: [
        {
          code: "SVO",
          city_code: "MOW",
          title: { ru: "Шереметьево" },
          has_afl_flights: true,
          location: { lat: 55, lon: 37 },
        },
      ],
    });

    render(<FlightsMapStartPage />);

    const markers = lastMapCanvasProps!["markers"] as Array<Record<string, unknown>>;
    const mow = markers.find((m) => m["id"] === "MOW")!;
    expect(mow["label"]).toBe("Москва");
    expect(mow["label"]).not.toBe("MOW");
  });
});

// ---------------------------------------------------------------------------
// TZ §4.1.24.4 R36-R39: interactive click sequence (first/second/third click)
// ---------------------------------------------------------------------------

describe("§4.1.24.4: interactive map click sequence", () => {
  beforeEach(() => {
    lastMapCanvasProps = null;
    lastMapFilterProps = null;
    dictState.dictionaries = buildDictionaries({
      regions: [],
      countries: [
        { code: "RU", title: { ru: "Россия" }, world_region_id: 500374 },
      ],
      cities: [
        { code: "MOW", title: { ru: "Москва" }, country_code: "RU", has_afl_flights: true, location: { lat: 55, lon: 37 } },
        { code: "LED", title: { ru: "Питер" }, country_code: "RU", has_afl_flights: true, location: { lat: 60, lon: 30 } },
        { code: "KZN", title: { ru: "Казань" }, country_code: "RU", has_afl_flights: true, location: { lat: 56, lon: 49 } },
      ],
      airports: [
        { code: "MOW", city_code: "MOW", title: { ru: "Шереметьево" }, has_afl_flights: true, location: { lat: 55, lon: 37 } },
        { code: "LED", city_code: "LED", title: { ru: "Пулково" }, has_afl_flights: true, location: { lat: 60, lon: 30 } },
        { code: "KZN", city_code: "KZN", title: { ru: "Казань-Аэропорт" }, has_afl_flights: true, location: { lat: 56, lon: 49 } },
      ],
    });
    dictState.loading = false;
    dictState.error = null;
    searchState.routes = [{ route: ["MOW", "LED"], isDirect: true }];
    searchState.loading = false;
    searchState.error = null;
    resetCrossSectionStore();
  });

  it("4.1.24-R36: first click sets departure in filter", () => {
    render(<FlightsMapStartPage />);
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("MOW");
    });
    const filterValue = lastMapFilterProps!["value"] as Record<string, unknown>;
    expect(filterValue["departure"]).toBe("MOW");
    expect(filterValue["arrival"]).toBeUndefined();
  });

  it("4.1.24-R37: second click sets arrival in filter", () => {
    render(<FlightsMapStartPage />);
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("MOW");
    });
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("LED");
    });
    const filterValue = lastMapFilterProps!["value"] as Record<string, unknown>;
    expect(filterValue["departure"]).toBe("MOW");
    expect(filterValue["arrival"]).toBe("LED");
  });

  it("4.1.24-R39: third click resets both and sets new departure", () => {
    render(<FlightsMapStartPage />);
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("MOW");
    });
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("LED");
    });
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("KZN");
    });
    const filterValue = lastMapFilterProps!["value"] as Record<string, unknown>;
    expect(filterValue["departure"]).toBe("KZN");
    expect(filterValue["arrival"]).toBeUndefined();
  });

  it("4.1.24-R39: third click returns to spider mode with connections reset", () => {
    searchState.routes = [{ route: ["MOW", "LED"], isDirect: true }];
    setMapFilter({
      departure: "MOW",
      arrival: "LED",
      date: null,
      showInternal: false,
      showInternational: false,
      showTransfers: true,
    });

    render(<FlightsMapStartPage />);
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("MOW");
    });

    const filterValue = lastMapFilterProps!["value"] as Record<string, unknown>;
    expect(filterValue["departure"]).toBe("MOW");
    expect(filterValue["arrival"]).toBeUndefined();
    expect(filterValue["connections"]).toBe(false);

    const polylines = lastMapCanvasProps!["polylines"] as Array<{ cityIds: string[] }>;
    expect(polylines).toHaveLength(1);
    expect(polylines[0]!.cityIds).toEqual(["MOW", "LED"]);
  });

  it("4.1.24-R37: buy-ticket popup appears after second click (arrival set)", () => {
    render(<FlightsMapStartPage />);
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("MOW");
    });
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("LED");
    });
    const popups = lastMapCanvasProps!["popups"] as unknown[];
    expect(popups.length).toBeGreaterThan(0);
  });

  it("4.1.24-R41: buy-ticket popup HTML does not appear in spider mode (arrival not set)", () => {
    render(<FlightsMapStartPage />);
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("MOW");
    });
    const popups = lastMapCanvasProps!["popups"] as unknown[];
    expect(popups).toEqual([]);
  });

  it("4.1.24-R48: popup buy-ticket URL omits date when filter.date is undefined", () => {
    render(<FlightsMapStartPage />);
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("MOW");
    });
    act(() => {
      (lastMapCanvasProps!["onMarkerClick"] as (id: string) => void)("LED");
    });
    const popups = lastMapCanvasProps!["popups"] as Array<{ content: string }>;
    // Date is undefined initially (no departure city auto-sets date in tests),
    // so the URL should use routes=MOW.LED (no date segment)
    const arrivalPopup = popups.find((p) => p.content.includes("routes="));
    if (arrivalPopup) {
      // Either no date (MOW.LED) or a date format (MOW.YYYYMMDD.LED) — both valid
      expect(arrivalPopup.content).toMatch(/routes=MOW\.((\d{8}\.)?LED)/);
    }
  });
});

// ---------------------------------------------------------------------------
// TZ §4.1.24.5 R44: routes API called with correct date range window
// ---------------------------------------------------------------------------

describe("§4.1.24.5 R44: routes API uses [-1d, +6mo] date window", () => {
  beforeEach(() => {
    lastMapFilterProps = null;
    searchCalls.length = 0;
    calendarCalls.length = 0;
    dictState.dictionaries = buildDictionaries();
    dictState.loading = false;
    dictState.error = null;
    searchState.routes = [];
    searchState.loading = false;
    searchState.error = null;
    resetCrossSectionStore();
  });

  it("4.1.24-R44: search not called when no departure is set", () => {
    render(<FlightsMapStartPage />);
    // With no departure, searchParams is null → hook called with null → no API call
    const callsWithDeparture = searchCalls.filter((p) => p?.departure);
    expect(callsWithDeparture).toHaveLength(0);
  });

  it("4.1.24-R44: dateFrom starts yesterday and dateTo spans to +6 months when departure is set", () => {
    resetCrossSectionStore();
    setMapFilter({
      departure: "MOW", arrival: null, date: null,
      showInternal: false, showInternational: false, showTransfers: false,
    });
    render(<FlightsMapStartPage today="20260506" />);

    const callsWithDeparture = searchCalls.filter((p) => p?.departure === "MOW");
    expect(callsWithDeparture).not.toHaveLength(0);
    expect(callsWithDeparture[0]).toMatchObject({
      departure: "MOW",
      dateFrom: "20260505",
      dateTo: "20261105",
    });
  });

  it("4.1.24-R44: calendar availability request starts from the same yesterday anchor", () => {
    resetCrossSectionStore();
    setMapFilter({
      departure: "MOW", arrival: null, date: null,
      showInternal: false, showInternational: false, showTransfers: false,
    });
    render(<FlightsMapStartPage today="20260506" />);

    const callsWithDeparture = calendarCalls.filter((p) => p?.departure === "MOW");
    expect(callsWithDeparture).not.toHaveLength(0);
    expect(callsWithDeparture[0]).toMatchObject({
      departure: "MOW",
      date: "20260505",
    });
  });
});
