import { ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, ContentChild, DoCheck, inject, Injector, input, OnInit, output, signal, TemplateRef, ViewChild } from "@angular/core";
import { FAST_KENDO_COMMON, FAST_PAGE_COMMON } from "../../app.config";
import { CommonModule } from "@angular/common";
import { ControlValueAccessor, FormControl, FormControlDirective, FormControlName, FormGroupDirective, NG_VALUE_ACCESSOR, NgControl } from "@angular/forms";
import { AutoCompleteComponent, KENDO_AUTOCOMPLETE, PreventableEvent } from "@progress/kendo-angular-dropdowns";
import { isMobileDevice } from '../utils/util';

type AutocompleteType = 'basic' | 'global-bar';

@Component({
  selector: 'fast-autocomplete',
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: true,
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    multi: true,
    useExisting: FastAutocompleteComponent
  },
  {
    provide: NgControl,
    multi: true,
    useExisting: FastAutocompleteComponent
  }],
  imports: [CommonModule, FAST_KENDO_COMMON, FAST_PAGE_COMMON, KENDO_AUTOCOMPLETE],
  template: `
  <kendo-autocomplete
  [filterable]="true"
  [placeholder]="placeholder()"
  [data]="data()"
  [valueField]="valueField()"
  [value]="internalValue()"
  [tabindex]="tabindex()"
  [listHeight]="listHeight()"
  [disabled]="isDisabled()"
  (filterChange)="onFilterChange($event)"
  (valueChange)="onValueChange($event)"
  (open)="open.emit($event)"
  [virtual]="{ itemHeight: isMobileDevice() ? 48 : 28 }"
  [class]="conditionalClasses()"
  >
  @if(itemTemplate) {
    <ng-template kendoAutoCompleteItemTemplate let-dataItem>
      <ng-container
          [ngTemplateOutlet]="itemTemplate"
          [ngTemplateOutletContext]="{ dataItem: dataItem }"
        ></ng-container>
    </ng-template>
  }
  </kendo-autocomplete>
  `
})

export class FastAutocompleteComponent<T extends object> implements ControlValueAccessor, OnInit, DoCheck {
  //  autocomplete necessities, allows a custom template
  @ContentChild('itemTemplate', { read: TemplateRef }) itemTemplate?: TemplateRef<undefined>;
  @ViewChild(AutoCompleteComponent) autocomplete: AutoCompleteComponent;

  cdr = inject(ChangeDetectorRef);
  injector = inject(Injector);
  isMobileDevice = isMobileDevice;

  data = input<T[] | null>();
  type = input<AutocompleteType>('basic');
  placeholder = input<string>('');
  valueField = input<string | null>(null);
  value = input<string | null>(null);
  internalValue = signal<string | null>(null);
  tabindex = input<number>(0);
  disabled = input<boolean>(false);
  listHeight = input<number>(200);
  isOpen = signal<boolean>(false);

  open = output<PreventableEvent>();
  filterChange = output<string>();
  valueChange = output<string | null>();
  invalidTrigger = signal(0);
  formControl: FormControl;

  // Internal signal to track form control disabled state
  private formControlDisabled = signal<boolean>(false);

  // Computed signal that combines input disabled state and form control disabled state
  isDisabled = computed(() => this.disabled() || this.formControlDisabled());

  onChange: (value: string | null) => void = () => { };
  onTouched = () => { };

  ngDoCheck() {
    //needed so that isInvalid is recomputed when the form control is touched with changing the value
    // and when `markAllAsTouched` is called on the parent form
    if (this.formControl?.touched)
      this.invalidTrigger.update((v) => v + 1);
  }

  ngOnInit() {
    const ngControl = this.injector.get(NgControl);

    if (ngControl instanceof FormControlName) {
      this.formControl = this.injector
        .get(FormGroupDirective)
        .getControl(ngControl)
    } else {
      this.formControl = (ngControl as FormControlDirective).form as FormControl;
    }
  }

  isInvalid = computed(() => {
    this.invalidTrigger();
    const control = this.formControl;
    if (control && !this.isDisabled())
      return control.invalid && (control.touched || control.dirty)
    else
      return false;
  });

  writeValue(value: string | null) {
    this.internalValue.set(value);
    this.cdr.markForCheck();
  }

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

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

  onValueChange(newValue: string | null) {
    this.internalValue.set(newValue);
    this.onChange(newValue);
    this.valueChange.emit(newValue);
    this.invalidTrigger.update((v) => v + 1);
  }

  onFilterChange(event: string): void {
    this.filterChange.emit(event);
  }

  setDisabledState(isDisabled: boolean) {
    this.formControlDisabled.set(isDisabled);
    this.cdr.markForCheck();
  }


  focus(): void { // check if self exists, then focus
    if (this.autocomplete) {
      this.autocomplete.focus();
    }
  }

  toggle(toggle: boolean): void { // check if self exists, then toggle
    if (this.autocomplete) {
      this.autocomplete.toggle(toggle);
    }
  }

  conditionalClasses = computed(() => {
    const classes = [] as string[];

    classes.push(...this.getCommonClasses());
    classes.push(...this.getLightBaseClasses());
    classes.push(...this.getDarkBaseClasses());
    if (!this.isDisabled()) {
      classes.push(...this.getLightHoverClasses());
      classes.push(...this.getDarkHoverClasses());
    }

    const conditionalClasses = this.getConditionalClassesFromArrays(classes);
    return conditionalClasses;
  });


  getConditionalClassesFromArrays(classArray: string[]): { [key: string]: boolean } {
    const classes: { [key: string]: boolean } = {};
    classArray.forEach(className => {
      classes[className] = true;
    });
    return classes;
  }

  getCommonClasses() {
    const baseClasses = [] as string[];
    baseClasses.push(
      "h-7",
      "flex",
      "grow"
    );
    if (this.type() === 'global-bar') {
      baseClasses.push(
        "border-transparent",
        "dark:border-transparent",
        "bg-transparent",
        "[&_.k-input-inner]:text-white",
        "[&_.k-input-inner]:font-bold",
        "[&_.k-input-inner::placeholder]:text-white/50"
      )
    }
    return baseClasses;
  }

  getLightBaseClasses() {
    const classes = [
      ""];

    if (this.isInvalid()) {
      classes.push(
        "border-red-500",
        "ring-red-500/50",
        "ring-2"
      );
    } else {
      classes.push(
        "border-base-gray-500",
        "ring-base-blue-250/50"
      );
    }

    return classes;
  }

  getDarkBaseClasses() {
    const classes = [
      ""];

    if (this.isInvalid()) {
      classes.push(
        "dark:border-red-500",
        "dark:ring-red-500/50"
      );
    } else {
      classes.push(
        "dark:border-alt-blue-250",
        "dark:ring-base-blurple-250/50"
      );
    }

    return classes;
  }

  getLightHoverClasses() {
    return [
      ""];
  }

  getDarkHoverClasses() {
    return [
      ""
    ];
  }
}
