import { Component } from '@angular/core';
import { By } from '@angular/platform-browser';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { DropdownButtonComponent } from './dropdown.component';
import { DropdownOptionComponent } from './dropdown-option.component';

/**
 * Test host component that wraps the dropdown with options
 */
@Component({
  selector: 'ui-test-host',
  standalone: true,
  imports: [DropdownButtonComponent, DropdownOptionComponent],
  template: `
    <ui-dropdown>
      <ui-dropdown-option [value]="'option1'" [disabled]="option1Disabled">
        Option 1
      </ui-dropdown-option>
      <ui-dropdown-option [value]="'option2'">Option 2</ui-dropdown-option>
      <ui-dropdown-option [value]="'option3'">Option 3</ui-dropdown-option>
    </ui-dropdown>
  `,
})
class TestHostComponent {
  option1Disabled = false;
}

describe('DropdownOptionComponent', () => {
  let spectator: Spectator<TestHostComponent>;

  const createComponent = createComponentFactory({
    component: TestHostComponent,
  });

  beforeEach(() => {
    spectator = createComponent();
  });

  const getDropdown = () =>
    spectator.debugElement
      .query(By.directive(DropdownButtonComponent))
      .componentInstance as DropdownButtonComponent<string>;

  // Access options through dropdown's contentChildren signal (works before rendering)
  const getOptions = () =>
    getDropdown().options() as unknown as DropdownOptionComponent<string>[];
  const getFirstOption = () => getOptions()[0];

  // For DOM queries, we need to open the dropdown first (options render in CDK overlay)
  const openAndGetOptionElements = () => {
    getDropdown().open();
    spectator.detectChanges();
    // CDK overlay renders outside component tree, query from document
    return Array.from(
      document.querySelectorAll('ui-dropdown-option'),
    ) as HTMLElement[];
  };

  it('should create options', () => {
    expect(getOptions().length).toBe(3);
  });

  describe('Content Rendering', () => {
    it('should render content via ng-content', () => {
      const elements = openAndGetOptionElements();
      expect(elements[0].textContent?.trim()).toBe('Option 1');
      expect(elements[1].textContent?.trim()).toBe('Option 2');
    });

    it('should return text content from getLabel()', () => {
      openAndGetOptionElements(); // Need to render first for getLabel() to work
      expect(getFirstOption().getLabel()).toBe('Option 1');
    });
  });

  describe('ARIA Attributes', () => {
    it('should have role="option"', () => {
      const element = openAndGetOptionElements()[0];
      expect(element.getAttribute('role')).toBe('option');
    });

    it('should have aria-selected="false" when not selected', () => {
      const element = openAndGetOptionElements()[0];
      expect(element.getAttribute('aria-selected')).toBe('false');
    });

    it('should have aria-selected="true" when selected', () => {
      // Arrange - select first option
      getDropdown().value.set('option1');
      const element = openAndGetOptionElements()[0];

      // Assert
      expect(element.getAttribute('aria-selected')).toBe('true');
    });

    it('should have tabindex="-1"', () => {
      const element = openAndGetOptionElements()[0];
      expect(element.getAttribute('tabindex')).toBe('-1');
    });
  });

  describe('CSS Classes', () => {
    it('should have base class ui-dropdown-option', () => {
      const element = openAndGetOptionElements()[0];
      expect(element).toHaveClass('ui-dropdown-option');
    });

    it('should add "selected" class when option value matches dropdown value', () => {
      // Arrange
      getDropdown().value.set('option1');
      const element = openAndGetOptionElements()[0];

      // Assert
      expect(element).toHaveClass('selected');
    });

    it('should not have "selected" class when option value does not match', () => {
      // Arrange
      getDropdown().value.set('option2');
      const element = openAndGetOptionElements()[0];

      // Assert
      expect(element).not.toHaveClass('selected');
    });

    it('should add "disabled" class when disabled', () => {
      // Arrange
      spectator.component.option1Disabled = true;
      spectator.detectChanges();
      const element = openAndGetOptionElements()[0];

      // Assert
      expect(element).toHaveClass('disabled');
    });

    it('should not have "disabled" class when enabled', () => {
      // Assert
      const element = openAndGetOptionElements()[0];
      expect(element).not.toHaveClass('disabled');
    });
  });

  describe('Active State (Keyboard Navigation)', () => {
    it('should add "active" class when setActiveStyles() is called', () => {
      // Arrange - open dropdown first to render options
      const elements = openAndGetOptionElements();

      // Act
      getFirstOption().setActiveStyles();
      spectator.detectChanges();

      // Assert
      expect(elements[0]).toHaveClass('active');
    });

    it('should remove "active" class when setInactiveStyles() is called', () => {
      // Arrange
      const elements = openAndGetOptionElements();
      getFirstOption().setActiveStyles();
      spectator.detectChanges();

      // Act
      getFirstOption().setInactiveStyles();
      spectator.detectChanges();

      // Assert
      expect(elements[0]).not.toHaveClass('active');
    });
  });

  describe('Selection Behavior', () => {
    it('should update dropdown value when option is clicked', () => {
      // Arrange
      const dropdown = getDropdown();
      const selectSpy = jest.spyOn(dropdown, 'select');
      const closeSpy = jest.spyOn(dropdown, 'close');
      const elements = openAndGetOptionElements();

      // Act
      elements[0].click();
      spectator.detectChanges();

      // Assert
      expect(selectSpy).toHaveBeenCalledWith(getFirstOption());
      expect(closeSpy).toHaveBeenCalled();
    });

    it('should NOT call select when disabled option is clicked', () => {
      // Arrange
      spectator.component.option1Disabled = true;
      spectator.detectChanges();
      const dropdown = getDropdown();
      const selectSpy = jest.spyOn(dropdown, 'select');
      const elements = openAndGetOptionElements();

      // Act
      elements[0].click();
      spectator.detectChanges();

      // Assert
      expect(selectSpy).not.toHaveBeenCalled();
    });
  });

  describe('Highlightable Interface', () => {
    it('should implement setActiveStyles method', () => {
      expect(typeof getFirstOption().setActiveStyles).toBe('function');
    });

    it('should implement setInactiveStyles method', () => {
      expect(typeof getFirstOption().setInactiveStyles).toBe('function');
    });

    it('should expose disabled property for skip predicate', () => {
      spectator.component.option1Disabled = true;
      spectator.detectChanges();
      expect(getFirstOption().disabled).toBe(true);
    });
  });

  describe('Value Comparison', () => {
    it('should detect selection with primitive values', () => {
      // Arrange
      getDropdown().value.set('option1');
      spectator.detectChanges();

      // Assert
      expect(getFirstOption().selected()).toBe(true);
      expect(getOptions()[1].selected()).toBe(false);
    });
  });
});

