import { describe, it, expect, beforeEach, vi } from 'vitest';
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import * as React from 'react';

import { Combobox, type ComboboxOption } from '../components/Combobox.js';
import { TagInput } from '../components/TagInput.js';

// happy-dom does not implement scrollIntoView — stub it so the Combobox
// highlight-into-view effect (ROADMAP V4 IMP-1) does not throw under test.
beforeEach(() => {
  cleanup();
  Element.prototype.scrollIntoView = vi.fn();
});

const OPTIONS: ComboboxOption[] = [
  { value: 'apple', label: 'Apple' },
  { value: 'banana', label: 'Banana' },
  { value: 'cherry', label: 'Cherry' },
];

describe('Combobox', () => {
  it('filters options by query and selects via keyboard', () => {
    const onChange = vi.fn();
    function Harness() {
      const [v, setV] = React.useState<string | null>(null);
      return (
        <Combobox
          options={OPTIONS}
          value={v}
          onChange={(next) => {
            setV(next);
            onChange(next);
          }}
        />
      );
    }
    render(<Harness />);
    const input = screen.getByRole('combobox');

    fireEvent.change(input, { target: { value: 'ban' } });
    expect(screen.getByText('Banana')).toBeTruthy();
    expect(screen.queryByText('Apple')).toBeNull();

    fireEvent.keyDown(input, { key: 'Enter' });
    expect(onChange).toHaveBeenCalledWith('banana');
  });

  it('scrolls the highlighted option into view on arrow nav (IMP-1)', () => {
    render(<Combobox options={OPTIONS} value={null} onChange={() => {}} />);
    const input = screen.getByRole('combobox');
    fireEvent.focus(input);
    fireEvent.keyDown(input, { key: 'ArrowDown' });
    expect(Element.prototype.scrollIntoView).toHaveBeenCalled();
  });
});

describe('TagInput', () => {
  function Harness({ commitOnBlur }: { commitOnBlur?: boolean }) {
    const [tags, setTags] = React.useState<string[]>([]);
    return <TagInput value={tags} onChange={setTags} commitOnBlur={commitOnBlur} />;
  }

  it('commits a tag on Enter', () => {
    render(<Harness />);
    const input = screen.getByLabelText('Add a tag');
    fireEvent.change(input, { target: { value: 'urgent' } });
    fireEvent.keyDown(input, { key: 'Enter' });
    expect(screen.getByText('urgent')).toBeTruthy();
  });

  it('does NOT commit on blur by default (IMP-3)', () => {
    render(<Harness />);
    const input = screen.getByLabelText('Add a tag');
    fireEvent.change(input, { target: { value: 'draft' } });
    fireEvent.blur(input);
    expect(screen.queryByText('draft')).toBeNull();
  });

  it('commits on blur when commitOnBlur is enabled (IMP-3)', () => {
    render(<Harness commitOnBlur />);
    const input = screen.getByLabelText('Add a tag');
    fireEvent.change(input, { target: { value: 'keep' } });
    fireEvent.blur(input);
    expect(screen.getByText('keep')).toBeTruthy();
  });

  it('removes the last tag on Backspace with an empty buffer', () => {
    render(<Harness />);
    const input = screen.getByLabelText('Add a tag');
    fireEvent.change(input, { target: { value: 'one' } });
    fireEvent.keyDown(input, { key: 'Enter' });
    fireEvent.change(input, { target: { value: 'two' } });
    fireEvent.keyDown(input, { key: 'Enter' });
    expect(screen.getByText('two')).toBeTruthy();

    fireEvent.keyDown(input, { key: 'Backspace' });
    expect(screen.queryByText('two')).toBeNull();
    expect(screen.getByText('one')).toBeTruthy();
  });
});
