import { describe, expect, it } from 'vitest';
import { wrapViews } from './spec-restructure';

describe('wrapViews — wrap across the target axis', () => {
  it('pairs a view beside a sibling in a new perpendicular concat', () => {
    // The motivating case: a vconcat of three, drop the last beside the middle to
    // make a row — vconcat:[A, hconcat:[B, C]].
    const spec = { data: { name: 'd' }, vconcat: [{ mark: 'a' }, { mark: 'b' }, { mark: 'c' }] };
    const next = wrapViews(spec, ['vconcat', 1], ['vconcat', 2], 'horizontal', 'after');
    expect(next).toEqual({
      data: { name: 'd' },
      vconcat: [{ mark: 'a' }, { hconcat: [{ mark: 'b' }, { mark: 'c' }] }],
    });
    expect(spec.vconcat).toHaveLength(3); // input untouched
  });

  it('honors the side — source before the target', () => {
    // Wrapping A and removing B empties the hconcat to one child, which collapses,
    // so the result is the bare vconcat with the source ahead of the target.
    const spec = { hconcat: [{ mark: 'a' }, { mark: 'b' }] };
    const next = wrapViews(spec, ['hconcat', 0], ['hconcat', 1], 'vertical', 'before');
    expect(next).toEqual({ vconcat: [{ mark: 'b' }, { mark: 'a' }] });
  });
});

describe('wrapViews — flatten makes a with-axis drop an insert', () => {
  it('inserts into the target concat instead of nesting a redundant one', () => {
    // Drop S (from a different container) onto the bottom edge of B, which already
    // sits in a vconcat → S joins that vconcat after B, the source container collapses.
    const spec = {
      hconcat: [{ vconcat: [{ mark: 'a' }, { mark: 'b' }] }, { mark: 's' }],
    };
    const next = wrapViews(spec, ['hconcat', 0, 'vconcat', 1], ['hconcat', 1], 'vertical', 'after');
    expect(next).toEqual({ vconcat: [{ mark: 'a' }, { mark: 'b' }, { mark: 's' }] });
  });
});

describe('wrapViews — collapse the source container', () => {
  it('unwraps a single-child container left behind by the move', () => {
    const spec = { vconcat: [{ hconcat: [{ mark: 'a' }, { mark: 'b' }] }, { mark: 'c' }] };
    // Drag B out to sit below C (a with-axis drop → reorder via flatten), leaving the
    // hconcat with one child, which collapses to a bare unit.
    const next = wrapViews(spec, ['vconcat', 1], ['vconcat', 0, 'hconcat', 1], 'vertical', 'after');
    expect(next).toEqual({ vconcat: [{ mark: 'a' }, { mark: 'c' }, { mark: 'b' }] });
  });
});

describe('wrapViews — data pinning', () => {
  it('pins the source’s inherited data when it would otherwise rebind', () => {
    const spec = {
      data: { name: 'root' },
      vconcat: [{ mark: 'a' }, { data: { name: 'd2' }, hconcat: [{ mark: 'b' }, { mark: 'c' }] }],
    };
    const next = wrapViews(
      spec,
      ['vconcat', 0],
      ['vconcat', 1, 'hconcat', 0],
      'horizontal',
      'after',
    );
    expect(next).toEqual({
      data: { name: 'root' },
      vconcat: [
        { hconcat: [{ mark: 'a' }, { data: { name: 'd2' }, mark: 'b' }] },
        { data: { name: 'd2' }, mark: 'c' },
      ],
    });
  });

  it('adds no data key when the source already inherits the destination data', () => {
    const spec = { data: { name: 'd' }, vconcat: [{ mark: 'a' }, { mark: 'b' }, { mark: 'c' }] };
    const next = wrapViews(spec, ['vconcat', 0], ['vconcat', 2], 'horizontal', 'after');
    // C had no data and the whole spec shares one source, so nothing is pinned.
    expect(JSON.stringify(next)).not.toContain('"data":{"name":"d"},{"mark":"c"');
    expect(next).toEqual({
      data: { name: 'd' },
      vconcat: [{ hconcat: [{ mark: 'a' }, { mark: 'c' }] }, { mark: 'b' }],
    });
  });
});

describe('wrapViews — degenerate drops', () => {
  const spec = { vconcat: [{ hconcat: [{ mark: 'a' }, { mark: 'b' }] }, { mark: 'c' }] };

  it('returns null for a drop onto itself', () => {
    expect(wrapViews(spec, ['vconcat', 1], ['vconcat', 1], 'horizontal', 'after')).toBeNull();
  });

  it('returns null when the source is an ancestor of the target', () => {
    expect(
      wrapViews(spec, ['vconcat', 0, 'hconcat', 1], ['vconcat', 0], 'horizontal', 'after'),
    ).toBeNull();
  });

  it('returns null when the target is a descendant of the source', () => {
    expect(
      wrapViews(spec, ['vconcat', 0], ['vconcat', 0, 'hconcat', 0], 'vertical', 'after'),
    ).toBeNull();
  });

  it('returns null when wrapping the root', () => {
    expect(wrapViews(spec, [], ['vconcat', 1], 'horizontal', 'after')).toBeNull();
  });
});
