/**
 * CompositionWireframe — the read-only structure tree (arch 08 / arch 10 §5).
 *
 * Guards the load-bearing behavior: the affordance is hidden for a single-view
 * spec (nothing to schematize), the disclosed panel is an APG tree with one
 * roving tab stop, arrow keys walk it in document order, and activating a box
 * asks the editor to reveal that view's exact source range. The viewTree model
 * itself is covered in core (spec-view-tree.test.ts).
 */

import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { act } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { useAppStore } from '../stores/AppStore';
import { usePopoverStore } from '../stores/PopoverStore';
import { useSnippetStore } from '../stores/SnippetStore';
import { CompositionWireframe } from './CompositionWireframe';

(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;

const COMPOSED = JSON.stringify({ vconcat: [{ mark: 'point' }, { mark: 'bar' }] }, null, 2);

let container: HTMLDivElement;
let root: Root;

const setSpec = (text: string) => {
  useSnippetStore.getState().reset();
  useSnippetStore.setState({ draftText: text });
};

const treeItems = () =>
  Array.from(document.body.querySelectorAll<HTMLElement>('[role="treeitem"]'));
const item = (key: string) => document.body.querySelector<HTMLElement>(`[data-key="${key}"]`)!;

async function renderOpen() {
  await act(async () => {
    root.render(<CompositionWireframe />);
    await Promise.resolve();
  });
  await act(async () => {
    usePopoverStore.getState().show('composition-wireframe');
    await Promise.resolve();
  });
}

beforeEach(() => {
  usePopoverStore.setState({ openId: null });
  useAppStore.setState({ revealTarget: null });
  setSpec(COMPOSED);
  container = document.createElement('div');
  document.body.appendChild(container);
  root = createRoot(container);
});

afterEach(() => {
  act(() => root.unmount());
  container.remove();
  usePopoverStore.setState({ openId: null });
  useAppStore.setState({ revealTarget: null });
  useSnippetStore.getState().reset();
});

describe('CompositionWireframe', () => {
  test('renders nothing for a single-view spec', () => {
    setSpec('{"mark":"point"}');
    act(() => root.render(<CompositionWireframe />));
    expect(container.querySelector('button')).toBeNull();
  });

  test('shows a structure trigger for a composed spec', () => {
    act(() => root.render(<CompositionWireframe />));
    expect(container.querySelector('button[aria-controls="composition-wireframe"]')).not.toBeNull();
  });

  test('discloses an APG tree: the vconcat root and its two leaf views', async () => {
    await renderOpen();
    expect(document.body.querySelector('[role="tree"]')).not.toBeNull();
    expect(treeItems().map((el) => el.dataset.key)).toEqual(['root', 'vconcat|0', 'vconcat|1']);
  });

  test('roving tabindex: exactly one treeitem is in the tab order', async () => {
    await renderOpen();
    expect(treeItems().filter((el) => el.tabIndex === 0)).toHaveLength(1);
    expect(item('root').tabIndex).toBe(0); // the root holds it on open
  });

  test('ArrowDown moves the roving tab stop in document order', async () => {
    await renderOpen();
    act(() => {
      item('root').dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
    });
    expect(item('root').tabIndex).toBe(-1);
    expect(item('vconcat|0').tabIndex).toBe(0);
  });

  test('activating a box asks the editor to reveal that view’s source range', async () => {
    await renderOpen();
    act(() => item('vconcat|1').click());
    const target = useAppStore.getState().revealTarget!;
    expect(COMPOSED.slice(target.offset, target.offset + target.length)).toContain('"bar"');
  });
});
