import '@angular/compiler';
import { ComponentRef } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import type { Option } from '../types';
import { AsyncSearchSelect } from './async-search-select.component';

describe('AsyncSearchSelect', () => {
  let fixture: ComponentFixture<AsyncSearchSelect>;
  let component: AsyncSearchSelect;
  let componentRef: ComponentRef<AsyncSearchSelect>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [AsyncSearchSelect],
    });

    fixture = TestBed.createComponent(AsyncSearchSelect);
    component = fixture.componentInstance;
    componentRef = fixture.componentRef;
  });

  describe('selectedOption computed', () => {
    it('should be null when value is null', () => {
      componentRef.setInput('value', null);
      componentRef.setInput('options', []);
      fixture.detectChanges();
      // biome-ignore lint/suspicious/noExplicitAny: access protected computed for test
      expect((component as any).selectedOption()).toBeNull();
    });

    it('should resolve the matching option from value + options', () => {
      const options: Option[] = [
        { value: 'a', label: 'A' },
        { value: 'b', label: 'B' },
      ];
      componentRef.setInput('value', 'b');
      componentRef.setInput('options', options);
      fixture.detectChanges();
      // biome-ignore lint/suspicious/noExplicitAny: access protected computed for test
      expect((component as any).selectedOption()).toEqual(options[1]);
    });

    it('should be null when value has no matching option', () => {
      componentRef.setInput('value', 'missing');
      componentRef.setInput('options', [{ value: 'a', label: 'A' }]);
      fixture.detectChanges();
      // biome-ignore lint/suspicious/noExplicitAny: access protected computed for test
      expect((component as any).selectedOption()).toBeNull();
    });
  });

  describe('valueChange output', () => {
    it('should emit the option value when a selection happens', () => {
      const emitted: (string | null)[] = [];
      component.valueChange.subscribe((v) => emitted.push(v));

      // biome-ignore lint/suspicious/noExplicitAny: invoke protected handler
      (component as any).onComboboxValueChange({ value: 'x', label: 'X' });

      expect(emitted).toEqual(['x']);
    });

    it('should emit null when selection is cleared', () => {
      const emitted: (string | null)[] = [];
      component.valueChange.subscribe((v) => emitted.push(v));

      // biome-ignore lint/suspicious/noExplicitAny: invoke protected handler
      (component as any).onComboboxValueChange(null);

      expect(emitted).toEqual([null]);
    });
  });

  describe('searchChange output', () => {
    beforeEach(() => {
      vi.useFakeTimers();
    });

    afterEach(() => {
      vi.useRealTimers();
    });

    it('should debounce search input before emitting', () => {
      componentRef.setInput('debounce', 300);
      fixture.detectChanges();

      const emitted: string[] = [];
      component.searchChange.subscribe((v) => emitted.push(v));

      // biome-ignore lint/suspicious/noExplicitAny: invoke protected handler
      const onSearch = (component as any).onComboboxSearchChange.bind(component);

      onSearch('a');
      onSearch('ac');
      onSearch('acm');
      expect(emitted).toEqual([]); // nothing emitted yet (still within debounce window)

      vi.advanceTimersByTime(300);
      expect(emitted).toEqual(['acm']); // only the last value after debounce
    });

    it('should suppress duplicate consecutive values', () => {
      componentRef.setInput('debounce', 100);
      fixture.detectChanges();

      const emitted: string[] = [];
      component.searchChange.subscribe((v) => emitted.push(v));

      // biome-ignore lint/suspicious/noExplicitAny: invoke protected handler
      const onSearch = (component as any).onComboboxSearchChange.bind(component);

      onSearch('same');
      vi.advanceTimersByTime(100);
      onSearch('same');
      vi.advanceTimersByTime(100);

      expect(emitted).toEqual(['same']);
    });
  });
});
