import {
  ChangeDetectorRef,
  Directive,
  ElementRef,
  HostListener,
  inject,
  Input,
  Renderer2,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { format, isValid, parse, isEqual } from 'date-fns';

@Directive({
  selector: 'input[uiDateInput]',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: DateInputDirective,
      multi: true,
    },
  ],
  standalone: true,
})
// TODO: Date Input Directive soll ausgelagert werden in eine eigene Lib
export class DateInputDirective implements ControlValueAccessor {
  elementRef = inject(ElementRef);

  renderer = inject(Renderer2);

  cdr = inject(ChangeDetectorRef);

  @Input() value: Date | undefined = undefined;

  onChanges?: (value: Date | undefined) => void;
  onTouched?: () => void;

  /**
   * Writes a value to the view.
   * @param obj - The new value for the input.
   */
  writeValue(obj: Date | undefined): void {
    this.setValue(obj, { emit: false });
  }

  /**
   * Registers a callback function to be invoked when the control's value changes.
   * @param fn - The callback function.
   */
  registerOnChange(fn: (value: Date | undefined) => void): void {
    this.onChanges = fn;
  }

  /**
   * Registers a callback function to be invoked when the control is touched.
   * @param fn - The callback function.
   */
  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }

  /**
   * Sets the value of the input and triggers change detection.
   * @param value - The new date value.
   * @param options - Optional settings to control event emission and rendering.
   */
  setValue(
    value: Date | undefined,
    options?: { emit?: boolean; render?: boolean },
  ) {
    if (this.value && value && isEqual(this.value, value)) {
      return;
    }

    this.value = value;
    this.onTouched?.();

    if (options?.emit ?? true) {
      this.onChanges?.(value);
    }

    if (options?.render ?? true) {
      this.renderValue();
    }
  }

  /**
   * Handles the input event, parses the entered string value into a Date,
   * and updates the control's value if valid.
   * @param event - The input event containing the new value.
   */
  @HostListener('input', ['$event'])
  onInput(event: InputEvent) {
    const value = (event.target as HTMLInputElement).value;
    const date = this.parseStringToDate(value);
    if (date) {
      this.setValue(date, { render: false });
    }
  }

  /**
   * Parses a string into a Date object using the format 'dd.MM.yyyy'.
   * @param value - The string representation of the date.
   * @returns The parsed Date if valid, otherwise undefined.
   */
  private parseStringToDate(value: string | undefined): Date | undefined {
    if (!value || typeof value !== 'string') return undefined;

    const parsed = parse(value, 'dd.MM.yyyy', new Date());
    return isValid(parsed) ? parsed : undefined;
  }

  /**
   * Renders the current value to the input element.
   * If the value is set and is a valid Date, formats it as 'dd.MM.yyyy'.
   * Otherwise, clears the input display.
   */
  @HostListener('focusout')
  @HostListener('blur')
  @HostListener('keydown.enter')
  renderValue() {
    if (!this.value) {
      this.renderer.setProperty(this.elementRef.nativeElement, 'value', '');
      return;
    }

    if (this.value instanceof Date) {
      this.renderer.setProperty(
        this.elementRef.nativeElement,
        'value',
        format(this.value, 'dd.MM.yyyy'),
      );
    }

    this.cdr.markForCheck();
  }
}
