import { ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, DoCheck, effect, inject, Injector, input, model, OnInit, output, signal } from '@angular/core';
import { FAST_KENDO_COMMON } from '../../app.config';
import { ControlValueAccessor, FormControl, FormControlDirective, FormControlName, FormGroupDirective, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
import { NumberFormatOptions } from '@progress/kendo-angular-intl';

@Component({
  selector: 'fast-numerictextbox',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [FAST_KENDO_COMMON],
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    multi: true,
    useExisting: FastNumericTextBoxComponent
  },
  {
    provide: NgControl,
    multi: true,
    useExisting: FastNumericTextBoxComponent
  }],
  template: `
  <kendo-numerictextbox
    [min]="min()"
    [max]="max()"
    [class]="conditionalClasses()"
    [autoCorrect]="autoCorrect()"
    [readonly]="readonly()"
    [format]="format()"
    [spinners]="spinners()"
    [step]="step()"
    [tabindex]="tabindex()"
    [value]="internalValue()"
    (valueChange)="onValueChange($event)"
    [disabled]="isDisabled()"
    [decimals]="decimals()"
    [placeholder]="placeholder()"
  />
  `
})
export class FastNumericTextBoxComponent implements ControlValueAccessor, OnInit, DoCheck {
  cdr = inject(ChangeDetectorRef);
  injector = inject(Injector);

  min = input<number | null>(null);
  max = input<number | null>(null);
  tabindex = input<number>(0);
  spinners = input<boolean>(false);
  step = input<number>(1);
  autoCorrect = input<boolean>(false);
  readonly = input<boolean>(false);
  disabled = input<boolean>(false);
  valueChange = output<number | null>();
  format = input<string | NumberFormatOptions>();
  value = input<number | null>(null);
  internalValue = signal<number | null>(null);
  decimals = input<number>(null);
  invalidTrigger = signal(0);
  formControl: FormControl;
  placeholder = model<string>('');

  // 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: number | null) => void = () => { };
  onTouched: () => void;

  constructor() {
    //form controls listen to onValueChange events and update internalValue
    //for non-form controls, we need to listen to value input changes and update internalValue
    effect(() => {
      if (!this.formControl) {
        const val = this.value();
        if (val !== this.internalValue()) {
          this.internalValue.set(val);
        }
      }
    });
  }

  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, null);

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

      ngControl.valueAccessor = this; // ensure Angular's binding still works, just in case there is no form control
    }
  }

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

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

  registerOnChange(fn: (value: number | null) => void): void {
    this.onChange = fn;
  }

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

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

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

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

    classes.push(...this.getCommonClasses());

    if (!this.disabled()) {
      classes.push(...this.getLightBaseClasses());
      classes.push(...this.getDarkBaseClasses());
      classes.push(...this.getLightActiveClasses());
      classes.push(...this.getDarkActiveClasses());
    }

    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() {
    return [
      "flex",
      "border-solid",
      "rounded-sm",
      "h-7",
      "[&_button]:border-none",
      "[&_button]:w-5.5",
      "[&_input]:pr-0",];
  }

  getLightBaseClasses() {
    if (this.readonly()) {
      return [
        "bg-transparent",
        "text-base-black-1000",
        "[&_button]:display-none",
      ];
    }
    else if (this.isDisabled()) {
      return [
        "border-base-gray-500",
        "ring-base-blue-250/50"
      ];
    }
    const classes = [
      "[&_button]:text-base-black-1000",
      "[&_button]:bg-base-white-250"];

    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() {
    if (this.readonly()) {
      return [
        "bg-transparent",
        "dark:text-base-white-500",
        "dark:[&_button]:display-none",
      ];
    }
    else if (this.isDisabled()) {
      return [
        "dark:ring-base-blurple-250/50",
        "dark:border-alt-blue-250",
      ];
    }
    const classes = [
      "dark:[&_button]:text-base-white-500",
      "dark:[&_button]:bg-alt-gray-1000"];

    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;
  }

  getLightActiveClasses() {
    return [
      "[&_button]:active:bg-base-white-1000"];
  }

  getDarkActiveClasses() {
    return [
      "dark:[&_button]:active:bg-alt-blue-250"];
  }
}
