/**
 * Dashboard layout — schema, defaults, presets, and persistence for the
 * customizable dashboard.
 *
 * Storage strategy:
 *   - localStorage `dashboard_layout_v1` is the cache (instant first-paint).
 *   - Server `GET/PUT /preferences/dashboard-layout` is the source of truth
 *     across browsers; pulled after auth, replaces local on mismatch.
 *   - Save path: PUT to server -> localStorage cache -> notify subscribers.
 *
 * Schema is intentionally an open registry: section/cell `key`s are strings,
 * not a closed enum. New cards can be added in v1.1+ (audio meters, alerts,
 * preview strips, etc.) without a schema bump or migration.
 */
import { fetchWithAuth } from '../core/api.ts';

const LS_KEY = 'dashboard_layout_v1';
const SCHEMA_VERSION = 1;

export type SectionKey =
    | 'perf'
    | 'integrations'
    | 'automations'
    | 'scenes'
    | 'sync-clocks'
    | 'targets'
    // Reserved registry keys for v1.1+ (so saved layouts forward-compat).
    | 'audio-meters'
    | 'alerts'
    | 'led-preview'
    | 'source-thumbs'
    | 'pinned'
    | 'flow';

export type PerfCellKey =
    | 'patches'
    | 'fps'
    | 'capture_fps'
    | 'capture_fps_actual'
    | 'errors'
    | 'devices'
    | 'cpu'
    | 'ram'
    | 'gpu'
    | 'temp'
    | 'network'
    | 'device_latency'
    | 'send_timing'
    // Reserved.
    | 'disk'
    | 'audio-peak';

export type Density = 'comfortable' | 'compact' | 'dense';
export type PerfMode = 'system' | 'app' | 'both' | 'inherit';
export type YScale = 'auto' | 'fixed' | 'log';
export type SampleWindow = 30 | 60 | 120 | 300;
export type Width = 'full' | 'centered' | 'narrow';
export type AccentSource = 'target' | 'palette' | 'mono';
export type AnimationsLevel = 'full' | 'reduced' | 'off';
export type EmptyStateMode = 'hide' | 'cta' | 'skeleton';
export type ToolbarPos = 'top' | 'bottom' | 'floating';

export interface SectionConfig {
    key: string;
    visible: boolean;
    collapsedDefault: boolean;
    density: Density;
    /** Per-section options (sort, filters, etc.). Versioned per-section
     *  via `_v` so we can migrate one section without touching others. */
    options: Record<string, unknown>;
}

export interface PerfCellConfig {
    key: string;
    visible: boolean;
    /** `inherit` defers to the global perf mode (system/app/both); a
     *  per-cell value pins that cell to one mode regardless of global. */
    mode: PerfMode;
    span: 1 | 2;
    /** `'inherit'` defers to `global.window`; a numeric value pins the
     *  cell's spark to that sample window regardless of global. */
    window: SampleWindow | 'inherit';
    yScale: YScale;
    precision: 0 | 1 | 2;
    showSubtitle: boolean;
    showRefLine: boolean;
    colorOverride?: string;
}

export interface GlobalConfig {
    width: Width;
    accent: AccentSource;
    animations: AnimationsLevel;
    emptyState: EmptyStateMode;
    toolbarPosition: ToolbarPos;
    autoCollapseRunningEmpty: boolean;
    showTutorial: boolean;
    /** Global perf mode default — used when a cell has `mode: 'inherit'`. */
    perfMode: 'system' | 'app' | 'both';
    /** Global spark sample-window default in seconds — used when a cell
     *  has `window: 'inherit'`. */
    perfWindow: SampleWindow;
    /** Poll interval for the perf strip + dashboard refresh, milliseconds. */
    pollMs: number;
}

export interface DashboardLayoutV1 {
    version: 1;
    sections: SectionConfig[];
    perfCells: PerfCellConfig[];
    global: GlobalConfig;
    /** Active preset key when the layout matches a built-in unmodified.
     *  Cleared on any user edit so the panel can show "modified" state. */
    presetActive?: string;
}

const _defaultSection = (key: string, visible = true): SectionConfig => ({
    key,
    visible,
    collapsedDefault: false,
    density: 'comfortable',
    options: {},
});

const _defaultPerfCell = (key: string, visible = true): PerfCellConfig => ({
    key,
    visible,
    mode: 'inherit',
    span: 1,
    window: 'inherit',
    yScale: 'auto',
    precision: 1,
    showSubtitle: true,
    showRefLine: true,
});

