import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { act } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { Button } from './Button';
import { IconButton } from './IconButton';

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

let container: HTMLDivElement;
let root: Root;

beforeEach(() => {
  container = document.createElement('div');
  document.body.appendChild(container);
  root = createRoot(container);
});

afterEach(() => {
  act(() => root.unmount());
  container.remove();
});

function render(node: React.ReactNode) {
  act(() => root.render(node));
  const button = container.querySelector('button');
  if (!button) throw new Error('no <button> rendered');
  return button;
}

describe('Button', () => {
  test('defaults to type="button" so it never submits an enclosing form', () => {
    expect(render(<Button>Save</Button>).getAttribute('type')).toBe('button');
    expect(render(<Button type="submit">Save</Button>).getAttribute('type')).toBe('submit');
  });

  test('each variant resolves to a distinct class string', () => {
    // Whatever the CSS-module hashing, the variants must produce different
    // classes so the arch 09 §4 taxonomy is actually applied, not ignored.
    const classes = (
      ['primary', 'secondary', 'ghost', 'soft-accent', 'danger', 'danger-outline'] as const
    ).map((variant) => render(<Button variant={variant}>X</Button>).className);
    expect(new Set(classes).size).toBe(classes.length);
  });

  test('size and call-site classes compose onto the base class', () => {
    // Capture the string before the next render — re-rendering reuses the node.
    const mdClass = render(<Button>X</Button>).className;
    const lg = render(
      <Button size="lg" className="layout">
        X
      </Button>,
    );
    expect(lg.className).not.toBe(mdClass);
    expect(lg.classList.contains('layout')).toBe(true);
  });
});

describe('IconButton', () => {
  test('label provides the accessible name and the default tooltip', () => {
    const btn = render(<IconButton label="Close" />);
    expect(btn.getAttribute('aria-label')).toBe('Close');
    expect(btn.getAttribute('title')).toBe('Close');
  });

  test('an explicit title overrides the tooltip but not the accessible name', () => {
    const btn = render(<IconButton label="Datasets" title="Datasets (⌘/Ctrl+K)" />);
    expect(btn.getAttribute('aria-label')).toBe('Datasets');
    expect(btn.getAttribute('title')).toBe('Datasets (⌘/Ctrl+K)');
  });

  test('defaults to type="button" and composes size/call-site classes', () => {
    const md = render(<IconButton label="X" />);
    expect(md.getAttribute('type')).toBe('button');
    const mdClass = md.className;
    const sm = render(<IconButton label="X" size="sm" className="reveal" />);
    expect(sm.className).not.toBe(mdClass);
    expect(sm.classList.contains('reveal')).toBe(true);
  });
});
