import { describe, it, expect, beforeEach } from 'vitest';
import '../../core/element.js';
import './combobox.js';

const tick = () => new Promise((r) => queueMicrotask(r));

function mount(html) {
  const wrap = document.createElement('div');
  wrap.innerHTML = html;
  document.body.appendChild(wrap);
  return wrap.firstElementChild;
}

describe('combobox-ui', () => {
  beforeEach(() => { document.body.innerHTML = ''; });

  it('parses declarative <option> children into the options model', async () => {
    const el = mount(`
      <combobox-ui>
        <option value="a">Alpha</option>
        <option value="b">Beta</option>
        <option value="c">Gamma</option>
      </combobox-ui>
    `);
    await tick();
    expect(el.options.length).toBe(3);
    expect(el.options[0].value).toBe('a');
    expect(el.options[0].label).toBe('Alpha');
  });

  // Wrapper-trap regression (audit-wrapper-trap arm 2): a `.map()`-interpolated
  // affordance (here [slot="footer"]) arrives nested in a display:contents
  // wrapper span. The old `:scope > [slot]` grab missed it → innerHTML='' wiped
  // it. logicalSlotted pierces the wrapper so the footer survives the rebuild.
  it('preserves a WRAPPED (interpolated) [slot="footer"] across the rebuild (wrapper trap)', async () => {
    const el = mount(`
      <combobox-ui>
        <option value="a">Alpha</option>
        <span style="display:contents" role="presentation"><div slot="footer" data-test="ft">Footer</div></span>
      </combobox-ui>
    `);
    await tick(); await tick();
    const footer = el.querySelector('[slot="footer"]');
    expect(footer).not.toBeNull();
    expect(footer.dataset.test).toBe('ft');
  });

  it('honours <option selected> as initial value', async () => {
    const el = mount(`
      <combobox-ui>
        <option value="a">Alpha</option>
        <option value="b" selected>Beta</option>
        <option value="c">Gamma</option>
      </combobox-ui>
    `);
    await tick();
    expect(el.value).toBe('b');
  });

  it('renders ARIA combobox + listbox wiring per WAI-APG', async () => {
    const el = mount(`
      <combobox-ui>
        <option value="a">Alpha</option>
      </combobox-ui>
    `);
    await tick();
    // Per spec §5: role + aria-expanded live on the editable surface.
    const input = el.querySelector('[data-input]');
    expect(input).toBeTruthy();
    expect(input.getAttribute('role')).toBe('combobox');
    expect(input.getAttribute('aria-autocomplete')).toBe('list');
    expect(input.getAttribute('aria-expanded')).toBe('false');
    expect(input.hasAttribute('aria-controls')).toBe(true);
    const listbox = el.querySelector('[data-listbox]');
    expect(listbox).toBeTruthy();
    expect(listbox.getAttribute('role')).toBe('listbox');
  });

  it('programmatic .options = [...] re-renders the listbox', async () => {
    const el = mount(`<combobox-ui></combobox-ui>`);
    await tick();
    el.options = [
      { value: 'a', label: 'Alpha' },
      { value: 'b', label: 'Beta' },
    ];
    await tick();
    const opts = el.querySelectorAll('[role="option"]');
    expect(opts.length).toBe(2);
    expect(opts[0].dataset.value).toBe('a');
  });

  it('reflects [open] + aria-expanded on the input when opening', async () => {
    const el = mount(`
      <combobox-ui>
        <option value="a">Alpha</option>
        <option value="b">Beta</option>
      </combobox-ui>
    `);
    await tick();
    el.open = true;
    await tick();
    expect(el.hasAttribute('open')).toBe(true);
    const input = el.querySelector('[data-input]');
    expect(input.getAttribute('aria-expanded')).toBe('true');
  });

  it('selectOption() commits + fires change with the option object', async () => {
    const el = mount(`
      <combobox-ui>
        <option value="a">Alpha</option>
        <option value="b">Beta</option>
      </combobox-ui>
    `);
    await tick();
    let changeDetail = null;
    el.addEventListener('change', (e) => { changeDetail = e.detail; });
    const hit = el.selectOption('b');
    expect(hit).toBe(true);
    await tick();
    expect(el.value).toBe('b');
    expect(changeDetail).not.toBeNull();
    expect(changeDetail.value).toBe('b');
    expect(changeDetail.option?.value).toBe('b');
    expect(changeDetail.source).toBe('programmatic');
  });

  it('selectOption() returns false for an unknown value (no commit)', async () => {
    const el = mount(`
      <combobox-ui>
        <option value="a">Alpha</option>
      </combobox-ui>
    `);
    await tick();
    let changeCount = 0;
    el.addEventListener('change', () => { changeCount += 1; });
    const hit = el.selectOption('zzz');
    expect(hit).toBe(false);
    expect(el.value).toBe('');
    expect(changeCount).toBe(0);
  });

  it('clear() resets value + fires change with null option', async () => {
    const el = mount(`
      <combobox-ui value="a">
        <option value="a">Alpha</option>
        <option value="b">Beta</option>
      </combobox-ui>
    `);
    await tick();
    let changeDetail = null;
    el.addEventListener('change', (e) => { changeDetail = e.detail; });
    el.clear();
    await tick();
    expect(el.value).toBe('');
    expect(changeDetail.option).toBeNull();
  });

  it('renders aria-selected on the currently committed option', async () => {
    const el = mount(`
      <combobox-ui value="b">
        <option value="a">Alpha</option>
        <option value="b">Beta</option>
      </combobox-ui>
    `);
    await tick();
    const opts = el.querySelectorAll('[role="option"]');
    const selected = Array.from(opts).filter((o) => o.getAttribute('aria-selected') === 'true');
    expect(selected.length).toBe(1);
    expect(selected[0].dataset.value).toBe('b');
  });

  it('exposes UICombobox extending UIFormElement (form participation)', async () => {
    const el = mount(`
      <combobox-ui name="x">
        <option value="a">Alpha</option>
      </combobox-ui>
    `);
    await tick();
    expect(typeof el.checkValidity).toBe('function');
    expect(typeof el.syncValue).toBe('function');
  });

  it('disabled options carry aria-disabled and are skipped by [role="option"][not aria-disabled]', async () => {
    const el = mount(`
      <combobox-ui>
        <option value="a">Alpha</option>
        <option value="b" disabled>Beta</option>
        <option value="c">Gamma</option>
      </combobox-ui>
    `);
    await tick();
    const opts = el.querySelectorAll('[role="option"]');
    expect(opts.length).toBe(3);
    expect(opts[1].getAttribute('aria-disabled')).toBe('true');
    const navigable = el.querySelectorAll('[role="option"]:not([aria-disabled="true"])');
    expect(navigable.length).toBe(2);
  });
});
