import {
  Component,
  ChangeDetectionStrategy,
  Input,
  ContentChildren,
  QueryList,
  HostBinding,
  AfterContentInit,
  ChangeDetectorRef,
  forwardRef,
  HostListener,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { UiFormControlDirective } from '@ui/form-control';
import { DeviceDetectorService } from 'ngx-device-detector';
import { UiSelectOptionComponent } from './ui-select-option.component';

@Component({
  selector: 'ui-select',
  templateUrl: 'ui-select.component.html',
  styleUrls: ['ui-select.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => UiSelectComponent), multi: true },
    {
      provide: UiFormControlDirective,
      useExisting: UiSelectComponent,
    },
  ],
})
export class UiSelectComponent extends UiFormControlDirective<any> implements ControlValueAccessor, AfterContentInit {
  readonly type = 'ui-select';

  @Input()
  value: any;

  @Input()
  defaultLabel: string;

  get label() {
    const option = this.options.find((o) => o.value === this.value);
    if (option) {
      return this.displayValueFn(option) ?? this.defaultLabel;
    }
    return this.defaultLabel;
  }

  @Input()
  disabled = false;

  @Input() displayValueFn = (option: UiSelectOptionComponent) => {
    return option.label;
  };

  @ContentChildren(UiSelectOptionComponent, { read: UiSelectOptionComponent })
  options: QueryList<UiSelectOptionComponent>;

  get valueEmpty(): boolean {
    return !this.value;
  }

  @HostBinding('class.toggled')
  toggled = false;

  onChange = (_: any) => {};

  onTouched = () => {};

  constructor(private cdr: ChangeDetectorRef, private deviceService: DeviceDetectorService) {
    super();
  }

  clear(): void {
    this.setValue(undefined);
  }

  @HostListener('focus')
  focus(): void {
    if (this.deviceService.isDesktop()) {
      this.open();
    }
  }

  ngAfterContentInit() {
    this.registerOptionsSelect();
    this.options.changes.subscribe((_) => {
      this.registerOptionsSelect();
      this.cdr.markForCheck();
    });
  }

  private registerOptionsSelect() {
    this.options.forEach((option) => {
      option.registerOnSelect((value) => {
        this.setValue(value);
        this.close();
      });
      option.registerOnFocusOut(() => {
        if (!this.options.some((o) => o.focused)) {
          this.close();
        }
      });
    });
  }

  writeValue(obj: any): void {
    this.setValue(obj, false);
  }

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

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

  setDisabledState?(isDisabled: boolean): void {
    this.disabled = isDisabled;
    if (this.disabled) {
      this.close();
    }
  }

  setValue(value: any, emitEvent: boolean = true) {
    if (this.value !== value) {
      this.value = value;

      if (emitEvent) {
        this.onChange(this.value);
      }
    }

    this.onTouched();
    this.cdr.markForCheck();
  }

  toggle() {
    if (!this.disabled) {
      if (this.toggled) {
        this.close();
      } else {
        this.open();
      }
    }
  }

  open() {
    this.toggled = true;
    this.options.first?.focus();
    this.cdr.markForCheck();
  }

  close() {
    if (this.toggled) {
      this.toggled = false;
      this.onChange(this.value);
      this.onTouched();
      this.cdr.markForCheck();
    }
  }

  @HostListener('keydown', ['$event'])
  keyup(event: KeyboardEvent) {
    let optionIndex = this.options.toArray().findIndex((o) => o.focused);

    if (event.key === 'ArrowUp') {
      event.preventDefault();

      optionIndex--;
      if (optionIndex < 0) {
        return;
      }
    } else if (event.key === 'ArrowDown') {
      event.preventDefault();

      optionIndex++;
      if (this.options.length - 1 < optionIndex) {
        return;
      }
    } else {
      return;
    }

    this.options.toArray()[optionIndex]?.focus();
  }
}
