import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { ChipsComponent } from './chips.component';

describe('ChipsComponent', () => {
  let spectator: Spectator<ChipsComponent<string>>;
  const createComponent = createComponentFactory({
    component: ChipsComponent<string>,
  });

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

  it('should create', () => {
    expect(spectator.component).toBeTruthy();
  });

  describe('ControlValueAccessor Implementation', () => {
    it('should implement writeValue correctly', () => {
      // Arrange
      const testValue = 'test';

      // Act
      spectator.component.writeValue(testValue);

      // Assert
      expect(spectator.component.value()).toBe(testValue);
    });

    it('should register onChange function', () => {
      // Arrange
      const mockOnChange = jest.fn();

      // Act
      spectator.component.registerOnChange(mockOnChange);

      // Assert
      expect(spectator.component['onChange']).toBe(mockOnChange);
    });

    it('should register onTouched function', () => {
      // Arrange
      const mockOnTouched = jest.fn();

      // Act
      spectator.component.registerOnTouched(mockOnTouched);

      // Assert
      expect(spectator.component['onTouched']).toBe(mockOnTouched);
    });

    it('should set disabled state correctly', () => {
      // Act
      spectator.component.setDisabledState?.(true);

      // Assert
      expect(spectator.component.disabled()).toBe(true);
      expect(spectator.component.disabledClass()).toBe('ui-chips__disabled');
    });
  });

  describe('Selection Behavior', () => {
    it('should select value and emit change event', () => {
      // Arrange
      const testValue = 'test';
      const mockOnChange = jest.fn();
      spectator.component.registerOnChange(mockOnChange);
      const mockOnTouched = jest.fn();
      spectator.component.registerOnTouched(mockOnTouched);

      // Act
      spectator.component.select(testValue);

      // Assert
      expect(spectator.component.value()).toBe(testValue);
      expect(mockOnChange).toHaveBeenCalledWith(testValue);
      expect(mockOnTouched).toHaveBeenCalled();
    });

    it('should select value without emitting change event when emitEvent is false', () => {
      // Arrange
      const testValue = 'test';
      const mockOnChange = jest.fn();
      spectator.component.registerOnChange(mockOnChange);

      // Act
      spectator.component.select(testValue, { emitEvent: false });

      // Assert
      expect(spectator.component.value()).toBe(testValue);
      expect(mockOnChange).not.toHaveBeenCalled();
    });

    it('should toggle value correctly', () => {
      // Arrange
      const testValue = 'test';
      const mockOnChange = jest.fn();
      spectator.component.registerOnChange(mockOnChange);

      // Act
      spectator.component.toggle(testValue);

      // Assert - First toggle selects the value
      expect(spectator.component.value()).toBe(testValue);
      expect(mockOnChange).toHaveBeenCalledWith(testValue);

      // Act - Second toggle deselects the value
      spectator.component.toggle(testValue);

      // Assert
      expect(spectator.component.value()).toBeUndefined();
      expect(mockOnChange).toHaveBeenCalledWith(undefined);
    });

    it('should toggle value without emitting change event when emitEvent is false', () => {
      // Arrange
      const testValue = 'test';
      const mockOnChange = jest.fn();
      spectator.component.registerOnChange(mockOnChange);

      // Act
      spectator.component.toggle(testValue, { emitEvent: false });

      // Assert
      expect(spectator.component.value()).toBe(testValue);
      expect(mockOnChange).not.toHaveBeenCalled();
    });
  });

  describe('CSS Classes', () => {
    it('should have base class', () => {
      expect(spectator.element).toHaveClass('ui-chips');
    });

    it('should add disabled class when disabled', () => {
      // Act
      spectator.component.setDisabledState?.(true);
      spectator.detectChanges();

      // Assert
      expect(spectator.element).toHaveClass('ui-chips__disabled');
    });

    it('should not have disabled class when enabled', () => {
      // Act
      spectator.component.setDisabledState?.(false);
      spectator.detectChanges();

      // Assert
      expect(spectator.element).not.toHaveClass('ui-chips__disabled');
    });
  });

  describe('Selection Model', () => {
    it('should allow only single selection', () => {
      // Arrange
      const value1 = 'test1';
      const value2 = 'test2';

      // Act
      spectator.component.select(value1);
      spectator.component.select(value2);

      // Assert
      expect(spectator.component.value()).toBe(value2);
    });

    it('should clear selection when toggling selected value', () => {
      // Arrange
      const testValue = 'test';
      spectator.component.select(testValue);

      // Act
      spectator.component.toggle(testValue);

      // Assert
      expect(spectator.component.value()).toBeUndefined();
    });
  });

  describe('Edge Cases', () => {
    it('should handle null value in writeValue', () => {
      // Act
      spectator.component.writeValue(null);

      // Assert
      expect(spectator.component.value()).toBeNull();
    });

    it('should handle undefined value in writeValue', () => {
      // Act
      spectator.component.writeValue(undefined);

      // Assert
      expect(spectator.component.value()).toBeUndefined();
    });

    it('should not throw when selecting null value', () => {
      // Act & Assert
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      expect(() => spectator.component.select(null as any)).not.toThrow();
    });

    it('should not throw when toggling undefined value', () => {
      // Act & Assert
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      expect(() => spectator.component.toggle(undefined as any)).not.toThrow();
    });
  });
});
