import { ChangeDetectionStrategy, Component, computed, model } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { SelectionModel } from '@angular/cdk/collections';

/**
 * A reusable chip component that implements ControlValueAccessor for form integration.
 * Supports single selection mode and disabled state management.
 *
 * @template T - The type of value that can be selected in the chip
 *
 * @example
 * ```typescript
 * // Basic usage in a template
 * <ui-chips [(ngModel)]="selectedValue">
 *   <ui-chip [value]="item1">Item 1</ui-chip>
 *   <ui-chip [value]="item2">Item 2</ui-chip>
 * </ui-chips>
 *
 * // Reactive form usage
 * <ui-chips formControlName="selection">
 *   <ui-chip [value]="item1">Item 1</ui-chip>
 *   <ui-chip [value]="item2">Item 2</ui-chip>
 * </ui-chips>
 * ```
 */
@Component({
  selector: 'ui-chips',
  template: `<ng-content></ng-content>`,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    '[class]': '["ui-chips", disabledClass()]',
  },
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: ChipsComponent,
      multi: true,
    },
  ],
})
export class ChipsComponent<T> implements ControlValueAccessor {
  /** The currently selected value */
  value = model<T>();

  /** Whether the chips component is disabled */
  disabled = model(false);

  /** Computed CSS class for disabled state */
  disabledClass = computed(() => {
    return this.disabled() ? 'ui-chips__disabled' : '';
  });

  /** Internal selection model for managing chip selection state */
  private selectionModel: SelectionModel<T>;

  /** Callback function for value changes */
  onChange?: (value: T) => void;

  /** Callback function for touched state */
  onTouched?: () => void;

  constructor() {
    this.selectionModel = new SelectionModel<T>(false);
  }

  /**
   * Writes a new value to the component.
   * Part of the ControlValueAccessor interface.
   *
   * @param obj - The new value to write
   */
  writeValue(obj: unknown): void {
    this.selectionModel?.select(obj as T);
    this.value.set(obj as T);
  }

  /**
   * Registers a callback function that is called when the control's value changes.
   * Part of the ControlValueAccessor interface.
   *
   * @param fn - The callback function to register
   */
  registerOnChange(fn: () => void): void {
    this.onChange = fn;
  }

  /**
   * Registers a callback function that is called when the control is touched.
   * Part of the ControlValueAccessor interface.
   *
   * @param fn - The callback function to register
   */
  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }

  /**
   * Sets the disabled state of the component.
   * Part of the ControlValueAccessor interface.
   *
   * @param isDisabled - Whether the component should be disabled
   */
  setDisabledState?(isDisabled: boolean): void {
    this.disabled.set(isDisabled);
  }

  /**
   * Selects a value in the chip component.
   *
   * @param value - The value to select
   * @param options - Configuration options for the selection
   * @param options.emitEvent - Whether to emit the change event (default: true)
   */
  select(value: T, options: { emitEvent: boolean } = { emitEvent: true }) {
    this.selectionModel.select(value);
    this.onTouched?.();
    this.value.set(this.selectionModel.selected[0]);
    if (options.emitEvent) {
      this.onChange?.(this.selectionModel.selected[0]);
    }
  }

  /**
   * Toggles the selection state of a value in the chip component.
   *
   * @param value - The value to toggle
   * @param options - Configuration options for the toggle operation
   * @param options.emitEvent - Whether to emit the change event (default: true)
   */
  toggle(value: T, options: { emitEvent: boolean } = { emitEvent: true }) {
    this.selectionModel.toggle(value);
    this.onTouched?.();
    this.value.set(this.selectionModel.selected[0]);
    if (options.emitEvent) {
      this.onChange?.(this.selectionModel.selected[0]);
    }
  }

  /**
   * Determines if the specified value is currently selected.
   *
   * This method uses the underlying selection model to check whether the given value
   * is part of the selected items.
   *
   * @param value - The value to check for selection.
   * @returns True if the value is selected; otherwise, false.
   */
  isSelected(value: T): boolean {
    return this.selectionModel.isSelected(value);
  }
}
