import { beforeEach, describe, expect, it } from 'vitest';
import { useAppStore } from './AppStore';
import { selectIsDraftDirty, selectSelectedTheme, useCustomThemeStore } from './CustomThemeStore';

const store = () => useCustomThemeStore.getState();

beforeEach(() => {
  store().reset();
  useAppStore.getState().setChartTheme('astrolabe');
});

describe('createTheme', () => {
  it('creates, selects, and opens the draft seeded from the config', () => {
    store().createTheme('My theme', { font: 'Georgia' });
    const s = store();
    expect(s.themes).toHaveLength(1);
    expect(s.themes[0].name).toBe('My theme');
    expect(s.selectedId).toBe(s.themes[0].id);
    expect(s.draft).toEqual({
      name: 'My theme',
      configText: JSON.stringify({ font: 'Georgia' }, null, 2),
    });
    expect(s.draftConfig).toEqual({ font: 'Georgia' });
  });

  it('auto-uniquifies the name (non-interactive path)', () => {
    store().createTheme('Brand', {});
    store().createTheme('Brand', {});
    expect(store().themes.map((t) => t.name)).toEqual(['Brand', 'Brand 2']);
  });

  it('assigns fresh monotonic ids', () => {
    store().createTheme('A', {});
    store().createTheme('B', {});
    const [a, b] = store().themes;
    expect(b.id).toBe(a.id + 1);
  });
});

describe('addThemes (import batch)', () => {
  it('reassigns ids from the store id authority and appends', () => {
    const existing = store().createTheme('Existing', {});
    store().addThemes([
      { id: existing.id, version: 1, name: 'A', config: {}, created: 'x', modified: 'x' },
      { id: existing.id, version: 1, name: 'B', config: {}, created: 'x', modified: 'x' },
    ]);
    const themes = store().themes;
    expect(themes.map((t) => t.name)).toEqual(['Existing', 'A', 'B']);
    expect(new Set(themes.map((t) => t.id)).size).toBe(3);
  });

  it('does not touch the builder selection or draft', () => {
    store().createTheme('Open', {});
    const before = store().selectedId;
    store().addThemes([{ id: 9, version: 1, name: 'A', config: {}, created: 'x', modified: 'x' }]);
    expect(store().selectedId).toBe(before);
    expect(store().draft?.name).toBe('Open');
  });

  it('is a no-op for an empty batch (no state churn)', () => {
    const before = store().themes;
    store().addThemes([]);
    expect(store().themes).toBe(before);
  });
});

describe('draft editing', () => {
  beforeEach(() => store().createTheme('Brand', { font: 'Helvetica' }));

  it('a valid config-text edit reparses for the gallery', () => {
    store().updateDraft({ configText: '{"font": "Georgia"}' });
    expect(store().draftConfig).toEqual({ font: 'Georgia' });
    expect(store().parseError).toBeNull();
  });

  it('an invalid edit keeps the last valid config and reports the error', () => {
    store().updateDraft({ configText: '{"font": ' });
    expect(store().draftConfig).toEqual({ font: 'Helvetica' });
    expect(store().parseError).toContain('Invalid JSON');
  });

  it('a non-object config is rejected with a shape error', () => {
    store().updateDraft({ configText: '[1, 2]' });
    expect(store().parseError).toContain('must be a JSON object');
  });

  it('empty config text parses as an empty config', () => {
    store().updateDraft({ configText: '  ' });
    expect(store().draftConfig).toEqual({});
    expect(store().parseError).toBeNull();
  });

  it('dirty tracking: untouched draft is clean, edits make it dirty', () => {
    expect(selectIsDraftDirty(store())).toBe(false);
    store().updateDraft({ name: 'Brand 2026' });
    expect(selectIsDraftDirty(store())).toBe(true);
  });

  it('applyDraftFont rewrites the draft text and parsed config', () => {
    store().updateDraft({
      configText: JSON.stringify({ font: 'Helvetica', axis: { labelFont: 'Helvetica' } }),
    });
    store().applyDraftFont('Georgia, serif');
    expect(store().draftConfig).toEqual({
      font: 'Georgia, serif',
      axis: { labelFont: 'Georgia, serif' },
    });
    expect(store().draft?.configText).toContain('"labelFont": "Georgia, serif"');
  });

  it('applyDraftFont refuses while the text is invalid JSON', () => {
    store().updateDraft({ configText: '{oops' });
    store().applyDraftFont('Georgia');
    expect(store().parseError).toContain('Invalid JSON');
    expect(store().draft?.configText).toBe('{oops');
  });
});

describe('saveDraft', () => {
  beforeEach(() => store().createTheme('Brand', { font: 'Helvetica' }));

  it('commits name and config and advances modified', () => {
    const created = store().themes[0].modified;
    store().updateDraft({ name: 'Brand 2026', configText: '{"font": "Georgia"}' });
    const ok = store().saveDraft(new Date('2026-06-13T00:00:00Z'));
    expect(ok).toBe(true);
    const theme = store().themes[0];
    expect(theme.name).toBe('Brand 2026');
    expect(theme.config).toEqual({ font: 'Georgia' });
    expect(theme.modified).not.toBe(created);
    expect(selectIsDraftDirty(store())).toBe(false);
  });

  it('rejects an empty name', () => {
    store().updateDraft({ name: '   ' });
    expect(store().saveDraft()).toBe(false);
    expect(store().saveError).toBe('Enter a theme name.');
  });

  it('rejects a name taken by another theme (case-insensitive), allows own', () => {
    store().createTheme('Other', {});
    store().updateDraft({ name: 'BRAND' });
    expect(store().saveDraft()).toBe(false);
    expect(store().saveError).toContain('already exists');

    store().select(store().themes[0].id);
    store().updateDraft({ name: 'Brand' });
    expect(store().saveDraft()).toBe(true);
  });

  it('rejects invalid config text', () => {
    store().updateDraft({ configText: '{nope' });
    expect(store().saveDraft()).toBe(false);
    expect(store().parseError).toContain('Invalid JSON');
  });
});

describe('remove', () => {
  it('drops the record and clears the open draft', () => {
    store().createTheme('Brand', {});
    const id = store().themes[0].id;
    store().remove(id);
    expect(store().themes).toHaveLength(0);
    expect(store().selectedId).toBeNull();
    expect(store().draft).toBeNull();
  });

  it('falls the active chart-theme selection back to the house style', () => {
    store().createTheme('Brand', {});
    const id = store().themes[0].id;
    useAppStore.getState().setChartTheme(`custom:${id}`);
    store().remove(id);
    expect(useAppStore.getState().chartTheme).toBe('astrolabe');
  });

  it('leaves an unrelated selection alone', () => {
    store().createTheme('Brand', {});
    useAppStore.getState().setChartTheme('fivethirtyeight');
    store().remove(store().themes[0].id);
    expect(useAppStore.getState().chartTheme).toBe('fivethirtyeight');
  });
});

describe('selectors', () => {
  it('selectSelectedTheme resolves the open record', () => {
    expect(selectSelectedTheme(store())).toBeNull();
    store().createTheme('Brand', {});
    expect(selectSelectedTheme(store())?.name).toBe('Brand');
  });
});
