import { afterEach, describe, expect, it, vi } from 'vitest';
import { cleanup, fireEvent, render } from '@testing-library/react';
import { Accordion as AccordionController, type AccordionOptions } from '@42/core/accordion';
import { useC42 } from './useC42';
import { Accordion } from './components/Accordion';

/**
 * Markup the headless `Accordion` controller enhances. Mirrors the structure
 * the controller expects: a root with items, each holding a trigger + panel.
 */
function AccordionHarness({ options }: { options?: AccordionOptions }) {
  const ref = useC42<AccordionOptions, HTMLDivElement>(AccordionController, options);
  return (
    <div ref={ref} data-c42-accordion>
      <div data-c42-accordion-item data-value="a">
        <button data-c42-accordion-trigger>A</button>
        <div data-c42-accordion-panel>Panel A</div>
      </div>
      <div data-c42-accordion-item data-value="b">
        <button data-c42-accordion-trigger>B</button>
        <div data-c42-accordion-panel>Panel B</div>
      </div>
    </div>
  );
}

afterEach(() => {
  cleanup();
  vi.restoreAllMocks();
});

describe('useC42', () => {
  it('instantiates the controller and the markup responds to interaction', () => {
    const { container } = render(<AccordionHarness />);

    const trigger = container.querySelector<HTMLButtonElement>('[data-c42-accordion-trigger]')!;

    // The controller ran inside useEffect: it added ARIA wiring to the markup.
    expect(trigger.getAttribute('aria-expanded')).toBe('false');

    // Clicking the enhanced trigger toggles the panel — proof the live instance
    // is bound to the DOM, not just the static React output.
    fireEvent.click(trigger);
    expect(trigger.getAttribute('aria-expanded')).toBe('true');
  });

  it('calls destroy() when the component unmounts', () => {
    const destroySpy = vi.spyOn(AccordionController.prototype, 'destroy');

    const { unmount } = render(<AccordionHarness />);
    expect(destroySpy).not.toHaveBeenCalled();

    unmount();
    expect(destroySpy).toHaveBeenCalledTimes(1);
  });

  it('tears down listeners so the detached controller no longer reacts', () => {
    const { container, unmount } = render(<AccordionHarness />);
    const trigger = container.querySelector<HTMLButtonElement>('[data-c42-accordion-trigger]')!;

    // Keep a reference to the node so we can probe it after React detaches it.
    const detached = trigger;
    unmount();

    fireEvent.click(detached);
    // destroy() removed the click listener, so state is frozen at its last value.
    expect(detached.getAttribute('aria-expanded')).toBe('false');
  });
});

describe('<Accordion> generated wrapper', () => {
  it('re-emits the accordion:change DOM event as the onChange callback', () => {
    const onChange = vi.fn();

    const { container } = render(
      <Accordion onChange={onChange}>
        <div data-c42-accordion-item data-value="a">
          <button data-c42-accordion-trigger>A</button>
          <div data-c42-accordion-panel>Panel A</div>
        </div>
        <div data-c42-accordion-item data-value="b">
          <button data-c42-accordion-trigger>B</button>
          <div data-c42-accordion-panel>Panel B</div>
        </div>
      </Accordion>,
    );

    const triggers = container.querySelectorAll<HTMLButtonElement>(
      '[data-c42-accordion-trigger]',
    );

    fireEvent.click(triggers[0]!);

    expect(onChange).toHaveBeenCalledTimes(1);
    // The wrapper unwraps event.detail and forwards (detail, event).
    const [detail, event] = onChange.mock.calls[0]!;
    expect(detail).toEqual({ value: ['a'] });
    expect(event).toBeInstanceOf(CustomEvent);
    expect(event.type).toBe('accordion:change');
  });
});
