/**
 * UX preferences persistence (localStorage) — docs/architecture/02 §5, spec §09D.
 *
 * These preferences persist **separately** from UserSettings so they can change
 * frequently (a drag emits many width updates) without rewriting the settings
 * record. Its own key, `astrolabe:ux-prefs`, holds the snippet sort preference
 * (spec §02 → Sort) and the panel layout (per-pane widths + visibility).
 *
 * This slice persists the panel **widths** and per-pane **visibility** (spec §01A).
 * Read-with-fallback + write-through merge, the same contract as the settings
 * adapter, so adding fields later upgrades cleanly. Widths and visibility are kept
 * as separate keys: a hidden pane retains its remembered width.
 *
 * Per the architecture rule this is one of the only modules that may touch
 * `localStorage`; everything else goes through these typed functions.
 */

import {
  DEFAULT_SORT_BY,
  DEFAULT_SORT_ORDER,
  type SortBy,
  type SortOrder,
} from '@core/snippet-sort';

const KEY = 'astrolabe:ux-prefs';

/** Per-pane widths (px). Optional — a missing field falls back to its default. */
export interface PanelLayout {
  libraryWidth?: number;
  previewWidth?: number;
  /** The editor's remembered width, so it re-shows at it when revealed (§01A). */
  editorWidth?: number;
}

/** Per-pane visibility. Optional — a missing field falls back to "shown". */
export interface PaneVisibilityPref {
  library?: boolean;
  editor?: boolean;
  preview?: boolean;
}

/** The persisted library sort (spec §02 → Sort; persists across sessions). */
export interface SnippetSortPref {
  by: SortBy;
  order: SortOrder;
}

interface StoredPrefs {
  panelLayout?: PanelLayout;
  paneVisibility?: PaneVisibilityPref;
  snippetSort?: Partial<SnippetSortPref>;
  [k: string]: unknown;
}

function available(): boolean {
  try {
    return typeof localStorage !== 'undefined' && typeof localStorage.getItem === 'function';
  } catch {
    return false;
  }
}

function readRaw(): StoredPrefs {
  if (!available()) return {};
  try {
    const raw = localStorage.getItem(KEY);
    if (!raw) return {};
    const parsed: unknown = JSON.parse(raw);
    return parsed && typeof parsed === 'object' ? (parsed as StoredPrefs) : {};
  } catch (err) {
    console.warn('[ux-prefs] failed to read, using defaults', err);
    return {};
  }
}

function writeRaw(next: StoredPrefs): void {
  if (!available()) return;
  try {
    localStorage.setItem(KEY, JSON.stringify(next));
  } catch (err) {
    console.warn('[ux-prefs] failed to write', err);
  }
}

/** A finite positive number, or undefined — guards against junk in storage. */
function posNumber(v: unknown): number | undefined {
  return typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : undefined;
}

/** The persisted panel layout, with only valid numeric widths surfaced. */
export function loadPanelLayout(): PanelLayout {
  const stored = readRaw().panelLayout ?? {};
  return {
    libraryWidth: posNumber(stored.libraryWidth),
    previewWidth: posNumber(stored.previewWidth),
    editorWidth: posNumber(stored.editorWidth),
  };
}

/** Persist the panel layout, preserving every other key already in the record. */
export function savePanelLayout(layout: PanelLayout): void {
  const current = readRaw();
  writeRaw({ ...current, panelLayout: { ...current.panelLayout, ...layout } });
}

/** A boolean, or undefined — guards against junk in storage. */
function bool(v: unknown): boolean | undefined {
  return typeof v === 'boolean' ? v : undefined;
}

/** The persisted per-pane visibility, with only valid booleans surfaced. */
export function loadPaneVisibility(): PaneVisibilityPref {
  const stored = readRaw().paneVisibility ?? {};
  return {
    library: bool(stored.library),
    editor: bool(stored.editor),
    preview: bool(stored.preview),
  };
}

/** Persist per-pane visibility, preserving every other key already in the record. */
export function savePaneVisibility(visibility: PaneVisibilityPref): void {
  const current = readRaw();
  writeRaw({ ...current, paneVisibility: { ...current.paneVisibility, ...visibility } });
}

/** Guards against junk in storage — only the known sort fields survive. */
function sortBy(v: unknown): SortBy | undefined {
  return v === 'modified' || v === 'created' || v === 'name' || v === 'size' ? v : undefined;
}
function sortOrder(v: unknown): SortOrder | undefined {
  return v === 'asc' || v === 'desc' ? v : undefined;
}

/**
 * The persisted library sort, falling back to the spec default (Modified, desc)
 * for anything missing or invalid in storage.
 */
export function loadSnippetSort(): SnippetSortPref {
  const stored = readRaw().snippetSort ?? {};
  return {
    by: sortBy(stored.by) ?? DEFAULT_SORT_BY,
    order: sortOrder(stored.order) ?? DEFAULT_SORT_ORDER,
  };
}

/** Persist the library sort, preserving every other key already in the record. */
export function saveSnippetSort(sort: SnippetSortPref): void {
  const current = readRaw();
  writeRaw({ ...current, snippetSort: { ...current.snippetSort, ...sort } });
}