export const DEFAULT_LAYOUT: DashboardLayoutV1 = {
    version: SCHEMA_VERSION,
    sections: [
        _defaultSection('perf'),
        _defaultSection('integrations'),
        _defaultSection('automations'),
        _defaultSection('scenes'),
        _defaultSection('sync-clocks'),
        _defaultSection('targets'),
    ],
    perfCells: [
        _defaultPerfCell('patches'),
        _defaultPerfCell('fps'),
        _defaultPerfCell('capture_fps'),
        _defaultPerfCell('capture_fps_actual', false),
        _defaultPerfCell('errors'),
        _defaultPerfCell('devices'),
        _defaultPerfCell('cpu'),
        _defaultPerfCell('ram'),
        _defaultPerfCell('gpu'),
        _defaultPerfCell('temp', false),
        _defaultPerfCell('network', false),
        _defaultPerfCell('device_latency', false),
        _defaultPerfCell('send_timing', false),
    ],
    global: {
        width: 'full',
        accent: 'target',
        animations: 'full',
        emptyState: 'hide',
        toolbarPosition: 'top',
        autoCollapseRunningEmpty: false,
        showTutorial: true,
        perfMode: 'both',
        perfWindow: 120,
        pollMs: 1000,
    },
    presetActive: 'studio',
};

/** Built-in presets — each is a complete layout the user can apply with one
 *  click. Stored as functions so they always produce a fresh object (no
 *  shared mutable references). */
export const PRESETS: Record<string, () => DashboardLayoutV1> = {
    studio: () => _clone(DEFAULT_LAYOUT, 'studio'),

    operator: () => {
        const l = _clone(DEFAULT_LAYOUT, 'operator');
        const hide = new Set(['integrations', 'scenes', 'sync-clocks']);
        l.sections = l.sections.map(s => hide.has(s.key) ? { ...s, visible: false } : s);
        l.sections = l.sections.map(s => ({ ...s, density: 'compact' }));
        return l;
    },

    showrunner: () => {
        const l = _clone(DEFAULT_LAYOUT, 'showrunner');
        const hide = new Set(['perf', 'integrations']);
        l.sections = l.sections.map(s => hide.has(s.key) ? { ...s, visible: false } : s);
        l.sections = l.sections.map(s => ({ ...s, density: 'compact' }));
        return l;
    },

    diagnostics: () => {
        const l = _clone(DEFAULT_LAYOUT, 'diagnostics');
        l.perfCells = l.perfCells.map(c => ({
            ...c,
            visible: true,
            window: 'inherit',
            showSubtitle: true,
            showRefLine: true,
        }));
        l.global = { ...l.global, perfMode: 'both', perfWindow: 300, pollMs: 500 };
        return l;
    },

    tv: () => {
        const l = _clone(DEFAULT_LAYOUT, 'tv');
        l.sections = l.sections.map(s => ({ ...s, density: 'dense' }));
        const keep = new Set(['perf', 'targets']);
        l.sections = l.sections.map(s => keep.has(s.key) ? s : { ...s, visible: false });
        l.global = { ...l.global, width: 'centered', toolbarPosition: 'top' };
        return l;
    },
};

function _clone(layout: DashboardLayoutV1, presetActive?: string): DashboardLayoutV1 {
    return {
        version: layout.version,
        sections: layout.sections.map(s => ({ ...s, options: { ...s.options } })),
        perfCells: layout.perfCells.map(c => ({ ...c })),
        global: { ...layout.global },
        presetActive,
    };
}

/** Structural deep-equal that ignores `undefined` properties and key order.
 *  Needed because a saved layout coming back from JSON.parse may omit
 *  optional fields (e.g. `colorOverride`) that the factory output also
 *  omits — but a naive `JSON.stringify` comparison can still differ if
 *  insertion order ever drifts. */
function _deepEqual(a: unknown, b: unknown): boolean {
    if (a === b) return true;
    if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') {
        return false;
    }
    if (Array.isArray(a) || Array.isArray(b)) {
        if (!Array.isArray(a) || !Array.isArray(b)) return false;
        if (a.length !== b.length) return false;
        for (let i = 0; i < a.length; i++) {
            if (!_deepEqual(a[i], b[i])) return false;
        }
        return true;
    }
    const ao = a as Record<string, unknown>;
    const bo = b as Record<string, unknown>;
    const aKeys = Object.keys(ao).filter(k => ao[k] !== undefined);
    const bKeys = Object.keys(bo).filter(k => bo[k] !== undefined);
    if (aKeys.length !== bKeys.length) return false;
    for (const k of aKeys) {
        if (!_deepEqual(ao[k], bo[k])) return false;
    }
    return true;
}

