import { describe, it, expect } from 'vitest';
import {
  CURRENT_SETTINGS_VERSION,
  DEFAULT_SETTINGS,
  defaultSettings,
  loadSettings,
  type UserSettings,
} from './settings';

describe('defaultSettings', () => {
  it('returns the factory-default shape (spec §07)', () => {
    expect(defaultSettings()).toEqual({
      version: CURRENT_SETTINGS_VERSION,
      editor: {
        fontSize: 12,
        theme: 'auto',
        minimap: false,
        wordWrap: 'on',
        lineNumbers: 'on',
        tabSize: 2,
      },
      performance: { renderDebounce: 1500 },
      ui: { theme: 'light', previewFitMode: 'default', chartTheme: 'astrolabe' },
      formatting: { dateFormat: 'smart', customDateFormat: '' },
    });
  });

  it('returns independent copies (mutating one does not affect another)', () => {
    const a = defaultSettings();
    const b = defaultSettings();
    a.editor.fontSize = 18;
    a.editor.wordWrap = 'off';
    a.formatting.customDateFormat = 'yyyy';
    expect(b.editor.fontSize).toBe(12);
    expect(b.editor.wordWrap).toBe('on');
    expect(b.formatting.customDateFormat).toBe('');
  });

  it('matches DEFAULT_SETTINGS by value', () => {
    expect(defaultSettings()).toEqual(DEFAULT_SETTINGS);
  });
});

describe('DEFAULT_SETTINGS', () => {
  it('is deeply frozen (read-only reference)', () => {
    expect(Object.isFrozen(DEFAULT_SETTINGS)).toBe(true);
    expect(Object.isFrozen(DEFAULT_SETTINGS.editor)).toBe(true);
    expect(Object.isFrozen(DEFAULT_SETTINGS.ui)).toBe(true);
    expect(Object.isFrozen(DEFAULT_SETTINGS.formatting)).toBe(true);
    expect(Object.isFrozen(DEFAULT_SETTINGS.performance)).toBe(true);
  });
});

describe('loadSettings — valid records', () => {
  it('round-trips a full valid record (stamping current version)', () => {
    const record: UserSettings = {
      version: CURRENT_SETTINGS_VERSION,
      editor: {
        fontSize: 14,
        theme: 'vs-dark',
        minimap: true,
        wordWrap: 'off',
        lineNumbers: 'off',
        tabSize: 4,
      },
      performance: { renderDebounce: 2500 },
      ui: { theme: 'dark', previewFitMode: 'full', chartTheme: 'fivethirtyeight' },
      formatting: { dateFormat: 'custom', customDateFormat: 'yyyy-MM-dd' },
    };
    expect(loadSettings(record)).toEqual(record);
  });

  it('accepts any string for editor.theme and customDateFormat', () => {
    const out = loadSettings({
      editor: { theme: 'my-custom-theme' },
      formatting: { dateFormat: 'custom', customDateFormat: 'HH:mm' },
    });
    expect(out.editor.theme).toBe('my-custom-theme');
    expect(out.formatting.customDateFormat).toBe('HH:mm');
  });
});

describe('loadSettings — partial records fill gaps', () => {
  it('keeps a single nested field and fills the rest with defaults', () => {
    const out = loadSettings({ editor: { fontSize: 14 } });
    expect(out.editor.fontSize).toBe(14);
    expect(out).toEqual({
      ...defaultSettings(),
      editor: { ...defaultSettings().editor, fontSize: 14 },
    });
  });

  it('fills entirely-missing groups from defaults', () => {
    const out = loadSettings({ ui: { theme: 'dark' } });
    expect(out.ui.theme).toBe('dark');
    expect(out.editor).toEqual(defaultSettings().editor);
    expect(out.performance).toEqual(defaultSettings().performance);
    expect(out.formatting).toEqual(defaultSettings().formatting);
  });

  it('treats a non-object group as empty (uses defaults)', () => {
    const out = loadSettings({ editor: 'nope', performance: 42, ui: null });
    expect(out.editor).toEqual(defaultSettings().editor);
    expect(out.performance).toEqual(defaultSettings().performance);
    expect(out.ui).toEqual(defaultSettings().ui);
  });
});

