/**
 * Search history hook backed by sessionStorage via `@/shared/storage`.
 *
 * Session-scoped per TZ §4.1.9.5: "last searches within one session".
 * Cleared on tab close / page reload — never persists across sessions.
 * History is namespaced per language so switching locales shows the
 * appropriate history.
 *
 * Angular equivalent: `SearchHistoryService` (ClientApp/src/app/shared/
 * services/history/search-history.service.ts).
 *
 * @module
 */

import { useState, useCallback, useEffect } from "react";
import { z } from "zod";
import { sessionStore } from "@/shared/storage.js";

// Custom event fired whenever any hook instance mutates search history.
// Other live hook instances listen for it and re-read from storage so
// the sidebar on a search page stays in sync when the same route
// performs a follow-up search (React Router reuses the page component,
// the useState initializer does NOT re-run, and the cross-tab
// `storage` event doesn't fire for same-tab writes).
const SEARCH_HISTORY_CHANGED_EVENT = "afl:search-history-changed";

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

/** Search history item type — mirrors Angular's ISearchHistoryItemType */
export type SearchHistoryItemType =
  | "board-route"
  | "schedule-route"
  | "flight-number";

/**
 * Optional structured payload — lets the sidebar render the same rich
 * layout Angular ships (icon + city pair + date range), instead of a
 * single flat label. All fields are optional so legacy entries keep
 * working with `label` only.
 */
export interface SearchHistoryParams {
  /** IATA code of the departure (city or airport). */
  departure?: string;
  /** IATA code of the arrival (city or airport). */
  arrival?: string;
  /** yyyyMMdd outbound start date. */
  dateFrom?: string;
  /** yyyyMMdd outbound end date (schedule only). */
  dateTo?: string;
  /** yyyyMMdd inbound start (round-trip schedule). */
  inboundDateFrom?: string;
  /** yyyyMMdd inbound end (round-trip schedule). */
  inboundDateTo?: string;
  /** "HH:mm-HH:mm" departure time range. */
  timeRange?: string;
  /** Schedule: directOnly toggle (`connections === 0`). */
  directOnly?: boolean;
  /** Flight number for `flight-number` searches (e.g. "SU 1234"). */
  flightNumber?: string;
}

/** Search history item */
export interface SearchHistoryItem {
  type: SearchHistoryItemType;
  url: string;
  label: string;
  params?: SearchHistoryParams;
}

export interface UseSearchHistoryResult {
  items: SearchHistoryItem[];
  add: (item: SearchHistoryItem) => void;
  clear: () => void;
}

// ---------------------------------------------------------------------------
// Schema (for validated storage round-tripping)
// ---------------------------------------------------------------------------

const searchHistoryParamsSchema = z
  .object({
    departure: z.string().optional(),
    arrival: z.string().optional(),
    dateFrom: z.string().optional(),
    dateTo: z.string().optional(),
    inboundDateFrom: z.string().optional(),
    inboundDateTo: z.string().optional(),
    timeRange: z.string().optional(),
    directOnly: z.boolean().optional(),
    flightNumber: z.string().optional(),
  })
  .partial();

const searchHistoryItemSchema = z.object({
  type: z.enum(["board-route", "schedule-route", "flight-number"]),
  url: z.string(),
  label: z.string(),
  params: searchHistoryParamsSchema.optional(),
});

const searchHistorySchema = z.array(searchHistoryItemSchema);

// ---------------------------------------------------------------------------
// Storage key
// ---------------------------------------------------------------------------

// TIRREDESIGN-5 / TZ §4.1.8: each section (board vs schedule vs flight-number)
// retains up to 8 recent searches independently. A new board-route entry
// cannot push a schedule-route entry out of the list.
const MAX_PER_SECTION = 8;

function sectionFor(type: SearchHistoryItem["type"]): "board" | "schedule" | "flight-number" {
  if (type === "schedule-route") return "schedule";
  if (type === "flight-number") return "flight-number";
  return "board";
}

function storageKey(lang: string): string {
  return `history_${lang}`;
}

// ---------------------------------------------------------------------------
// Hook
// ---------------------------------------------------------------------------

/**
 * Hook for search history persisted to localStorage via `@/shared/storage`.
 *
 * @param lang - Current language code (e.g. "ru", "en"). History is
 *   namespaced per language so switching locales shows the appropriate
 *   history.
 */
export function useSearchHistory(lang: string): UseSearchHistoryResult {
  const key = storageKey(lang);

  const [items, setItems] = useState<SearchHistoryItem[]>(() => {
    return (sessionStore.get(key, searchHistorySchema) ?? []) as SearchHistoryItem[];
  });

  // Re-read storage whenever any instance broadcasts a change. Covers
  // the case where a search-results page persists the search via one
  // hook instance and the sidebar on the same mounted page uses a
  // separate hook instance whose initial useState() has already run.
  useEffect(() => {
    if (typeof window === "undefined") return;
    const handler = (): void => {
      const fresh =
        (sessionStore.get(key, searchHistorySchema) ?? []) as SearchHistoryItem[];
      setItems(fresh);
    };
    window.addEventListener(SEARCH_HISTORY_CHANGED_EVENT, handler);
    return () => window.removeEventListener(SEARCH_HISTORY_CHANGED_EVENT, handler);
  }, [key]);

  const add = useCallback(
    (item: SearchHistoryItem) => {
      setItems((prev) => {
        // Deduplicate by URL — don't add if the most recent entry matches
        if (prev[0]?.url === item.url) {
          return prev;
        }

        // Remove any existing entry with the same URL, then prepend.
        const filtered = prev.filter((existing) => existing.url !== item.url);
        const combined = [item, ...filtered];

        // Cap per section — the board/schedule/flight-number buckets each
        // hold up to MAX_PER_SECTION (8) entries independently. This keeps
        // the resulting list ordered by recency across sections but
        // prevents a burst of board searches from evicting schedule
        // history (and vice versa).
        const perSectionCount = { board: 0, schedule: 0, "flight-number": 0 };
        const next: SearchHistoryItem[] = [];
        for (const entry of combined) {
          const bucket = sectionFor(entry.type);
          if (perSectionCount[bucket] >= MAX_PER_SECTION) continue;
          perSectionCount[bucket]++;
          next.push(entry);
        }

        sessionStore.set(key, next, searchHistorySchema);
        if (typeof window !== "undefined") {
          window.dispatchEvent(new Event(SEARCH_HISTORY_CHANGED_EVENT));
        }
        return next;
      });
    },
    [key],
  );

  const clear = useCallback(() => {
    sessionStore.deleteNs(key);
    setItems([]);
    if (typeof window !== "undefined") {
      window.dispatchEvent(new Event(SEARCH_HISTORY_CHANGED_EVENT));
    }
  }, [key]);

  return { items, add, clear };
}
