import { useCallback, useEffect, useRef, useState } from 'react';
import type { HistoryMode } from '@/lib/url/historyPolicy';

export type { HistoryMode };

export interface UrlCodec<TState> {
  /** Parse current location -> fully-defaulted state. Pure, never throws. */
  parse: (loc: { pathname: string; search: string }) => TState;
  /** Serialize state -> full URL (path + query). Omits defaults. */
  build: (state: TState) => string;
}

export interface UseUrlFilterStateOpts<TState> {
  /** SSR-parsed initial state (hydration-safe source of truth on mount). */
  initial: TState;
  codec: UrlCodec<TState>;
  /** Fired ONLY by popstate re-parse so the page can refetch. */
  onUrlChange?: (state: TState) => void;
}

export interface UseUrlFilterStateReturn<TState> {
  state: TState;
  /** Merge overrides into state + write URL. mode is explicit (default 'replace'). */
  setUrlState: (overrides: Partial<TState>, mode?: HistoryMode) => void;
}

export function useUrlFilterState<TState extends Record<string, unknown>>(
  opts: UseUrlFilterStateOpts<TState>,
): UseUrlFilterStateReturn<TState> {
  const { initial, codec, onUrlChange } = opts;
  const [state, setState] = useState<TState>(() => initial);
  const stateRef = useRef(state);
  const codecRef = useRef(codec);
  const onUrlChangeRef = useRef(onUrlChange);

  useEffect(() => {
    stateRef.current = state;
  }, [state]);
  useEffect(() => {
    codecRef.current = codec;
  }, [codec]);
  useEffect(() => {
    onUrlChangeRef.current = onUrlChange;
  }, [onUrlChange]);

  // Mount reconcile — SILENT ONLY. Corrects client-only nav drift; never refetches.
  // Rationale (spec): /deals SSR seeds DB-canonical slug while parse() returns raw slug;
  // firing onUrlChange here would spuriously refetch = the load flicker we are eliminating.
  useEffect(() => {
    const parsed = codecRef.current.parse({
      pathname: window.location.pathname,
      search: window.location.search,
    });
    if (!shallowEqual(stateRef.current, parsed)) {
      stateRef.current = parsed;
      setState(parsed);
    }
  }, []);

  // popstate is the ONLY path that fires onUrlChange.
  useEffect(() => {
    const onPop = () => {
      const parsed = codecRef.current.parse({
        pathname: window.location.pathname,
        search: window.location.search,
      });
      stateRef.current = parsed;
      setState(parsed);
      onUrlChangeRef.current?.(parsed);
    };
    window.addEventListener('popstate', onPop);
    return () => window.removeEventListener('popstate', onPop);
  }, []);

  const setUrlState = useCallback((overrides: Partial<TState>, mode: HistoryMode = 'replace') => {
    const next = { ...stateRef.current, ...overrides };
    stateRef.current = next;
    const url = codecRef.current.build(next);
    if (mode === 'push') window.history.pushState({}, '', url);
    else window.history.replaceState({}, '', url);
    setState(next);
  }, []);

  return { state, setUrlState };
}

/** TState fields must be scalar or string[]; nested objects are not deep-compared and would always trigger mount-reconcile replace. */
function shallowEqual(a: Record<string, unknown>, b: Record<string, unknown>): boolean {
  const ak = Object.keys(a),
    bk = Object.keys(b);
  if (ak.length !== bk.length) return false;
  for (const k of ak) {
    const av = a[k],
      bv = b[k];
    if (Array.isArray(av) && Array.isArray(bv)) {
      if (av.length !== bv.length || av.some((x, i) => x !== bv[i])) return false;
    } else if (av !== bv) return false;
  }
  return true;
}