describe('loadSettings — numeric coercion and clamping', () => {
  it('clamps and rounds fontSize to [10, 18]', () => {
    expect(loadSettings({ editor: { fontSize: 5 } }).editor.fontSize).toBe(10);
    expect(loadSettings({ editor: { fontSize: 99 } }).editor.fontSize).toBe(18);
    expect(loadSettings({ editor: { fontSize: 12.7 } }).editor.fontSize).toBe(13);
    expect(loadSettings({ editor: { fontSize: 14 } }).editor.fontSize).toBe(14);
  });

  it('coerces tabSize to a positive integer', () => {
    expect(loadSettings({ editor: { tabSize: 0 } }).editor.tabSize).toBe(1);
    expect(loadSettings({ editor: { tabSize: -3 } }).editor.tabSize).toBe(1);
    expect(loadSettings({ editor: { tabSize: 2.9 } }).editor.tabSize).toBe(3);
    expect(loadSettings({ editor: { tabSize: 8 } }).editor.tabSize).toBe(8);
  });

  it('clamps renderDebounce to [500, 5000]', () => {
    expect(loadSettings({ performance: { renderDebounce: 100 } }).performance.renderDebounce).toBe(
      500,
    );
    expect(loadSettings({ performance: { renderDebounce: 9999 } }).performance.renderDebounce).toBe(
      5000,
    );
    expect(loadSettings({ performance: { renderDebounce: 1500 } }).performance.renderDebounce).toBe(
      1500,
    );
  });

  it('falls back to default for non-number / non-finite numerics', () => {
    const d = defaultSettings();
    expect(loadSettings({ editor: { fontSize: '14' } }).editor.fontSize).toBe(d.editor.fontSize);
    expect(loadSettings({ editor: { fontSize: NaN } }).editor.fontSize).toBe(d.editor.fontSize);
    expect(loadSettings({ editor: { fontSize: Infinity } }).editor.fontSize).toBe(
      d.editor.fontSize,
    );
    expect(loadSettings({ editor: { tabSize: null } }).editor.tabSize).toBe(d.editor.tabSize);
    expect(
      loadSettings({ performance: { renderDebounce: 'fast' } }).performance.renderDebounce,
    ).toBe(d.performance.renderDebounce);
  });
});

describe('loadSettings — enum validation', () => {
  it('falls back when an enum value is not allowed', () => {
    const d = defaultSettings();
    expect(loadSettings({ editor: { wordWrap: 'sometimes' } }).editor.wordWrap).toBe(
      d.editor.wordWrap,
    );
    expect(loadSettings({ editor: { lineNumbers: 1 } }).editor.lineNumbers).toBe(
      d.editor.lineNumbers,
    );
    expect(loadSettings({ ui: { theme: 'sepia' } }).ui.theme).toBe(d.ui.theme);
    expect(loadSettings({ ui: { previewFitMode: 'tall' } }).ui.previewFitMode).toBe(
      d.ui.previewFitMode,
    );
    expect(loadSettings({ ui: { chartTheme: 'comic-sans' } }).ui.chartTheme).toBe(d.ui.chartTheme);
    expect(loadSettings({ formatting: { dateFormat: 'relative' } }).formatting.dateFormat).toBe(
      d.formatting.dateFormat,
    );
  });

  it('accepts each allowed enum value', () => {
    expect(loadSettings({ editor: { wordWrap: 'off' } }).editor.wordWrap).toBe('off');
    expect(loadSettings({ editor: { lineNumbers: 'off' } }).editor.lineNumbers).toBe('off');
    expect(loadSettings({ ui: { theme: 'dark' } }).ui.theme).toBe('dark');
    expect(loadSettings({ ui: { previewFitMode: 'width' } }).ui.previewFitMode).toBe('width');
    expect(loadSettings({ ui: { previewFitMode: 'height' } }).ui.previewFitMode).toBe('height');
    expect(loadSettings({ ui: { chartTheme: 'stock' } }).ui.chartTheme).toBe('stock');
    expect(loadSettings({ ui: { chartTheme: 'latimes' } }).ui.chartTheme).toBe('latimes');
    expect(loadSettings({ formatting: { dateFormat: 'iso' } }).formatting.dateFormat).toBe('iso');
  });

  it('falls back when minimap is not a boolean', () => {
    expect(loadSettings({ editor: { minimap: 'yes' } }).editor.minimap).toBe(false);
    expect(loadSettings({ editor: { minimap: true } }).editor.minimap).toBe(true);
  });
});

describe('loadSettings — junk and edge inputs', () => {
  it('returns defaults for null/undefined/array/string/number', () => {
    const d = defaultSettings();
    expect(loadSettings(null)).toEqual(d);
    expect(loadSettings(undefined)).toEqual(d);
    expect(loadSettings([])).toEqual(d);
    expect(loadSettings([{ editor: { fontSize: 14 } }])).toEqual(d);
    expect(loadSettings('settings')).toEqual(d);
    expect(loadSettings(42)).toEqual(d);
    expect(loadSettings(true)).toEqual(d);
  });

  it('tolerates unknown extra keys without throwing', () => {
    const out = loadSettings({
      version: CURRENT_SETTINGS_VERSION,
      futureFeature: { enabled: true },
      editor: { fontSize: 16, somethingNew: 'x' },
    });
    expect(out.editor.fontSize).toBe(16);
    expect(out).not.toHaveProperty('futureFeature');
    expect(out.editor).not.toHaveProperty('somethingNew');
  });
});

describe('loadSettings — version stamping', () => {
  it('always stamps the current version regardless of input version', () => {
    expect(loadSettings({ version: 0 }).version).toBe(CURRENT_SETTINGS_VERSION);
    expect(loadSettings({ version: 99 }).version).toBe(CURRENT_SETTINGS_VERSION);
    expect(loadSettings({ version: 'old' }).version).toBe(CURRENT_SETTINGS_VERSION);
    expect(loadSettings({}).version).toBe(CURRENT_SETTINGS_VERSION);
  });
});
