import {
  Component,
  Input,
  Output,
  EventEmitter,
  ViewChild,
  ElementRef,
  OnChanges,
  SimpleChanges,
  forwardRef,
} from '@angular/core';

import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';

@Component({
  selector: 'ccl-custom-datefield',
  standalone: true,
  imports: [],
  templateUrl: './custom-date-field.html',
  styleUrls: ['./custom-date-field.css'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomDateFieldComponent),
      multi: true,
    },
  ],
})
export class CustomDateFieldComponent implements ControlValueAccessor, OnChanges {
  @Input() value: Date | null = null;
  @Input() minDate?: Date;
  @Input() maxDate?: Date;
  @Input() disabled = false;
  @Input() required = false;
  @Input() placeholder = 'YYYY-MM-DD';
  @Input() displayFormat = 'yyyy-MM-dd';
  @Input() showTime = false;

  @Output() valueChange = new EventEmitter<Date | null>();

  @ViewChild('nativePicker', { static: true }) nativePicker!: ElementRef<HTMLInputElement>;

  displayValue = '';
  errorMessage = '';

  private onChangeFn: (value: Date | null) => void = () => {};
  private onTouchedFn: () => void = () => {};

  ngOnChanges(changes: SimpleChanges) {
    if (changes['value'] || changes['displayFormat'] || changes['showTime']) {
      this.updateDisplayValue();
      if (changes['showTime']) {
        // Update placeholder when showTime changes
        this.placeholder = this.showTime ? 'DD-MM-YYYY HH:mm' : 'DD-MM-YYYY';
      }
    }
  }

  writeValue(value: Date | null): void {
    this.value = value;
    this.updateDisplayValue();
  }

  registerOnChange(fn: (val: Date | null) => void): void {
    this.onChangeFn = fn;
  }

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

  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  onTextInput(event: Event) {
    const input = event.target as HTMLInputElement;
    this.displayValue = input.value;
    const parsed = this.parseFormattedValue(input.value);

    if (!input.value) {
      this.setValue(null);
      this.errorMessage = '';
      return;
    }

    if (parsed && this.isWithinBounds(parsed)) {
      this.errorMessage = '';
      this.setValue(parsed);
    } else {
      this.errorMessage = 'Invalid date';
    }
  }

  openNativePicker(event?: Event) {
    event?.stopPropagation();
    if (this.disabled) return;
    this.nativePicker.nativeElement.showPicker?.();
  }

  onNativeChange(event: Event) {
    const input = event.target as HTMLInputElement;
    if (!input.value) {
      this.setValue(null);
      this.displayValue = '';
      // Ensure native picker value is cleared so selecting the same date again will fire change
      if (this.nativePicker?.nativeElement) {
        this.nativePicker.nativeElement.value = '';
      }
      return;
    }
    const parsed = new Date(input.value);
    if (!isNaN(parsed.getTime())) {
      // Check if the selected value is within bounds
      if (this.isWithinBounds(parsed)) {
        this.errorMessage = '';
        this.setValue(parsed);
        this.updateDisplayValue();
      } else {
        // Invalid selection (outside min/max) - clear it
        this.errorMessage = '';
        this.setValue(null);
        this.displayValue = '';
        // Clear native picker to allow re-selection
        if (this.nativePicker?.nativeElement) {
          this.nativePicker.nativeElement.value = '';
        }
      }
    }
  }

  clear() {
    if (this.disabled) return;
    this.setValue(null);
    this.displayValue = '';
    this.errorMessage = '';
    // Also clear the hidden native picker value so re-selecting the same date triggers change
    if (this.nativePicker?.nativeElement) {
      this.nativePicker.nativeElement.value = '';
    }
  }

  onWrapperClick(event: MouseEvent) {
    if (this.disabled) return;
    const target = event.target as HTMLElement;
    if (
      target.classList.contains('ccl-custom-datefield__clear') ||
      target.classList.contains('ccl-custom-datefield__icon') ||
      target.tagName.toLowerCase() === 'button'
    ) {
      return;
    }
    this.openNativePicker(event);
  }

  onIconClick(event: Event) {
    this.openNativePicker(event);
  }

