/**
 * Safe localStorage helpers.
 *
 * Replaces the unsafe `JSON.parse(localStorage.getItem(key)!)` pattern,
 * which throws on missing keys, malformed JSON, or unexpected payloads.
 */

import { logError } from './log.ts';

/**
 * Read and validate a JSON value from localStorage.
 *
 * @param key Storage key.
 * @param guard Type guard validating the parsed value before returning it.
 * @returns The validated value or `null` if missing/invalid.
 */
export function readJson<T>(key: string, guard: (v: unknown) => v is T): T | null {
    let raw: string | null;
    try {
        raw = localStorage.getItem(key);
    } catch (err) {
        // Some embedded WebViews throw on storage access (private mode etc.)
        logError('storage.readJson', err);
        return null;
    }
    if (raw === null || raw === '') return null;
    let parsed: unknown;
    try {
        parsed = JSON.parse(raw);
    } catch (err) {
        logError(`storage.readJson:${key}`, err);
        return null;
    }
    return guard(parsed) ? parsed : null;
}

/** Best-effort write; logs but does not throw on quota/storage errors. */
export function writeJson(key: string, value: unknown): void {
    try {
        localStorage.setItem(key, JSON.stringify(value));
    } catch (err) {
        logError(`storage.writeJson:${key}`, err);
    }
}

/**
 * Parse a JSON string, returning ``fallback`` on any parse failure.
 *
 * Use for hot-path string-to-JSON conversions where a malformed payload
 * (corrupt WebSocket frame, stale ``data-*`` attribute, hand-edited
 * mapping in storage) would otherwise raise an uncaught exception.
 */
export function safeJsonParse<T = unknown>(raw: string | null | undefined, fallback: T): T {
    if (raw == null || raw === '') return fallback;
    try {
        return JSON.parse(raw) as T;
    } catch (err) {
        logError('safeJsonParse', err);
        return fallback;
    }
}

// ── Common type guards ────────────────────────────────────────

export function isObject(v: unknown): v is Record<string, unknown> {
    return typeof v === 'object' && v !== null && !Array.isArray(v);
}

export function isString(v: unknown): v is string {
    return typeof v === 'string';
}

export function isNumber(v: unknown): v is number {
    return typeof v === 'number' && Number.isFinite(v);
}

export function isStringRecord(v: unknown): v is Record<string, string> {
    if (!isObject(v)) return false;
    return Object.values(v).every(isString);
}