/** Detect which preset (if any) the layout currently matches. Recomputed
 *  on every save/load so the "modified" hint is the truth, not a flag that
 *  drifts when a user edits then reverts a setting.
 *
 *  Compares only the data-bearing fields — `presetActive` itself is ignored,
 *  since it's the value we're computing. */
function _computeActivePreset(layout: DashboardLayoutV1): string | undefined {
    for (const [name, factory] of Object.entries(PRESETS)) {
        const p = factory();
        if (
            layout.version === p.version &&
            _deepEqual(layout.global, p.global) &&
            _deepEqual(layout.sections, p.sections) &&
            _deepEqual(layout.perfCells, p.perfCells)
        ) {
            return name;
        }
    }
    return undefined;
}

/** Diagnostic: list per-preset mismatches for the current layout. Exposed
 *  on `window.__dashboardLayoutDiff` so a user reporting "MODIFIED with no
 *  changes" can run it from DevTools and see exactly which field drifted. */
function _diffAgainstPresets(layout: DashboardLayoutV1): Record<string, string[]> {
    const out: Record<string, string[]> = {};
    for (const [name, factory] of Object.entries(PRESETS)) {
        const p = factory();
        const diffs: string[] = [];
        if (layout.version !== p.version) diffs.push(`version: ${layout.version} vs ${p.version}`);
        const asRec = (o: object): Record<string, unknown> => o as unknown as Record<string, unknown>;
        if (!_deepEqual(layout.global, p.global)) {
            for (const k of Object.keys({ ...layout.global, ...p.global })) {
                const lv = asRec(layout.global)[k];
                const pv = asRec(p.global)[k];
                if (!_deepEqual(lv, pv)) diffs.push(`global.${k}: ${JSON.stringify(lv)} vs ${JSON.stringify(pv)}`);
            }
        }
        if (!_deepEqual(layout.sections, p.sections)) {
            const lKeys = layout.sections.map(s => s.key).join(',');
            const pKeys = p.sections.map(s => s.key).join(',');
            if (lKeys !== pKeys) diffs.push(`sections.order: [${lKeys}] vs [${pKeys}]`);
            for (const ls of layout.sections) {
                const ps = p.sections.find(x => x.key === ls.key);
                if (!ps) { diffs.push(`sections.${ls.key}: extra`); continue; }
                if (!_deepEqual(ls, ps)) {
                    for (const k of Object.keys({ ...ls, ...ps })) {
                        const lv = asRec(ls)[k];
                        const pv = asRec(ps)[k];
                        if (!_deepEqual(lv, pv)) diffs.push(`sections.${ls.key}.${k}: ${JSON.stringify(lv)} vs ${JSON.stringify(pv)}`);
                    }
                }
            }
        }
        if (!_deepEqual(layout.perfCells, p.perfCells)) {
            const lKeys = layout.perfCells.map(c => c.key).join(',');
            const pKeys = p.perfCells.map(c => c.key).join(',');
            if (lKeys !== pKeys) diffs.push(`perfCells.order: [${lKeys}] vs [${pKeys}]`);
            for (const lc of layout.perfCells) {
                const pc = p.perfCells.find(x => x.key === lc.key);
                if (!pc) { diffs.push(`perfCells.${lc.key}: extra`); continue; }
                if (!_deepEqual(lc, pc)) {
                    for (const k of Object.keys({ ...lc, ...pc })) {
                        const lv = asRec(lc)[k];
                        const pv = asRec(pc)[k];
                        if (!_deepEqual(lv, pv)) diffs.push(`perfCells.${lc.key}.${k}: ${JSON.stringify(lv)} vs ${JSON.stringify(pv)}`);
                    }
                }
            }
        }
        out[name] = diffs;
    }
    return out;
}

if (typeof window !== 'undefined') {
    (window as unknown as Record<string, unknown>).__dashboardLayoutDiff = () => _diffAgainstPresets(_current);
}

