import { beforeEach, describe, expect, test } from 'vitest';
import { createSnippet } from '@core/snippet';
import { useDatasetStore } from './DatasetStore';
import { hasExtractableData, useExtractStore } from './ExtractStore';
import { useSnippetStore } from './SnippetStore';

const extract = () => useExtractStore.getState();
const snippets = () => useSnippetStore.getState();
const datasets = () => useDatasetStore.getState();

/** Load a single active snippet whose live draft buffer is `draft`. */
function activeDraft(draft: string): void {
  const s = createSnippet({ id: 's', spec: draft, now: new Date('2026-01-01T00:00:00Z') });
  useSnippetStore.getState().hydrate([s], 's');
  useSnippetStore.getState().updateDraft(draft);
}

beforeEach(() => {
  useExtractStore.getState().reset();
  useSnippetStore.getState().reset();
  useDatasetStore.getState().reset();
});

describe('hasExtractableData', () => {
  test('true for inline data, false for a library reference or bad JSON', () => {
    expect(hasExtractableData('{"data":{"values":[{"a":1}]},"mark":"bar"}')).toBe(true);
    expect(hasExtractableData('{"data":{"name":"sales"}}')).toBe(false);
    expect(hasExtractableData('not json')).toBe(false);
  });

  test('true when inline data lives only in a nested view', () => {
    expect(
      hasExtractableData('{"layer":[{"data":{"name":"sales"}},{"data":{"values":[{"a":1}]}}]}'),
    ).toBe(true);
  });

  test('true for a reference to a self-defined datasets entry', () => {
    expect(hasExtractableData('{"datasets":{"sales":[{"a":1}]},"data":{"name":"sales"}}')).toBe(
      true,
    );
  });
});

describe('confirm — inline rewrite', () => {
  test('extracts the root binding and rewrites it to a by-name reference', () => {
    activeDraft('{"data":{"values":[{"a":1},{"a":2}]},"mark":"bar"}');
    extract().begin({
      source: { values: [{ a: 1 }, { a: 2 }], format: 'json' },
      target: { kind: 'inline', anchorPath: [] },
    });
    extract().setName('Sales');

    expect(extract().confirm(new Date('2026-02-01T00:00:00Z'))).toBe(true);

    const ds = datasets().datasets.find((d) => d.name === 'Sales');
    expect(ds?.data).toEqual([{ a: 1 }, { a: 2 }]);

    const draft = JSON.parse(snippets().draftText) as { data: unknown; mark: unknown };
    expect(draft.data).toEqual({ name: 'Sales' });
    expect(draft.mark).toBe('bar');
  });

  test('rewrites only the focused view in a composition, leaving siblings intact', () => {
    activeDraft(
      JSON.stringify({
        layer: [{ data: { name: 'sales' }, mark: 'line' }, { data: { values: [{ a: 1 }] } }],
      }),
    );
    extract().begin({
      source: { values: [{ a: 1 }], format: 'json' },
      target: { kind: 'inline', anchorPath: ['layer', 1] },
    });
    extract().setName('Overlay');
    expect(extract().confirm()).toBe(true);

    const draft = JSON.parse(snippets().draftText) as { layer: Array<{ data: unknown }> };
    expect(draft.layer[0].data).toEqual({ name: 'sales' }); // sibling untouched
    expect(draft.layer[1].data).toEqual({ name: 'Overlay' }); // focused view rewritten
  });

  test("rewrites a lookup transform's inline from.data (the cursor sits inside it)", () => {
    activeDraft(
      JSON.stringify({
        data: { name: 'Library' },
        transform: [
          { lookup: 'k', from: { data: { values: [{ k: 1, v: 9 }] }, key: 'k', fields: ['v'] } },
        ],
        mark: 'bar',
      }),
    );
    extract().begin({
      source: { values: [{ k: 1, v: 9 }], format: 'json' },
      target: { kind: 'inline', anchorPath: ['transform', 0, 'from'] },
    });
    extract().setName('Lookup');
    expect(extract().confirm()).toBe(true);

    const draft = JSON.parse(snippets().draftText) as {
      transform: Array<{ from: { data: unknown } }>;
    };
    expect(draft.transform[0].from.data).toEqual({ name: 'Lookup' });
  });

  test('preserves a CSV string payload as a raw-text dataset', () => {
    activeDraft('{"data":{"values":"a,b\\n1,2","format":{"type":"csv"}},"mark":"bar"}');
    extract().begin({
      source: { values: 'a,b\n1,2', format: 'csv' },
      target: { kind: 'inline', anchorPath: [] },
    });
    extract().setName('Raw');
    expect(extract().confirm()).toBe(true);

    const ds = datasets().datasets.find((d) => d.name === 'Raw');
    expect(ds?.format).toBe('csv');
    expect(ds?.data).toBe('a,b\n1,2');
  });
});

