import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from "react";
import { useTranslation } from "react-i18next";
import { isPage, readPage, type Page } from "../lib/urlState";
import { pageTitleKey } from "../lib/pageMeta";
import { getAccountRaw, LS, setAccountRaw } from "../lib/storage";
import { setNavigator } from "../lib/nav";

// Owns the app's navigation + browser-history state, lifted out of the App god component so pages
// consume it via a hook instead of prop-drilling `page`/`setPage`/`onOpenChannel` through the tree.
// Mirrors ConfirmProvider (context + default value + one-line hooks); composed in main.tsx inside
// ConfirmProvider, around <App/>.
//
// TWO contexts on purpose: a STABLE actions context (setPage/openChannel/… never change reference)
// and a CHANGING state context (page/channelView/ytSearch). Context consumers re-render whenever the
// value changes, so deep memo'd leaves (VideoCard, PlayerModal) subscribe to actions ONLY and never
// re-render on a page/channel switch — preserving the memo that keeps feed appends from re-rendering
// every existing card.

const PAGE_KEY = LS.page;

export interface ChannelView {
  id: string;
  name?: string;
}

export interface NavigationState {
  page: Page;
  /** The open channel page (null = none). Overlays `page`; rides in history.state._chan. */
  channelView: ChannelView | null;
  /** Live YouTube search term (null = normal feed). A feed sub-view; rides in history.state._yt. */
  ytSearch: string | null;
}

export interface NavigationActions {
  setPage: (next: Page) => void;
  openChannel: (id: string, name?: string) => void;
  closeChannel: () => void;
  enterYtSearch: (q: string) => void;
  exitYtSearch: () => void;
}

function loadInitialPage(): Page {
  const params = new URLSearchParams(window.location.search);
  if (params.has("page")) return readPage();
  const stored = getAccountRaw(PAGE_KEY);
  return isPage(stored) ? stored : "feed";
}

const StateContext = createContext<NavigationState>({
  page: "feed",
  channelView: null,
  ytSearch: null,
});
const ActionsContext = createContext<NavigationActions>({
  setPage: () => {},
  openChannel: () => {},
  closeChannel: () => {},
  enterYtSearch: () => {},
  exitYtSearch: () => {},
});

export function useNavigation(): NavigationState {
  return useContext(StateContext);
}
export function useNavigationActions(): NavigationActions {
  return useContext(ActionsContext);
}

