import { describe, expect, it } from 'vitest';
import {
  inlineValuesOf,
  rowsForDataBinding,
  selfDefinedPayloadOf,
  specHasExtractableData,
} from './spec-inline-data';

describe('rowsForDataBinding', () => {
  it('reads inline values from the given binding', () => {
    const spec = { data: { values: [{ a: 1 }, { a: 2 }] } };
    expect(rowsForDataBinding(spec, spec.data)).toEqual([{ a: 1 }, { a: 2 }]);
  });

  it('resolves a self-defined named binding against top-level datasets', () => {
    const spec = { datasets: { ds: [{ c: 1 }, { c: 2 }] }, data: { name: 'ds' } };
    expect(rowsForDataBinding(spec, { name: 'ds' })).toEqual([{ c: 1 }, { c: 2 }]);
  });

  it('returns null for a library reference, url, generator, or CSV-string values', () => {
    const spec = { datasets: { ds: [{ c: 1 }] } };
    expect(rowsForDataBinding(spec, { name: 'LibraryRef' })).toBeNull(); // not self-defined
    expect(rowsForDataBinding(spec, { url: 'x.csv' })).toBeNull();
    expect(rowsForDataBinding(spec, { sequence: { start: 0, stop: 5 } })).toBeNull();
    expect(rowsForDataBinding(spec, { values: 'a,b\n1,2' })).toBeNull();
    expect(rowsForDataBinding({ mark: 'bar' }, { values: [] })).toBeNull(); // empty rows
  });
});

describe('inlineValuesOf', () => {
  it('returns the JSON array payload as json', () => {
    expect(inlineValuesOf({ values: [{ a: 1 }] })).toEqual({ values: [{ a: 1 }], format: 'json' });
  });

  it('preserves a CSV/TSV string payload verbatim with its declared format', () => {
    expect(inlineValuesOf({ values: 'a,b\n1,2', format: { type: 'csv' } })).toEqual({
      values: 'a,b\n1,2',
      format: 'csv',
    });
    expect(inlineValuesOf({ values: 'a\tb', format: { type: 'tsv' } })).toEqual({
      values: 'a\tb',
      format: 'tsv',
    });
  });

  it('keeps a name riding on inline values (named-inline is still inline)', () => {
    expect(inlineValuesOf({ name: 'x', values: [{ a: 1 }] })).toEqual({
      values: [{ a: 1 }],
      format: 'json',
    });
  });

  it('returns null for a library reference, url, generator, or non-object', () => {
    expect(inlineValuesOf({ name: 'sales' })).toBeNull();
    expect(inlineValuesOf({ url: 'x.csv' })).toBeNull();
    expect(inlineValuesOf({ sequence: { start: 0, stop: 5 } })).toBeNull();
    expect(inlineValuesOf(null)).toBeNull();
  });
});

describe('selfDefinedPayloadOf', () => {
  it('returns the rows of a referenced self-defined datasets entry', () => {
    const spec = { datasets: { sales: [{ a: 1 }, { a: 2 }] }, data: { name: 'sales' } };
    expect(selfDefinedPayloadOf(spec, spec.data)).toEqual({
      values: [{ a: 1 }, { a: 2 }],
      format: 'json',
    });
  });

  it('returns null for a library reference, inline values, or a non-array entry', () => {
    const spec = { datasets: { sales: [{ a: 1 }] } };
    expect(selfDefinedPayloadOf(spec, { name: 'LibraryRef' })).toBeNull(); // not self-defined
    expect(selfDefinedPayloadOf(spec, { values: [{ a: 1 }] })).toBeNull(); // inline, not named
    expect(
      selfDefinedPayloadOf({ datasets: { s: 'a,b\n1,2' }, data: {} }, { name: 's' }),
    ).toBeNull();
  });
});

describe('specHasExtractableData', () => {
  it('is true for inline values and for a self-defined reference', () => {
    expect(specHasExtractableData({ data: { values: [{ a: 1 }] } })).toBe(true);
    expect(specHasExtractableData({ datasets: { ds: [{ a: 1 }] }, data: { name: 'ds' } })).toBe(
      true,
    );
  });

  it('is false for a library reference, url, or generator', () => {
    expect(specHasExtractableData({ data: { name: 'sales' } })).toBe(false);
    expect(specHasExtractableData({ data: { url: 'x.csv' } })).toBe(false);
    expect(specHasExtractableData({ data: { sequence: { start: 0, stop: 5 } } })).toBe(false);
  });

  it('ignores a field literally named data inside a user data row (prune-the-payload)', () => {
    // The walker prunes the `data`/`datasets` keys, so a row that carries a field
    // named `data` is never mistaken for a binding — only the genuine top-level
    // inline binding triggers, and an encoding field named `data` triggers nothing.
    expect(
      specHasExtractableData({ data: { values: [{ data: { name: 'x' } }] }, mark: 'bar' }),
    ).toBe(true);
    expect(
      specHasExtractableData({ layer: [{ mark: 'bar', encoding: { x: { field: 'data' } } }] }),
    ).toBe(false);
  });
});