describe('confirm — self-defined datasets rewrite', () => {
  test('keeping the name drops the datasets entry; the reference resolves to the library', () => {
    activeDraft('{"datasets":{"sales":[{"a":1},{"a":2}]},"data":{"name":"sales"},"mark":"bar"}');
    extract().begin({
      source: { values: [{ a: 1 }, { a: 2 }], format: 'json' },
      target: { kind: 'self-defined', datasetName: 'sales' },
      name: 'sales',
    });
    expect(extract().confirm()).toBe(true);

    const ds = datasets().datasets.find((d) => d.name === 'sales');
    expect(ds?.data).toEqual([{ a: 1 }, { a: 2 }]);

    const draft = JSON.parse(snippets().draftText) as { datasets?: unknown; data: unknown };
    expect(draft.datasets).toBeUndefined(); // map emptied → removed
    expect(draft.data).toEqual({ name: 'sales' }); // reference unchanged, now a library ref
  });

  test('renaming rewrites every reference and keeps other datasets entries', () => {
    activeDraft(
      JSON.stringify({
        datasets: { sales: [{ a: 1 }], other: [{ b: 2 }] },
        layer: [
          { data: { name: 'sales' } },
          { data: { name: 'sales' } },
          { data: { name: 'other' } },
        ],
      }),
    );
    extract().begin({
      source: { values: [{ a: 1 }], format: 'json' },
      target: { kind: 'self-defined', datasetName: 'sales' },
      name: 'Sales 2024',
    });
    expect(extract().confirm()).toBe(true);

    const draft = JSON.parse(snippets().draftText) as {
      datasets: Record<string, unknown>;
      layer: Array<{ data: unknown }>;
    };
    expect(draft.layer[0].data).toEqual({ name: 'Sales 2024' }); // both sales refs rewritten
    expect(draft.layer[1].data).toEqual({ name: 'Sales 2024' });
    expect(draft.layer[2].data).toEqual({ name: 'other' }); // untouched
    expect(draft.datasets).toEqual({ other: [{ b: 2 }] }); // sales entry dropped, other kept
  });
});

describe('confirm — guards', () => {
  test('rejects a duplicate name and creates nothing', () => {
    activeDraft('{"data":{"values":[{"a":1}]}}');
    extract().begin({
      source: { values: [{ a: 1 }], format: 'json' },
      target: { kind: 'inline', anchorPath: [] },
    });
    extract().setName('Sales');
    extract().confirm();
    const countAfterFirst = datasets().datasets.length;

    activeDraft('{"data":{"values":[{"b":2}]}}');
    extract().begin({
      source: { values: [{ b: 2 }], format: 'json' },
      target: { kind: 'inline', anchorPath: [] },
    });
    extract().setName('Sales');
    expect(extract().confirm()).toBe(false);
    expect(extract().error).toMatch(/already exists/);
    expect(datasets().datasets.length).toBe(countAfterFirst);
  });

  test('refuses when the anchor path no longer resolves, creating nothing', () => {
    activeDraft('{"data":{"values":[{"a":1}]}}'); // no layer array
    extract().begin({
      source: { values: [{ a: 1 }], format: 'json' },
      target: { kind: 'inline', anchorPath: ['layer', 3] },
    });
    extract().setName('Ghost');
    expect(extract().confirm()).toBe(false);
    expect(extract().error).toMatch(/Could not locate/);
    expect(datasets().datasets.length).toBe(0);
  });
});
