import { useState } from "react";

// Central registry of every localStorage key the app uses. One place to see them all, rename
// safely, and avoid collisions — instead of `"siftlode.x"` string literals scattered across
// files. Parametric (per-user) keys are functions. All keys are namespaced `siftlode.*`.
export const LS = {
  theme: "siftlode.theme",
  sidebarLayout: "siftlode.sidebarLayout",
  hints: "siftlode.hints",
  lang: "siftlode.lang",
  filters: "siftlode.filters",
  page: "siftlode.page",
  perfMode: "siftlode.perfMode",
  channelFilter: "siftlode.channelFilter",
  channelsView: "siftlode.channelsView",
  settingsTab: "siftlode.settingsTab",
  statsTab: "siftlode.statsTab",
  adminUsersTab: "siftlode.adminUsersTab",
  navCollapsed: "siftlode.navCollapsed",
  playlist: "siftlode.playlist",
  plSort: "siftlode.plSort",
  notifHistory: "siftlode.notifications",
  notifSettings: "siftlode.notifSettings",
  onboardingDismissed: "siftlode.onboarding.dismissed",
  seenVersion: "siftlode.seenVersion",
  chatDock: (userId: number) => `siftlode.chatDock.${userId}`,
} as const;

// --- typed read/write helpers (do the JSON + try/catch once) ----------------------------

/** Read an object-shaped value merged over `defaults` (so missing/added keys are tolerated),
 * falling back to `defaults` on missing or corrupt data. */
export function readMerged<T extends object>(key: string, defaults: T): T {
  try {
    return { ...defaults, ...JSON.parse(localStorage.getItem(key) || "{}") };
  } catch {
    return { ...defaults };
  }
}

/** Read any JSON value (arrays/scalars), falling back on missing or corrupt data. */
export function readJSON<T>(key: string, fallback: T): T {
  try {
    const raw = localStorage.getItem(key);
    return raw == null ? fallback : (JSON.parse(raw) as T);
  } catch {
    return fallback;
  }
}

/** Persist a value as JSON, swallowing quota/serialization errors. */
export function writeJSON(key: string, value: unknown): void {
  try {
    localStorage.setItem(key, JSON.stringify(value));
  } catch {
    /* ignore quota / serialization errors */
  }
}

// --- reactive persisted string state ----------------------------------------------------

/** A `useState` whose string value is mirrored to localStorage, so it survives a reload (F5).
 * Validation is deferred to the caller (clamp at render) so it can be used before an
 * async-loaded option list is known, keeping hook order stable. Generalizes the per-component
 * "persisted active tab" pattern (Settings, Stats, admin pages, the channel filter…). */
export function usePersistedState(
  key: string,
  fallback = "",
): [string, (value: string) => void] {
  const [value, setValue] = useState<string>(() => localStorage.getItem(key) ?? fallback);
  const set = (next: string) => {
    setValue(next);
    try {
      localStorage.setItem(key, next);
    } catch {
      /* ignore */
    }
  };
  return [value, set];
}
