import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
  loadPanelLayout,
  loadPaneVisibility,
  loadSnippetSort,
  savePanelLayout,
  savePaneVisibility,
  saveSnippetSort,
} from './ux-prefs';

const KEY = 'astrolabe:ux-prefs';

/** In-memory localStorage stub (Node's global one is non-functional). */
function makeStorageStub() {
  const map = new Map<string, string>();
  return {
    getItem: (k: string) => (map.has(k) ? map.get(k)! : null),
    setItem: (k: string, v: string) => void map.set(k, String(v)),
    removeItem: (k: string) => void map.delete(k),
    clear: () => map.clear(),
    key: (i: number) => [...map.keys()][i] ?? null,
    get length() {
      return map.size;
    },
  };
}

describe('ux-prefs · panelLayout', () => {
  beforeEach(() => vi.stubGlobal('localStorage', makeStorageStub()));
  afterEach(() => vi.unstubAllGlobals());

  it('returns empty widths when nothing is stored', () => {
    expect(loadPanelLayout()).toEqual({ libraryWidth: undefined, previewWidth: undefined });
  });

  it('round-trips stored widths', () => {
    savePanelLayout({ libraryWidth: 300, previewWidth: 420 });
    expect(loadPanelLayout()).toEqual({ libraryWidth: 300, previewWidth: 420 });
  });

  it('merges partial writes without dropping the other width', () => {
    savePanelLayout({ libraryWidth: 300, previewWidth: 420 });
    savePanelLayout({ libraryWidth: 250 });
    expect(loadPanelLayout()).toEqual({ libraryWidth: 250, previewWidth: 420 });
  });

  it('rejects non-positive / non-finite junk', () => {
    localStorage.setItem(
      KEY,
      JSON.stringify({ panelLayout: { libraryWidth: -5, previewWidth: 'wide' } }),
    );
    expect(loadPanelLayout()).toEqual({ libraryWidth: undefined, previewWidth: undefined });
  });

  it('tolerates malformed JSON', () => {
    localStorage.setItem(KEY, '{ not json');
    expect(loadPanelLayout()).toEqual({ libraryWidth: undefined, previewWidth: undefined });
  });

  it('preserves unrelated keys in the record (forward-compatible merge)', () => {
    localStorage.setItem(KEY, JSON.stringify({ sort: { sortBy: 'name', sortOrder: 'asc' } }));
    savePanelLayout({ libraryWidth: 260 });
    const stored = JSON.parse(localStorage.getItem(KEY)!) as {
      sort: { sortBy: string };
      panelLayout: { libraryWidth: number };
    };
    expect(stored.sort.sortBy).toBe('name');
    expect(stored.panelLayout.libraryWidth).toBe(260);
  });
});

describe('ux-prefs · paneVisibility', () => {
  beforeEach(() => vi.stubGlobal('localStorage', makeStorageStub()));

  it('round-trips persisted visibility; absent flags stay undefined', () => {
    savePaneVisibility({ library: false, editor: true });
    expect(loadPaneVisibility()).toEqual({ library: false, editor: true, preview: undefined });
  });

  it('surfaces only booleans — junk in storage falls back to undefined', () => {
    localStorage.setItem(
      KEY,
      JSON.stringify({ paneVisibility: { library: 'nope', preview: false } }),
    );
    expect(loadPaneVisibility()).toEqual({
      library: undefined,
      editor: undefined,
      preview: false,
    });
  });

  it('preserves panelLayout when writing visibility (separate keys)', () => {
    savePanelLayout({ libraryWidth: 300 });
    savePaneVisibility({ editor: false });
    const stored = JSON.parse(localStorage.getItem(KEY)!) as {
      panelLayout: { libraryWidth: number };
      paneVisibility: { editor: boolean };
    };
    expect(stored.panelLayout.libraryWidth).toBe(300);
    expect(stored.paneVisibility.editor).toBe(false);
  });
});

describe('ux-prefs · snippetSort', () => {
  beforeEach(() => vi.stubGlobal('localStorage', makeStorageStub()));
  afterEach(() => vi.unstubAllGlobals());

  it('defaults to Modified, descending when nothing is stored', () => {
    expect(loadSnippetSort()).toEqual({ by: 'modified', order: 'desc' });
  });

  it('round-trips a stored sort', () => {
    saveSnippetSort({ by: 'name', order: 'asc' });
    expect(loadSnippetSort()).toEqual({ by: 'name', order: 'asc' });
  });

  it('falls back to defaults for invalid stored values', () => {
    localStorage.setItem(KEY, JSON.stringify({ snippetSort: { by: 'bogus', order: 'sideways' } }));
    expect(loadSnippetSort()).toEqual({ by: 'modified', order: 'desc' });
  });

  it('preserves panelLayout when writing the sort (separate keys)', () => {
    savePanelLayout({ libraryWidth: 280 });
    saveSnippetSort({ by: 'size', order: 'desc' });
    const stored = JSON.parse(localStorage.getItem(KEY)!) as {
      panelLayout: { libraryWidth: number };
      snippetSort: { by: string; order: string };
    };
    expect(stored.panelLayout.libraryWidth).toBe(280);
    expect(stored.snippetSort).toEqual({ by: 'size', order: 'desc' });
  });
});