let _current: DashboardLayoutV1 = _clone(DEFAULT_LAYOUT, 'studio');
let _serverSyncedOnce = false;
const _listeners = new Set<() => void>();
let _saveTimer: ReturnType<typeof setTimeout> | null = null;

/** Read the current layout. Always returns a defensive copy so callers
 *  can't mutate it directly — mutations must go through `saveDashboardLayout`. */
export function getDashboardLayout(): DashboardLayoutV1 {
    return _clone(_current, _current.presetActive);
}

/** Subscribe to layout changes. Returns an unsubscribe function. */
export function subscribeDashboardLayout(fn: () => void): () => void {
    _listeners.add(fn);
    return () => _listeners.delete(fn);
}

function _notify(): void {
    for (const fn of _listeners) {
        try { fn(); } catch (e) { console.error('dashboard layout listener', e); }
    }
}

/** Hydrate from localStorage cache (synchronous, for first-paint). Falls
 *  back to defaults + legacy-key migration if no cached layout exists. */
export function hydrateDashboardLayoutFromCache(): DashboardLayoutV1 {
    try {
        const raw = localStorage.getItem(LS_KEY);
        if (raw) {
            const parsed = JSON.parse(raw);
            const merged = _mergeWithDefaults(parsed);
            _current = merged;
            return merged;
        }
    } catch (e) {
        console.warn('dashboard layout cache parse failed', e);
    }
    // No cache — pull from legacy keys so first migration is seamless.
    _current = _migrateFromLegacyKeys();
    try { localStorage.setItem(LS_KEY, JSON.stringify(_current)); } catch { /* quota */ }
    return _clone(_current, _current.presetActive);
}

/** Pull layout from server after auth. Replaces local cache if server has
 *  a saved layout, otherwise pushes the local cache up. Safe to call
 *  before login (will no-op on auth error). */
export async function syncDashboardLayoutFromServer(): Promise<void> {
    if (_serverSyncedOnce) return;
    try {
        const resp = await fetchWithAuth('/preferences/dashboard-layout');
        if (!resp || !resp.ok) return;
        const data = await resp.json();
        if (data && typeof data === 'object' && data.version) {
            const merged = _mergeWithDefaults(data);
            _current = merged;
            try { localStorage.setItem(LS_KEY, JSON.stringify(_current)); } catch { /* quota */ }
            _notify();
        } else {
            // Server has nothing — push our cached/default layout up.
            await _pushToServer(_current);
        }
        _serverSyncedOnce = true;
    } catch (e) {
        // Network or auth failure — keep using cache.
        console.warn('dashboard layout server sync failed', e);
    }
}

/** Persist a layout. Updates in-memory state immediately, debounces
 *  the network write, and notifies listeners synchronously. */
export function saveDashboardLayout(next: DashboardLayoutV1): void {
    _current = _clone(next, _computeActivePreset(next));
    try { localStorage.setItem(LS_KEY, JSON.stringify(_current)); } catch { /* quota */ }
    _notify();
    if (_saveTimer) clearTimeout(_saveTimer);
    _saveTimer = setTimeout(() => {
        _saveTimer = null;
        _pushToServer(_current).catch(e => console.warn('dashboard layout server PUT failed', e));
    }, 300);
}

async function _pushToServer(layout: DashboardLayoutV1): Promise<void> {
    try {
        await fetchWithAuth('/preferences/dashboard-layout', {
            method: 'PUT',
            body: JSON.stringify(layout),
        });
    } catch (e) {
        console.warn('dashboard layout PUT failed', e);
    }
}

/** Apply a built-in preset and persist it. */
export function applyDashboardPreset(name: string): void {
    const factory = PRESETS[name];
    if (!factory) return;
    saveDashboardLayout(factory());
}

/** Reset to the studio default. */
export function resetDashboardLayout(): void {
    saveDashboardLayout(PRESETS.studio());
}

/** Export the current layout as a downloadable JSON string. */
export function exportDashboardLayoutJson(): string {
    return JSON.stringify(_current, null, 2);
}

/** Import a JSON layout string. Returns true on success. */
export function importDashboardLayoutJson(json: string): boolean {
    try {
        const parsed = JSON.parse(json);
        if (!parsed || typeof parsed !== 'object') return false;
        const merged = _mergeWithDefaults(parsed);
        merged.presetActive = undefined;
        saveDashboardLayout(merged);
        return true;
    } catch (e) {
        console.warn('dashboard layout import failed', e);
        return false;
    }
}