  onClearClick(event: Event) {
    event.stopPropagation();
    this.clear();
  }

  onBlur() {
    this.onTouchedFn();
  }

  getMinDateString(): string {
    return this.minDate ? this.toInputValue(this.minDate) : '';
  }

  getMaxDateString(): string {
    return this.maxDate ? this.toInputValue(this.maxDate) : '';
  }

  private setValue(value: Date | null) {
    this.value = value;
    this.onChangeFn(value);
    this.valueChange.emit(value);
    // Keep native picker in sync with the current value
    if (this.nativePicker?.nativeElement) {
      this.nativePicker.nativeElement.value = value ? this.toInputValue(value) : '';
    }
  }

  private updateDisplayValue() {
    this.displayValue = this.value ? this.formatDate(this.value) : '';
  }

  private formatDate(date: Date): string {
    const tokens: Record<string, string> = {
      yyyy: date.getFullYear().toString().padStart(4, '0'),
      MM: (date.getMonth() + 1).toString().padStart(2, '0'),
      dd: date.getDate().toString().padStart(2, '0'),
      HH: date.getHours().toString().padStart(2, '0'),
      mm: date.getMinutes().toString().padStart(2, '0'),
      ss: date.getSeconds().toString().padStart(2, '0'),
    };

    let formatted = this.displayFormat;
    Object.entries(tokens).forEach(([token, value]) => {
      formatted = formatted.replace(token, value);
    });
    return formatted;
  }

  private parseFormattedValue(value: string): Date | null {
    const regex = this.buildFormatRegex();
    const match = value.match(regex);
    if (!match || !match.groups) {
      return null;
    }

    const year = Number(match.groups['yyyy']);
    const month = Number(match.groups['MM']);
    const day = Number(match.groups['dd']);
    const hours = match.groups['HH'] ? Number(match.groups['HH']) : 0;
    const minutes = match.groups['mm'] ? Number(match.groups['mm']) : 0;
    const seconds = match.groups['ss'] ? Number(match.groups['ss']) : 0;

    if (
      Number.isNaN(year) ||
      Number.isNaN(month) ||
      Number.isNaN(day) ||
      month < 1 ||
      month > 12 ||
      day < 1 ||
      day > 31 ||
      (this.showTime && (hours < 0 || hours > 23 || minutes < 0 || minutes > 59 || seconds < 0 || seconds > 59))
    ) {
      return null;
    }

    const parsed = new Date(year, month - 1, day, hours, minutes, seconds);
    if (Number.isNaN(parsed.getTime()) || parsed.getMonth() !== month - 1 || parsed.getDate() !== day) {
      return null;
    }

    return parsed;
  }

  private buildFormatRegex(): RegExp {
    const escapeRegex = /[.*+?^${}()|[\]\\]/g;
    let pattern = this.displayFormat.replace(escapeRegex, '\\$&');

    pattern = pattern
      .replace('yyyy', '(?<yyyy>\\d{4})')
      .replace('MM', '(?<MM>\\d{2})')
      .replace('dd', '(?<dd>\\d{2})')
      .replace('HH', '(?<HH>\\d{2})')
      .replace('mm', '(?<mm>\\d{2})')
      .replace('ss', '(?<ss>\\d{2})');

    return new RegExp(`^${pattern}$`);
  }

  private isWithinBounds(date: Date): boolean {
    if (this.minDate && date < this.minDate) {
      return false;
    }
    if (this.maxDate && date > this.maxDate) {
      return false;
    }
    return true;
  }

  private stripTime(value: Date): Date {
    return new Date(value.getFullYear(), value.getMonth(), value.getDate());
  }

  private toInputValue(date: Date): string {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    
    // Always include time when showTime is enabled, or when date has non-zero time components
    // This ensures min/max constraints properly restrict time selection in native picker
    if (this.showTime || date.getHours() !== 0 || date.getMinutes() !== 0 || date.getSeconds() !== 0) {
      const hours = String(date.getHours()).padStart(2, '0');
      const minutes = String(date.getMinutes()).padStart(2, '0');
      return `${year}-${month}-${day}T${hours}:${minutes}`;
    }
    
    return `${year}-${month}-${day}`;
  }
}

