import { ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, DoCheck, inject, Injector, input, model, OnInit, signal, ViewChild, ElementRef } from '@angular/core';
import { ControlValueAccessor, FormControl, FormControlDirective, FormControlName, FormGroupDirective, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
import { isMobileDevice } from '../utils/util';

type InputType = "text" | "hidden" | "range" | "search" | "password" | "url";

@Component({
  selector: 'fast-textbox',
  imports: [],
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    multi: true,
    useExisting: FastTextboxComponent
  },
  {
    provide: NgControl,
    multi: true,
    useExisting: FastTextboxComponent
  }],
  template: `
    <div class="relative w-full">
      <input #inputElem
        [type]="actualInputType()"
        [class]="conditionalClasses()"
        [value]="value()"
        [maxLength]="maxLength()"
        (input)="onValueChange($event)"
        (blur)="onTouched()"
        [disabled]="isDisabled()"
        [readOnly]="readonly()"
        [placeholder]="placeholder()"
        autocomplete="off"
        (focus)="scrollToForMobile()"
      />
      @if (type() === 'password') {
        <button
          type="button"
          (click)="togglePasswordVisibility()"
          class="absolute right-2 top-1/2 -translate-y-1/2 border-0 bg-transparent p-0 cursor-pointer"
        >
          @if (showPassword()) {
            <svg class="size-2 stroke-base-black-1000 dark:stroke-base-white-500 w-5 h-5"
              viewBox="0 0 24 24" fill="none">
              <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
              <line x1="1" y1="1" x2="23" y2="23"/>
            </svg>
          } @else {
            <svg class="size-2 stroke-base-black-1000 dark:stroke-base-white-500 w-5 h-5"
              viewBox="0 0 24 24" fill="none">
              <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
              <circle cx="12" cy="12" r="3"/>
            </svg>
          }
        </button>
      }
    </div>
    <ng-content></ng-content>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class FastTextboxComponent implements ControlValueAccessor, OnInit, DoCheck {
  @ViewChild("inputElem") inputElem: ElementRef<HTMLInputElement>;

  cdr = inject(ChangeDetectorRef);
  injector = inject(Injector);
  type = input<InputType>("text");
  readonly = input<boolean>(false);
  value = model<string | null>(null);
  placeholder = input<string>('');
  disabled = input<boolean>(false);
  maxLength = input<number | null>(200);
  showPassword = signal<boolean>(false);
  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: () => void = () => { };

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

  actualInputType = computed(() => {
    if (this.type() === 'password' && this.showPassword()) {
      return 'text';
    }
    return this.type();
  });

  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() && !this.readonly())
      return control.invalid && (control.touched || control.dirty)
    else
      return false;
  });

  togglePasswordVisibility() {
    this.showPassword.update(show => !show);
  }

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

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

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

  // ControlValueAccessor method to handle disabled state changes from reactive forms
  setDisabledState(isDisabled: boolean) {
    this.formControlDisabled.set(isDisabled);
    this.cdr.markForCheck();
  }

  onValueChange(event: Event) {
    const target = event.target as HTMLInputElement;
    const newValue = target.value;

    this.value.set(newValue);
    this.onChange(newValue);
    this.invalidTrigger.update((v) => v + 1);
  }

  focus() {
    this.inputElem.nativeElement.focus();
  }

  scrollTo() {
    this.inputElem.nativeElement.scrollIntoView();
  }

  scrollToForMobile() {
    if (isMobileDevice())
      setTimeout(() => {
        this.inputElem.nativeElement.scrollIntoView();
      }, 200);
  }

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

    classes.push(...this.getCommonClasses(this.type()));
    classes.push(...this.getLightBaseClasses());
    classes.push(...this.getDarkBaseClasses());

    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(type: InputType) {
    const commonBase = [];
    commonBase.push(
      "w-full",
      "relative",
      "items-stretch",
      "flex",
      "flex-row",
      "flex-nowrap",
      "h-7",
      "m-0",
      "px-3",
      "py-1.5",
      "border-1",
      "border-solid",
      "rounded-md",
      "overflow-clip",
      "focus:ring-4",
      "focus:outline-none");
    if (type === "password" && !this.showPassword())
      commonBase.push("text-2xl");
    if (type === "hidden")
      commonBase.push("text-transparent");
    return commonBase;
  }

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

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

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