import {
  ChangeDetectionStrategy,
  Component,
  Injector,
  computed,
  effect,
  forwardRef,
  inject,
  input,
  signal,
} from '@angular/core';
import {
  AbstractControl,
  ControlValueAccessor,
  NG_VALUE_ACCESSOR,
  NgControl,
} from '@angular/forms';

import { FormField } from '../form-field/form-field';
import { ValidationMessageMap } from '../form-validation-message/form-validation-message';

@Component({
  selector: 'app-form-date-picker',
  standalone: true,
  imports: [FormField],
  templateUrl: './form-date-picker.html',
  styleUrl: './form-date-picker.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => FormDatePicker),
      multi: true,
    },
  ],
})
export class FormDatePicker implements ControlValueAccessor {
  private readonly injector = inject(Injector);

  readonly inputId = input.required<string>();
  readonly label = input.required<string>();
  readonly placeholder = input('');
  readonly required = input(false);
  readonly readonly = input(false);
  readonly disabled = input(false);
  readonly min = input<string | null>(null);
  readonly max = input<string | null>(null);
  readonly hint = input<string | null>(null);
  readonly help = input<string | null>(null);
  readonly description = input<string | null>(null);
  readonly hideValidation = input(false);
  readonly showValidationWhenDirty = input(false);
  readonly submitAttempted = input(false);
  readonly validationMessages = input<ValidationMessageMap>({});
  readonly variant = input<'default' | 'floating'>('default');
  readonly wrapperClass = input('');
  readonly labelClass = input('');
  readonly fieldContentClass = input('');

  readonly value = signal<string | null>(null);
  readonly formDisabled = signal(false);
  readonly focused = signal(false);
  private readonly controlStateVersion = signal(0);

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

  readonly control = computed<AbstractControl | null>(() =>
    this.injector.get(NgControl, null, { self: true, optional: true })?.control ?? null
  );

  readonly isFloating = computed(() => this.variant() === 'floating');
  readonly isDisabled = computed(() => this.disabled() || this.formDisabled());
  readonly hasValue = computed(() => !!this.value());
  readonly shouldFloat = computed(() => this.focused() || this.hasValue());
  readonly showError = computed(() => {
    this.controlStateVersion();
    const control = this.control();
    return !!control?.invalid && (this.submitAttempted() || (this.showValidationWhenDirty() && control.dirty));
  });

  readonly inputClass = computed(() => [
    'form-control',
    this.showError() ? 'is-invalid' : '',
  ].filter(Boolean).join(' '));

  constructor() {
    effect((onCleanup) => {
      const control = this.control();

      if (!control) {
        return;
      }

      const statusSubscription = control.statusChanges.subscribe(() => {
        this.controlStateVersion.update(value => value + 1);
      });

      const valueSubscription = control.valueChanges.subscribe(() => {
        this.controlStateVersion.update(value => value + 1);
      });

      const eventsSubscription = control.events?.subscribe(() => {
        this.controlStateVersion.update(value => value + 1);
      });

      onCleanup(() => {
        statusSubscription.unsubscribe();
        valueSubscription.unsubscribe();
        eventsSubscription?.unsubscribe();
      });
    });
  }

  writeValue(value: string | null): void {
    this.value.set(value ?? null);
  }

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

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

  setDisabledState(isDisabled: boolean): void {
    this.formDisabled.set(isDisabled);
  }

  onInput(event: Event): void {
    const target = event.target as HTMLInputElement;
    const nextValue = target.value.trim() || null;
    this.value.set(nextValue);
    this.onChange(nextValue);
  }

  onFocus(): void {
    this.focused.set(true);
  }

  onBlur(): void {
    this.focused.set(false);
    this.onTouched();
  }

}
