import { describe, expect, it } from 'vitest';
import { derivedFieldNamesAtPath } from './spec-fields';

describe('derivedFieldNamesAtPath', () => {
  const spec = {
    transform: [{ calculate: 'x', as: 'shared' }],
    layer: [
      { transform: [{ calculate: 'y', as: 'inner' }], mark: 'bar' },
      { transform: [{ calculate: 'z', as: 'sibling' }], mark: 'line' },
    ],
  };

  it('collects calculate / timeUnit / bin as-names', () => {
    const s = {
      transform: [
        { calculate: 'datum.a + 1', as: 'plusOne' },
        { timeUnit: 'month', field: 'date', as: 'mo' },
        { bin: true, field: 'x', as: ['x_start', 'x_end'] },
      ],
    };
    expect(derivedFieldNamesAtPath(s, []).sort()).toEqual(['mo', 'plusOne', 'x_end', 'x_start']);
  });

  it('reaches into op-list transforms (aggregate/window/joinaggregate)', () => {
    const s = {
      transform: [
        { aggregate: [{ op: 'mean', field: 'v', as: 'meanV' }], groupby: ['g'] },
        { window: [{ op: 'rank', as: 'rk' }] },
        { joinaggregate: [{ op: 'sum', field: 'v', as: 'total' }] },
      ],
    };
    expect(derivedFieldNamesAtPath(s, []).sort()).toEqual(['meanV', 'rk', 'total']);
  });

  it('defaults fold output to key/value when as is omitted', () => {
    expect(derivedFieldNamesAtPath({ transform: [{ fold: ['a', 'b'] }] }, []).sort()).toEqual([
      'key',
      'value',
    ]);
    expect(
      derivedFieldNamesAtPath({ transform: [{ fold: ['a'], as: ['k', 'v'] }] }, []).sort(),
    ).toEqual(['k', 'v']);
  });

  it('returns nothing for a spec with no transforms', () => {
    expect(derivedFieldNamesAtPath({ mark: 'bar', encoding: { x: { field: 'a' } } }, [])).toEqual(
      [],
    );
  });

  it('includes ancestor transforms but not sibling-view transforms', () => {
    // Inside layer 0: sees its own `inner` and the inherited top-level `shared`,
    // but never layer 1's `sibling`.
    expect(derivedFieldNamesAtPath(spec, ['layer', 0, 'mark']).sort()).toEqual(['inner', 'shared']);
    expect(derivedFieldNamesAtPath(spec, ['layer', 1, 'mark']).sort()).toEqual([
      'shared',
      'sibling',
    ]);
  });

  it('at the root sees only the top-level transforms', () => {
    expect(derivedFieldNamesAtPath(spec, [])).toEqual(['shared']);
  });

  it('descends defensively past a non-object on the path', () => {
    expect(derivedFieldNamesAtPath(spec, ['transform', 0, 'calculate', 'nope'])).toEqual([
      'shared',
    ]);
  });
});