// ── Helpers exposed to other modules ─────────────────────────────────────

export function getOrderedSections(): SectionConfig[] {
    return _current.sections.map(s => ({ ...s, options: { ...s.options } }));
}

export function getOrderedPerfCells(): PerfCellConfig[] {
    return _current.perfCells.map(c => ({ ...c }));
}

export function getSection(key: string): SectionConfig | undefined {
    const s = _current.sections.find(s => s.key === key);
    return s ? { ...s, options: { ...s.options } } : undefined;
}

export function getPerfCell(key: string): PerfCellConfig | undefined {
    const c = _current.perfCells.find(c => c.key === key);
    return c ? { ...c } : undefined;
}

export function isSectionVisible(key: string): boolean {
    return _current.sections.find(s => s.key === key)?.visible ?? true;
}

export function isPerfCellVisible(key: string): boolean {
    return _current.perfCells.find(c => c.key === key)?.visible ?? true;
}

export function getGlobalConfig(): GlobalConfig {
    return { ..._current.global };
}

/** Effective perf mode for a given cell — resolves `inherit`. */
export function effectivePerfMode(cellKey: string): 'system' | 'app' | 'both' {
    const cell = _current.perfCells.find(c => c.key === cellKey);
    if (!cell || cell.mode === 'inherit') return _current.global.perfMode;
    return cell.mode;
}

/** Effective spark window for a given cell — resolves `inherit`. */
export function effectivePerfWindow(cellKey: string): SampleWindow {
    const cell = _current.perfCells.find(c => c.key === cellKey);
    if (!cell || cell.window === 'inherit') return _current.global.perfWindow;
    return cell.window;
}

// ── Mutation helpers — return a new layout, don't persist ────────────────

export function setSectionVisible(layout: DashboardLayoutV1, key: string, visible: boolean): DashboardLayoutV1 {
    const next = _clone(layout);
    const s = next.sections.find(s => s.key === key);
    if (s) s.visible = visible;
    next.presetActive = undefined;
    return next;
}

export function setSectionOrder(layout: DashboardLayoutV1, orderedKeys: string[]): DashboardLayoutV1 {
    const next = _clone(layout);
    const map = new Map(next.sections.map(s => [s.key, s]));
    const reordered: SectionConfig[] = [];
    for (const k of orderedKeys) {
        const s = map.get(k);
        if (s) { reordered.push(s); map.delete(k); }
    }
    // Append any sections not in the order list (e.g. new registry entries).
    for (const s of map.values()) reordered.push(s);
    next.sections = reordered;
    next.presetActive = undefined;
    return next;
}

export function setSectionDensity(layout: DashboardLayoutV1, key: string, density: Density): DashboardLayoutV1 {
    const next = _clone(layout);
    const s = next.sections.find(s => s.key === key);
    if (s) s.density = density;
    next.presetActive = undefined;
    return next;
}

export function setSectionCollapsedDefault(layout: DashboardLayoutV1, key: string, collapsed: boolean): DashboardLayoutV1 {
    const next = _clone(layout);
    const s = next.sections.find(s => s.key === key);
    if (s) s.collapsedDefault = collapsed;
    next.presetActive = undefined;
    return next;
}

export function setPerfCellVisible(layout: DashboardLayoutV1, key: string, visible: boolean): DashboardLayoutV1 {
    const next = _clone(layout);
    const c = next.perfCells.find(c => c.key === key);
    if (c) c.visible = visible;
    next.presetActive = undefined;
    return next;
}

export function setPerfCellOrder(layout: DashboardLayoutV1, orderedKeys: string[]): DashboardLayoutV1 {
    const next = _clone(layout);
    const map = new Map(next.perfCells.map(c => [c.key, c]));
    const reordered: PerfCellConfig[] = [];
    for (const k of orderedKeys) {
        const c = map.get(k);
        if (c) { reordered.push(c); map.delete(k); }
    }
    for (const c of map.values()) reordered.push(c);
    next.perfCells = reordered;
    next.presetActive = undefined;
    return next;
}

export function setPerfCellMode(layout: DashboardLayoutV1, key: string, mode: PerfMode): DashboardLayoutV1 {
    const next = _clone(layout);
    const c = next.perfCells.find(c => c.key === key);
    if (c) c.mode = mode;
    next.presetActive = undefined;
    return next;
}

