import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
  type ReactNode,
} from "react";
import { qk } from "../lib/queryKeys";
import { useQuery } from "@tanstack/react-query";
import { api, getActiveAccount, type FeedFilters } from "../lib/api";
import { hasFilterParams, paramsToFilters } from "../lib/urlState";
import { accountKey, LS, readJSON, readMerged, writeJSON } from "../lib/storage";

// Owns the GLOBAL feed-filter state (the feed's scope/tags/content/sort/show), lifted out of the App
// god component so consumers read it via a hook instead of prop-drilling `filters`/`setFilters`
// through the tree. Mirrors NavigationProvider (split contexts + one-line hooks); composed in
// main.tsx inside NavigationProvider, around <App/>.
//
// TWO contexts on purpose (mirrors NavigationProvider): a STABLE actions context (setFilters never
// changes reference) and a CHANGING state context (the filters object). Context consumers re-render
// only when the context they read changes — so a component that dispatches filter changes without
// reading the current filters can subscribe to actions ONLY and skip re-rendering on every tweak.
//
// SCOPE NOTE: this owns the FEED's filters only. The channel detail page keeps its OWN isolated
// filter state (ChannelPage) — it renders a channel-scoped catalog, not the global feed — and the
// reusable <Feed> is parameterized by a `filters` prop so it can render either. Because the two live
// in structurally separate cells, opening a channel can never leak into the global feed (bug 2).

const DEFAULT_FILTERS: FeedFilters = {
  tags: [],
  tagMode: "or",
  channelIds: [],
  q: "",
  sort: "newest",
  scope: "my",
  includeNormal: true,
  includeShorts: false,
  includeLive: false,
  show: "unwatched",
};

const FILTERS_KEY = LS.filters;

// This account's own persisted filters (per-account keys — never another account's). A chosen
// default saved view wins over the last-session filters (SavedViewsWidget mirrors it here). When
// the account isn't known yet (first load, before the tab is pinned), start from defaults; the
// post-login effect loads the real ones once the id arrives.
function loadAccountFilters(id: number | null): FeedFilters {
  // Your last-applied filters win, so a reload keeps whatever view you're on (a saved view you
  // picked, or manual tweaks) — setFilters persists them under LS.filters on every change. The
  // starred default view only SEEDS a fresh account that has never stored filters: it drives the
  // very first load, after which that state persists like any other. (Re-apply the default view
  // from the sidebar to return to it.)
  const fKey = accountKey(FILTERS_KEY, id);
  let hasStored = false;
  try {
    hasStored = !!fKey && localStorage.getItem(fKey) != null;
  } catch {
    /* localStorage may be unavailable */
  }
  if (hasStored && fKey) return readMerged(fKey, DEFAULT_FILTERS);
  const dvKey = accountKey(LS.defaultViewFilters, id);
  const def = dvKey ? readJSON<FeedFilters | null>(dvKey, null) : null;
  if (def) return { ...DEFAULT_FILTERS, ...def };
  return { ...DEFAULT_FILTERS };
}

// URL wins over localStorage so a pasted link reproduces the exact view.
function loadInitialFilters(): FeedFilters {
  const params = new URLSearchParams(window.location.search);
  if (hasFilterParams(params)) return paramsToFilters(params, DEFAULT_FILTERS);
  return loadAccountFilters(getActiveAccount());
}

interface FeedFiltersActions {
  setFilters: (next: FeedFilters) => void;
}

const StateContext = createContext<FeedFilters>(DEFAULT_FILTERS);
const ActionsContext = createContext<FeedFiltersActions>({ setFilters: () => {} });

export function useFeedFilters(): FeedFilters {
  return useContext(StateContext);
}
export function useFeedFiltersActions(): FeedFiltersActions {
  return useContext(ActionsContext);
}

export function FeedFiltersProvider({ children }: { children: ReactNode }) {
  const [filters, setFiltersState] = useState<FeedFilters>(loadInitialFilters);
  // Whether this load arrived via a "Share view" link (captured once, before the URL is stripped)
  // — such filters win over the account's stored view and are persisted to the account instead.
  const [initialHadUrlFilters] = useState(() =>
    hasFilterParams(new URLSearchParams(window.location.search)),
  );

  // Passive observer: App owns the fetching `qk.me()` query (gated on setup state); enabled:false
  // here reads its cache and re-renders when it lands, without triggering a second fetch (which
  // could 503 in setup mode). We only need id + is_demo, which don't change on background refetch.
  const meQuery = useQuery({ queryKey: qk.me(), queryFn: api.me, enabled: false });

  const setFilters = useCallback((next: FeedFilters) => {
    setFiltersState(next);
    // Persist under THIS tab's active account so filters don't bleed between accounts.
    const key = accountKey(FILTERS_KEY, getActiveAccount());
    if (key) writeJSON(key, next);
  }, []);

  // Load THIS account's own filters when the account first resolves or changes (login / switch /
  // add). Filters are per-account localStorage state, so another account's last view (incl. its
  // default saved view) must never carry over. A share link's filters (captured at mount) win and
  // are persisted to the account instead. Runs on id change only, so a 60s background refetch of
  // the same account never clobbers the filters you're editing.
  useEffect(() => {
    const id = meQuery.data?.id;
    if (id == null) return;
    if (initialHadUrlFilters) {
      const key = accountKey(FILTERS_KEY, id);
      if (key) writeJSON(key, filters);
      return;
    }
    let f = loadAccountFilters(id);
    // The shared demo account has no subscriptions, so default it to the whole library.
    if (meQuery.data?.is_demo) f = { ...f, scope: "all" };
    setFiltersState(f);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [meQuery.data?.id]);

  const actions = useMemo<FeedFiltersActions>(() => ({ setFilters }), [setFilters]);

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