/**
 * Tests for object value comparison (deep equality)
 */
@Component({
  selector: 'ui-test-host-objects',
  standalone: true,
  imports: [DropdownButtonComponent, DropdownOptionComponent],
  template: `
    <ui-dropdown>
      <ui-dropdown-option [value]="{ id: 1, name: 'First' }">
        First
      </ui-dropdown-option>
      <ui-dropdown-option [value]="{ id: 2, name: 'Second' }">
        Second
      </ui-dropdown-option>
    </ui-dropdown>
  `,
})
class TestHostObjectsComponent {}

describe('DropdownOptionComponent with Object Values', () => {
  let spectator: Spectator<TestHostObjectsComponent>;

  const createComponent = createComponentFactory({
    component: TestHostObjectsComponent,
  });

  beforeEach(() => {
    spectator = createComponent();
  });

  const getDropdown = () =>
    spectator.debugElement
      .query(By.directive(DropdownButtonComponent))
      .componentInstance as DropdownButtonComponent<{
      id: number;
      name: string;
    }>;

  // Access options through dropdown's contentChildren signal
  const getOptions = () =>
    getDropdown().options() as unknown as DropdownOptionComponent<{
      id: number;
      name: string;
    }>[];

  it('should detect selection using deep equality for objects', () => {
    // Arrange - set value to equal object (different reference)
    getDropdown().value.set({ id: 1, name: 'First' });
    spectator.detectChanges();

    // Assert
    expect(getOptions()[0].selected()).toBe(true);
    expect(getOptions()[1].selected()).toBe(false);
  });
});
