import {
  ChangeDetectionStrategy,
  Component,
  ElementRef,
  computed,
  effect,
  input,
  model,
  output,
  signal,
  untracked,
  viewChild
} from '@angular/core';
import { NgClass } from '@angular/common';

export type AppInputType =
  | 'search'
  | 'user'
  | 'text'
  | 'number'
  | 'email'
  | 'password'
  | 'password-toggle'
  | 'currency'
  | 'percent'
  | 'date'
  | 'period'
  | 'editable'
  | 'file';

const DEFAULT_TYPE: AppInputType = 'text';
const INPUT_TYPES = new Set<AppInputType>([
  'search',
  'user',
  'text',
  'number',
  'email',
  'password',
  'password-toggle',
  'currency',
  'percent',
  'date',
  'period',
  'editable',
  'file'
]);

function normalizeType(value: AppInputType | string | undefined): AppInputType {
  return value && INPUT_TYPES.has(value as AppInputType)
    ? (value as AppInputType)
    : DEFAULT_TYPE;
}

@Component({
  selector: 'app-input',
  imports: [NgClass],
  templateUrl: './input.component.html',
  styleUrl: './input.component.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    class: 'form-group'
  }
})
export class InputComponent {
  private readonly inputElement = viewChild<ElementRef<HTMLInputElement>>('inputElement');
  private readonly selectedFileName = signal('');
  private readonly editableDisabled = signal(false);
  private readonly passwordVisible = signal(false);
  private lastClearTrigger = 0;
  private readonly defaultId = `app-input-${globalThis.crypto.randomUUID()}`;

  readonly invalid = input(false);
  readonly id = input(this.defaultId);
  readonly placeholder = input('');
  readonly value = model<string | number>('');
  readonly maxlength = input<number | null>(null);
  readonly accept = input('');
  readonly fileName = input('');
  readonly clearTrigger = input(0);
  readonly disabled = input(false);
  readonly visible = input<boolean | null>(null);
  readonly type = input<AppInputType, AppInputType | string>(DEFAULT_TYPE, {
    transform: normalizeType
  });

  readonly disabledChange = output<boolean>();
  readonly fileChange = output<File | null>();
  readonly visibleChange = output<boolean>();

  protected readonly inputType = computed(() => {
    const type = this.type();

    if (type === 'password-toggle') {
      return this.isPasswordVisible() ? 'text' : 'password';
    }

    if (
      type === 'search' ||
      type === 'user' ||
      type === 'currency' ||
      type === 'percent' ||
      type === 'editable'
    ) {
      return 'text';
    }

    if (type === 'period') {
      return 'month';
    }

    return type;
  });

  protected readonly inputClasses = computed(() => ({
    'form-control': true,
    'is-invalid': this.invalid(),
    'input-with-search-icon': this.type() === 'search',
    'input-with-action-icon': this.type() === 'user' || this.type() === 'password-toggle',
    'input-with-prefix': this.type() === 'currency',
    'input-with-suffix': this.type() === 'percent',
    'input-with-icon': this.type() === 'editable'
  }));

  protected readonly isInputDisabled = computed(() =>
    this.type() === 'editable' ? this.editableDisabled() : this.disabled()
  );

  protected readonly isEditableActive = computed(
    () => this.type() === 'editable' && !this.editableDisabled()
  );

  protected readonly isPasswordVisible = computed(() => {
    const visible = this.visible();
    return visible === null ? this.passwordVisible() : visible;
  });

  protected readonly resolvedFileName = computed(
    () => this.selectedFileName() || this.fileName() || ''
  );

  constructor() {
    effect(() => {
      this.editableDisabled.set(this.disabled());
    });

    effect(() => {
      if (this.type() !== 'file') {
        return;
      }

      const clearTrigger = this.clearTrigger();
      const value = this.value();
      const fileName = this.fileName();

      untracked(() => {
        if (clearTrigger !== this.lastClearTrigger) {
          this.lastClearTrigger = clearTrigger;
          this.clearFileInput();
          return;
        }

        if (!value && !fileName) {
          this.clearFileInput();
        }
      });
    });
  }

  protected onValueChange(event: Event): void {
    this.value.set((event.target as HTMLInputElement).value);
  }

  protected onFileChange(event: Event): void {
    const file = (event.target as HTMLInputElement).files?.[0] ?? null;
    this.selectedFileName.set(file?.name ?? '');
    this.fileChange.emit(file);
  }

  protected toggleEditableState(): void {
    if (this.type() !== 'editable') {
      return;
    }

    const nextState = !this.editableDisabled();
    this.editableDisabled.set(nextState);
    this.disabledChange.emit(nextState);
  }

  protected togglePasswordVisibility(): void {
    if (this.type() !== 'password-toggle' || this.disabled()) {
      return;
    }

    const nextState = !this.isPasswordVisible();

    if (this.visible() === null) {
      this.passwordVisible.set(nextState);
    }

    this.visibleChange.emit(nextState);
  }

  private clearFileInput(): void {
    this.inputElement()?.nativeElement && (this.inputElement()!.nativeElement.value = '');
    this.selectedFileName.set('');
  }
}