export function NavigationProvider({ children }: { children: ReactNode }) {
  const { t, i18n } = useTranslation();
  const [page, setPageState] = useState<Page>(loadInitialPage);
  // Ephemeral: not persisted and not in the URL, so a reload returns to the normal feed (a search
  // spends quota, so we never auto-replay it) — UNLESS it was zero-quota scrape-sourced, which rides
  // in history.state and is free to re-fetch. The popstate handler derives it from history.state so
  // Back steps player → search → feed instead of the search vanishing without a history entry.
  const [ytSearch, setYtSearch] = useState<string | null>(() => {
    const st = window.history.state;
    return st?._yt && st?._ytScrape ? (st._yt as string) : null;
  });
  // The open channel page rides in history.state (_chan = id, _chanName = a name to show before the
  // detail loads), so Back closes it and a reload restores it (unlike ytSearch, which is dropped).
  const [channelView, setChannelView] = useState<ChannelView | null>(() => {
    const c = window.history.state?._chan as string | undefined;
    return c ? { id: c, name: (window.history.state?._chanName as string) ?? undefined } : null;
  });

  // Refs so the STABLE action callbacks can read the latest state without depending on it — a dep
  // would recreate them on every navigation and re-render every actions consumer (e.g. VideoCard).
  // Written during render (the sanctioned "latest value" pattern): the ref is only READ in event
  // handlers after commit, and this provider never suspends or renders in a transition, so no
  // discarded render can leave it stale.
  const pageRef = useRef(page);
  pageRef.current = page;
  const channelViewRef = useRef(channelView);
  channelViewRef.current = channelView;

  const setPage = useCallback((next: Page) => {
    // A channel page overlays the content column, so a nav click must close it even when the
    // underlying page is unchanged (e.g. "Feed" while a channel opened from the feed).
    if (next === pageRef.current && !channelViewRef.current) return;
    setChannelView(null); // leave any open channel page when navigating via the rail
    setPageState(next);
    setAccountRaw(PAGE_KEY, next);
    // Push an in-app history entry so browser/mouse Back steps through pages instead of leaving the
    // app. The URL stays clean — the page rides in history.state. A fresh { sfPage } (no carried-over
    // _yt/_chan markers) so each page starts at its root.
    window.history.pushState({ sfPage: next }, "");
  }, []);

  const openChannel = useCallback((id: string, name?: string) => {
    setChannelView({ id, name });
    const st = window.history.state || {};
    if (st._chan) window.history.replaceState({ ...st, _chan: id, _chanName: name ?? null }, "");
    else window.history.pushState({ ...st, _chan: id, _chanName: name ?? null }, "");
  }, []);

  const closeChannel = useCallback(() => {
    if (window.history.state?._chan) window.history.back();
    else setChannelView(null);
  }, []);

  const enterYtSearch = useCallback((q: string) => {
    setYtSearch(q);
    // Drop any prior _ytScrape marker — the new search's source isn't known yet; Feed re-stamps it
    // once the results resolve, so a reload only restores a confirmed scrape-sourced search.
    const { _ytScrape: _drop, ...st } = window.history.state || {};
    if (st._yt)
      window.history.replaceState({ ...st, _yt: q }, ""); // refine current search
    else window.history.pushState({ ...st, sfPage: "feed", _yt: q }, ""); // new sub-view entry
  }, []);

  const exitYtSearch = useCallback(() => {
    // Pop our search entry (so Forward still works); fall back to a plain clear if we don't own one.
    if (window.history.state?._yt) window.history.back();
    else setYtSearch(null);
  }, []);

  // Expose setPage to decoupled callers (download toast "View", etc.) via the lib/nav singleton.
  useEffect(() => setNavigator(setPage), [setPage]);

  // Leaving the feed ends any live YouTube search, so coming back shows the normal feed.
  useEffect(() => {
    if (page !== "feed") setYtSearch(null);
  }, [page]);

  // Reflect the current module in the browser tab title. Feed = brand only; an open channel page
  // shows the channel name.
  useEffect(() => {
    const brand = "Siftlode";
    const label = channelView
      ? channelView.name || t("header.channelManager")
      : page === "feed"
        ? null
        : t(pageTitleKey(page));
    document.title = label ? `${label} · ${brand}` : brand;
  }, [page, channelView, i18n.language, t]);

  // In-app Back/Forward: stamp the initial entry with the current page, then sync state from
  // history.state on popstate. (stripUrlParams preserves history.state, so this stamp survives a
  // later query-string strip in App.)
  useEffect(() => {
    // A reload drops a stale _yt EXCEPT when scrape-sourced (zero quota): then keep _yt + _ytScrape
    // so it restores and re-fetches free (ytSearch above already initialised from it). _chan/_chanName
    // are always kept so a channel page survives F5.
    const st = window.history.state || {};
    if (st._yt && st._ytScrape) {
      window.history.replaceState({ ...st, sfPage: page }, "");
    } else {
      const { _yt: _staleYt, _ytScrape: _staleScrape, ...rest } = st;
      window.history.replaceState({ ...rest, sfPage: page }, "");
    }
    function onPop(e: PopStateEvent) {
      const p = (e.state?.sfPage as Page) ?? "feed";
      setPageState(p);
      setAccountRaw(PAGE_KEY, p);
      // The YouTube-search sub-view and the channel page ride in history.state, so Back/Forward
      // restores or clears them to match the entry we landed on.
      setYtSearch((e.state?._yt as string) ?? null);
      const chan = e.state?._chan as string | undefined;
      setChannelView(chan ? { id: chan, name: (e.state?._chanName as string) ?? undefined } : null);
    }
    window.addEventListener("popstate", onPop);
    return () => window.removeEventListener("popstate", onPop);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const state = useMemo<NavigationState>(
    () => ({ page, channelView, ytSearch }),
    [page, channelView, ytSearch],
  );
  const actions = useMemo<NavigationActions>(
    () => ({ setPage, openChannel, closeChannel, enterYtSearch, exitYtSearch }),
    [setPage, openChannel, closeChannel, enterYtSearch, exitYtSearch],
  );

  return (
    <ActionsContext.Provider value={actions}>
      <StateContext.Provider value={state}>{children}</StateContext.Provider>
    </ActionsContext.Provider>
  );
}
