import '@angular/compiler';
import { ComponentRef } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import type { DataTableColumnDef } from '../data-table/types';
import { GridSelectElement } from './grid-select-element.component';

interface Client {
  id: string;
  name: string;
  email: string;
}

const CLIENTS: Client[] = [
  { id: 'c1', name: 'Acme', email: 'hello@acme.com' },
  { id: 'c2', name: 'Globex', email: 'info@globex.com' },
  { id: 'c3', name: 'Initech', email: 'contact@initech.com' },
];

const COLUMNS: DataTableColumnDef<Client>[] = [
  { id: 'name', header: 'Name', accessorKey: 'name' },
  { id: 'email', header: 'Email', accessorKey: 'email' },
];

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

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

    fixture = TestBed.createComponent<GridSelectElement<Client>>(
      GridSelectElement,
    );
    component = fixture.componentInstance;
    componentRef = fixture.componentRef;
    componentRef.setInput('columns', COLUMNS);
  });

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

    it('should resolve the matching entity from value + options', () => {
      componentRef.setInput('value', 'c2');
      componentRef.setInput('options', CLIENTS);
      fixture.detectChanges();
      // biome-ignore lint/suspicious/noExplicitAny: access protected computed for test
      expect((component as any).selectedEntity()).toEqual(CLIENTS[1]);
    });

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

  describe('triggerLabel computed', () => {
    it('should show placeholder when nothing is selected', () => {
      componentRef.setInput('value', null);
      componentRef.setInput('options', CLIENTS);
      componentRef.setInput('placeholder', 'Pick one...');
      fixture.detectChanges();
      // biome-ignore lint/suspicious/noExplicitAny: access protected computed for test
      expect((component as any).triggerLabel()).toBe('Pick one...');
    });

    it('should use displayWith to label the selected entity', () => {
      componentRef.setInput('value', 'c1');
      componentRef.setInput('options', CLIENTS);
      componentRef.setInput('displayWith', (client: Client) => `${client.name} <${client.email}>`);
      fixture.detectChanges();
      // biome-ignore lint/suspicious/noExplicitAny: access protected computed for test
      expect((component as any).triggerLabel()).toBe('Acme <hello@acme.com>');
    });

    it('should fall back to placeholder when selected id does not resolve', () => {
      componentRef.setInput('value', 'nonexistent');
      componentRef.setInput('options', CLIENTS);
      componentRef.setInput('placeholder', 'Pick one...');
      fixture.detectChanges();
      // biome-ignore lint/suspicious/noExplicitAny: access protected computed for test
      expect((component as any).triggerLabel()).toBe('Pick one...');
    });
  });

  describe('valueChange output via onRowSelect', () => {
    beforeEach(() => {
      componentRef.setInput('options', CLIENTS);
      fixture.detectChanges();
    });

    it('should emit the row id and close the dialog', () => {
      const emitted: (string | null)[] = [];
      component.valueChange.subscribe((v) => emitted.push(v));

      const closeSpy = vi.fn();
      // biome-ignore lint/suspicious/noExplicitAny: invoke protected handler
      (component as any).onRowSelect(CLIENTS[1], { close: closeSpy });

      expect(emitted).toEqual(['c2']);
      expect(closeSpy).toHaveBeenCalledTimes(1);
    });
  });

  describe('searchChange output via onSearchChange', () => {
    it('should propagate the search string to the host', () => {
      componentRef.setInput('options', CLIENTS);
      fixture.detectChanges();

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

      // biome-ignore lint/suspicious/noExplicitAny: invoke protected handler
      (component as any).onSearchChange('acme');
      // biome-ignore lint/suspicious/noExplicitAny: invoke protected handler
      (component as any).onSearchChange('globex');

      expect(emitted).toEqual(['acme', 'globex']);
    });
  });

  describe('disabled state', () => {
    it('should mark the trigger button as disabled when disabled is true', () => {
      componentRef.setInput('options', CLIENTS);
      componentRef.setInput('disabled', true);
      fixture.detectChanges();

      const triggerButton = fixture.nativeElement.querySelector(
        'button[hlmdialogtrigger]',
      ) as HTMLButtonElement | null;
      expect(triggerButton).not.toBeNull();
      expect(triggerButton?.disabled).toBe(true);
    });

    it('should leave the trigger button enabled by default', () => {
      componentRef.setInput('options', CLIENTS);
      fixture.detectChanges();

      const triggerButton = fixture.nativeElement.querySelector(
        'button[hlmdialogtrigger]',
      ) as HTMLButtonElement | null;
      expect(triggerButton).not.toBeNull();
      expect(triggerButton?.disabled).toBe(false);
    });
  });
});
