import { useCallback, useMemo, useRef } from 'react';
import { useGlobal } from './useGlobal';

// ── State that belongs to *this tab*, and survives a refresh ──
// `useGlobal` is the shared in-memory store: every consumer of a key re-renders together, with no
// provider. What it cannot do is outlive the document — a reload starts from the initial value again.
// `useLocalStorageState` outlives it but is shared by every tab at once, which is wrong for anything
// describing *this window* (which panel you maximised, what you named the tab): two windows would fight
// over one value.
//
// sessionStorage is the per-tab store — separate per tab, survives a reload and in-place navigation,
// discarded when the tab closes. So: `useGlobal` for the live value, sessionStorage as the write-through
// copy that the next document reads back. Storage can throw outright (private mode, storage disabled),
// and no piece of UI state is worth a crash, so every access fails soft to "not persisted".

function readStored<T>(key: string): { hit: true; value: T } | { hit: false } {
  try {
    const raw = sessionStorage.getItem(key);
    if (raw === null) return { hit: false };
    try {
      return { hit: true, value: JSON.parse(raw) as T };
    } catch {
      // A value written before this key was JSON-encoded. Strings round-trip; anything else was garbage
      // already.
      return { hit: true, value: raw as unknown as T };
    }
  } catch {
    return { hit: false };
  }
}

/** Write a session value from outside React — for module-level code that has no hook to call. */
export function writeSessionValue<T>(key: string, value: T): void {
  try {
    sessionStorage.setItem(key, JSON.stringify(value));
  } catch {
    /* the value just doesn't persist */
  }
}

/** Read a session value from outside React. Returns `fallback` when nothing is stored. */
export function readSessionValue<T>(key: string, fallback: T): T {
  const stored = readStored<T>(key);
  return stored.hit ? stored.value : fallback;
}

export function clearSessionValue(key: string): void {
  try {
    sessionStorage.removeItem(key);
  } catch {
    /* nothing to clear */
  }
}

/**
 * `useGlobal`, but the value comes back after a refresh.
 *
 * Same shape as `useState`: a value, a setter that accepts an updater, and a `reset` that forgets the
 * stored copy and returns to `initialValue`. `key` is the sessionStorage key verbatim — scope it
 * yourself (`` `FULLSCREEN_PANEL:${dashboardId}` ``) when one screen needs one value per thing.
 *
 * `null` is a real stored value, not an absence: setting it persists "explicitly nothing", which is the
 * difference between a panel you took back out of full screen and one you never put there.
 */
export function useSessionState<T>(key: string, initialValue: T) {
  // Read the stored value once per mount rather than on every render — `useGlobal` re-evaluates its
  // initialData each time, and only the first result is ever used. `initialValue` is deliberately not a
  // dependency: it is the value for a key with nothing stored, and re-reading on a new identity of the
  // same default would only churn.
  const initial = useMemo(() => readSessionValue(key, initialValue), [key]);
  const [value, setGlobal] = useGlobal<T>(['SESSION_STATE', key], initial);

  // Both are rebuilt every render, so they are read through refs — as dependencies they would give the
  // setter a new identity on every render and defeat every memo downstream.
  const valueRef = useRef(value);
  valueRef.current = value;
  const setGlobalRef = useRef(setGlobal);
  setGlobalRef.current = setGlobal;
  const initialValueRef = useRef(initialValue);
  initialValueRef.current = initialValue;

  const setValue = useCallback(
    (arg: React.SetStateAction<T>) => {
      const next = typeof arg === 'function' ? (arg as (prev: T) => T)(valueRef.current) : arg;
      writeSessionValue(key, next);
      setGlobalRef.current(next);
    },
    [key],
  );

  // Read through the ref, not the closure: resetting means "whatever the default is now", and a
  // `useCallback` over `initialValue` would freeze the default from the render that built the callback.
  const reset = useCallback(() => {
    clearSessionValue(key);
    setGlobalRef.current(initialValueRef.current);
  }, [key]);

  return [value, setValue, reset] as const;
}
