import { BooleanInput, NumberInput, coerceBooleanProperty } from '@angular/cdk/coercion';
import {
  Component,
  ChangeDetectionStrategy,
  Input,
  ChangeDetectorRef,
  ContentChildren,
  QueryList,
  Output,
  EventEmitter,
  ViewEncapsulation,
  HostListener,
  ElementRef,
  HostBinding,
  OnDestroy,
  AfterContentInit,
  inject,
  DestroyRef,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { SelectOptionComponent } from './select-option.component';
import { IconComponent } from '@shared/components/icon';
import { BaseFormControlDirective } from '@shared/components/form-control';
import { Subject } from 'rxjs';

import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';

@Component({
  selector: 'shared-select',
  templateUrl: 'select.component.html',
  styleUrls: ['select.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  host: { class: 'shared-select' },
  imports: [IconComponent],
  providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: SelectComponent, multi: true }],
})
export class SelectComponent<T = any>
  extends BaseFormControlDirective
  implements AfterContentInit, ControlValueAccessor, OnDestroy
{
  private destroyRef = inject(DestroyRef);

  @ContentChildren(SelectOptionComponent)
  options: QueryList<SelectOptionComponent>;

  private _value: T;
  @Input()
  get value(): T {
    return this._value;
  }
  set value(value: T) {
    this._value = value;
  }
  @Output()
  valueChange = new EventEmitter<T>();

  @Input()
  placeholder: string;

  @Input()
  clearable: BooleanInput = true;

  get clearableEnabled(): boolean {
    return coerceBooleanProperty(this.clearable);
  }

  @Input()
  clearValue: T = undefined;

  @Input()
  @HostBinding('tabindex')
  tabindex: NumberInput = 0;

  get hasValue(): boolean {
    if (typeof this.value === 'string') {
      return this.value.trim().length > 0;
    }
    return this.value !== undefined && this.value !== null;
  }

  private _onChange: (value: T) => void;

  private _onTouched: () => void;

  get displayValue(): string {
    const option = this.options.find((option) => option.value === this.value);
    return option?.getLabel() ?? this.placeholder ?? '';
  }

  private _open: boolean = false;
  get isOpen(): boolean {
    return this._open;
  }

  private _forceClose: boolean = false;
  get forceClose(): boolean {
    return this._forceClose;
  }

  private _onDestroy$ = new Subject<void>();

  constructor(
    private _cdr: ChangeDetectorRef,
    private _elementRef: ElementRef<HTMLElement>,
  ) {
    super();
  }

  ngAfterContentInit(): void {
    this.options.changes.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
      this.options.forEach((option) => {
        option.registerOnSelect((value) => this.setValue(value));
      });
    });

    this.options.forEach((option) => {
      option.registerOnSelect((value) => this.setValue(value));
    });
  }

  ngOnDestroy() {
    this._onDestroy$.next();
    this._onDestroy$.complete();
  }

  writeValue(obj: any): void {
    this.value = obj;
    this._cdr.markForCheck();
  }

  registerOnChange(fn: any): void {
    this._onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this._onTouched = fn;
  }

  setDisabledState?(isDisabled: boolean): void {
    this.disabled = isDisabled;
    this._cdr.markForCheck();
  }

  setValue(value: T): void {
    this.value = value;
    this._onChange(value);
    this._onTouched();
    this.valueChange.emit(value);
    this.close();
    this._cdr.markForCheck();
  }

  toggle(): void {
    if (this.isOpen) {
      this.close();
    } else {
      this.open();
    }
  }

  open() {
    if (this.disabled || this.readonly) {
      return;
    }
    this._open = true;
    this._forceClose = false;
    this._cdr.markForCheck();

    this.options.forEach((option) => {
      option.registerOnSelect((value) => this.setValue(value));
    });
  }

  close() {
    this._open = false;
    this._forceClose = true;
    this._cdr.markForCheck();
  }

  @HostListener('document:click', ['$event'])
  onDocumentClick(event: MouseEvent): void {
    if (this.isOpen && !this.contains(event.target)) {
      this.close();
    }
  }

  @HostListener('keydown', ['$event'])
  onKeydown(event: KeyboardEvent): void {}

  private contains(target: EventTarget): boolean {
    return this._elementRef.nativeElement.contains(target as Element);
  }

  @HostListener('blur')
  onBlur(): void {
    this._onTouched();
  }
}