export function setPerfCellWindow(layout: DashboardLayoutV1, key: string, window: SampleWindow | 'inherit'): DashboardLayoutV1 {
    const next = _clone(layout);
    const c = next.perfCells.find(c => c.key === key);
    if (c) c.window = window;
    next.presetActive = undefined;
    return next;
}

export function setGlobalPerfWindow(layout: DashboardLayoutV1, window: SampleWindow): DashboardLayoutV1 {
    const next = _clone(layout);
    next.global.perfWindow = window;
    next.presetActive = undefined;
    return next;
}

export function setPerfCellYScale(layout: DashboardLayoutV1, key: string, yScale: YScale): DashboardLayoutV1 {
    const next = _clone(layout);
    const c = next.perfCells.find(c => c.key === key);
    if (c) c.yScale = yScale;
    next.presetActive = undefined;
    return next;
}

export function setGlobalPerfMode(layout: DashboardLayoutV1, mode: 'system' | 'app' | 'both'): DashboardLayoutV1 {
    const next = _clone(layout);
    next.global.perfMode = mode;
    next.presetActive = undefined;
    return next;
}

export function setGlobalConfig(layout: DashboardLayoutV1, patch: Partial<GlobalConfig>): DashboardLayoutV1 {
    const next = _clone(layout);
    next.global = { ...next.global, ...patch };
    next.presetActive = undefined;
    return next;
}

// ── Internal: merge / migrate ────────────────────────────────────────────

/** Merge a (possibly partial or older) layout with current defaults. New
 *  registry keys not in the saved layout are appended to the end with
 *  default settings; unknown keys in the saved layout are dropped. */
function _mergeWithDefaults(input: unknown): DashboardLayoutV1 {
    const base = _clone(DEFAULT_LAYOUT);
    if (!input || typeof input !== 'object') return base;
    const obj = input as Partial<DashboardLayoutV1>;

    if (Array.isArray(obj.sections)) {
        const known = new Map(base.sections.map(s => [s.key, s]));
        const reordered: SectionConfig[] = [];
        for (const s of obj.sections as SectionConfig[]) {
            const def = known.get(s.key);
            if (!def) continue;
            reordered.push({
                ...def,
                ...s,
                options: { ...def.options, ...(s.options || {}) },
            });
            known.delete(s.key);
        }
        for (const s of known.values()) reordered.push(s);
        base.sections = reordered;
    }

    if (Array.isArray(obj.perfCells)) {
        const known = new Map(base.perfCells.map(c => [c.key, c]));
        const reordered: PerfCellConfig[] = [];
        for (const c of obj.perfCells as PerfCellConfig[]) {
            const def = known.get(c.key);
            if (!def) continue;
            reordered.push({ ...def, ...c });
            known.delete(c.key);
        }
        for (const c of known.values()) reordered.push(c);
        base.perfCells = reordered;
    }

    if (obj.global && typeof obj.global === 'object') {
        base.global = { ...base.global, ...obj.global };
    }

    base.presetActive = _computeActivePreset(base);
    return base;
}

/** First-time migration from legacy keys (`dashboard_collapsed`,
 *  `perfMetricsMode`, `perfChartColor_*`). Reads them, builds a layout,
 *  then leaves the legacy keys in place — they remain harmless and
 *  some still drive existing UI paths until fully cut over. */
function _migrateFromLegacyKeys(): DashboardLayoutV1 {
    const layout = _clone(DEFAULT_LAYOUT, 'studio');

    try {
        const collapsedRaw = localStorage.getItem('dashboard_collapsed');
        if (collapsedRaw) {
            const collapsed = JSON.parse(collapsedRaw) as Record<string, boolean>;
            for (const s of layout.sections) {
                if (collapsed[s.key]) s.collapsedDefault = true;
            }
        }
    } catch { /* ignore */ }

    try {
        const mode = localStorage.getItem('perfMetricsMode');
        if (mode === 'system' || mode === 'app' || mode === 'both') {
            layout.global.perfMode = mode;
        }
    } catch { /* ignore */ }

    for (const cell of layout.perfCells) {
        try {
            const color = localStorage.getItem(`perfChartColor_${cell.key}`);
            if (color) cell.colorOverride = color;
        } catch { /* ignore */ }
    }

    layout.presetActive = _computeActivePreset(layout);
    return layout;
}
