import {
  customElement,
  type TemplateResult,
  property,
  state,
} from '@design.estate/dees-element';
import { DeesInputBase } from '../dees-input-base/dees-input-base.js';
import { demoFunc } from './demo.js';
import { datepickerStyles } from './styles.js';
import { renderDatepicker } from './template.js';
import type { IDateEvent } from './types.js';
import '../../dees-icon/dees-icon.js';
import '../../dees-label/dees-label.js';


declare global {
  interface HTMLElementTagNameMap {
    'dees-input-datepicker': DeesInputDatepicker;
  }
}

@customElement('dees-input-datepicker')
export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
  public static demo = demoFunc;
  public static demoGroup = 'Input';

  @property({ type: String })
  accessor value: string = '';

  @property({ type: Boolean })
  accessor enableTime: boolean = false;

  @property({ type: String })
  accessor timeFormat: '24h' | '12h' = '24h';

  @property({ type: Number })
  accessor minuteIncrement: number = 1;

  @property({ type: String })
  accessor dateFormat: string = 'YYYY-MM-DD';

  @property({ type: String })
  accessor minDate: string = '';

  @property({ type: String })
  accessor maxDate: string = '';

  @property({ type: Array })
  accessor disabledDates: string[] = [];

  @property({ type: Number })
  accessor weekStartsOn: 0 | 1 = 1; // Default to Monday

  @property({ type: String })
  accessor placeholder: string = 'YYYY-MM-DD';

  @property({ type: Boolean })
  accessor enableTimezone: boolean = false;

  @property({ type: String })
  accessor timezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone;

  @property({ type: Array })
  accessor events: IDateEvent[] = [];

  @state()
  accessor isOpened: boolean = false;

  @state()
  accessor opensToTop: boolean = false;

  @state()
  accessor selectedDate: Date | null = null;

  @state()
  accessor viewDate: Date = new Date();

  @state()
  accessor selectedHour: number = 0;

  @state()
  accessor selectedMinute: number = 0;

  public static styles = datepickerStyles;



  public getTimezones(): { value: string; label: string }[] {
    // Common timezones with their display names
    return [
      { value: 'UTC', label: 'UTC (Coordinated Universal Time)' },
      { value: 'America/New_York', label: 'Eastern Time (US & Canada)' },
      { value: 'America/Chicago', label: 'Central Time (US & Canada)' },
      { value: 'America/Denver', label: 'Mountain Time (US & Canada)' },
      { value: 'America/Los_Angeles', label: 'Pacific Time (US & Canada)' },
      { value: 'America/Phoenix', label: 'Arizona' },
      { value: 'America/Anchorage', label: 'Alaska' },
      { value: 'Pacific/Honolulu', label: 'Hawaii' },
      { value: 'Europe/London', label: 'London' },
      { value: 'Europe/Paris', label: 'Paris' },
      { value: 'Europe/Berlin', label: 'Berlin' },
      { value: 'Europe/Moscow', label: 'Moscow' },
      { value: 'Asia/Dubai', label: 'Dubai' },
      { value: 'Asia/Kolkata', label: 'India Standard Time' },
      { value: 'Asia/Shanghai', label: 'China Standard Time' },
      { value: 'Asia/Tokyo', label: 'Tokyo' },
      { value: 'Australia/Sydney', label: 'Sydney' },
      { value: 'Pacific/Auckland', label: 'Auckland' },
    ];
  }

  public render(): TemplateResult {
    return renderDatepicker(this);
  }



  async connectedCallback() {
    super.connectedCallback();
    this.handleClickOutside = this.handleClickOutside.bind(this);
  }

  async disconnectedCallback() {
    await super.disconnectedCallback();
    document.removeEventListener('click', this.handleClickOutside);
  }

  async firstUpdated() {
    // Initialize with empty value if not set
    if (!this.value) {
      this.value = '';
    }

    // Initialize view date and selected time
    if (this.value) {
      try {
        const date = new Date(this.value);
        if (!isNaN(date.getTime())) {
          this.selectedDate = date;
          this.viewDate = new Date(date);
          this.selectedHour = date.getHours();
          this.selectedMinute = date.getMinutes();
        }
      } catch {
        // Invalid date
      }
    } else {
      const now = new Date();
      this.viewDate = new Date(now);
      this.selectedHour = now.getHours();
      this.selectedMinute = 0;
    }
  }

  public formatDate(isoString: string): string {
    if (!isoString) return '';

    try {
      const date = new Date(isoString);
      if (isNaN(date.getTime())) return '';

      let formatted = this.dateFormat;
      
      // Basic date formatting
      const day = date.getDate().toString().padStart(2, '0');
      const month = (date.getMonth() + 1).toString().padStart(2, '0');
      const year = date.getFullYear().toString();
      
      // Replace in correct order to avoid conflicts
      formatted = formatted.replace('YYYY', year);
      formatted = formatted.replace('YY', year.slice(-2));
      formatted = formatted.replace('MM', month);
      formatted = formatted.replace('DD', day);

      // Time formatting if enabled
      if (this.enableTime) {
        const hours24 = date.getHours();
        const hours12 = hours24 === 0 ? 12 : hours24 > 12 ? hours24 - 12 : hours24;
        const minutes = date.getMinutes().toString().padStart(2, '0');
        const ampm = hours24 >= 12 ? 'PM' : 'AM';

        if (this.timeFormat === '12h') {
          formatted += ` ${hours12}:${minutes} ${ampm}`;
        } else {
          formatted += ` ${hours24.toString().padStart(2, '0')}:${minutes}`;
        }
      }

      // Timezone formatting if enabled
      if (this.enableTimezone) {
        const formatter = new Intl.DateTimeFormat('en-US', {
          timeZoneName: 'short',
          timeZone: this.timezone
        });
        const parts = formatter.formatToParts(date);
        const tzPart = parts.find(part => part.type === 'timeZoneName');
        if (tzPart) {
          formatted += ` ${tzPart.value}`;
        }
      }

      return formatted;
    } catch {
      return '';
    }
  }

  private handleClickOutside = (event: MouseEvent) => {
    const path = event.composedPath();
    if (!path.includes(this)) {
      this.isOpened = false;
      document.removeEventListener('click', this.handleClickOutside);
    }
  };

  public async toggleCalendar(): Promise<void> {
    if (this.disabled) return;

    this.isOpened = !this.isOpened;

    if (this.isOpened) {
      // Check available space and set position
      const inputContainer = this.shadowRoot!.querySelector('.input-container') as HTMLElement;
      const rect = inputContainer.getBoundingClientRect();
      const spaceBelow = window.innerHeight - rect.bottom;
      const spaceAbove = rect.top;
      
      // Determine if we should open upwards (approximate height of 400px)
      this.opensToTop = spaceBelow < 400 && spaceAbove > spaceBelow;

      // Add click outside listener
      setTimeout(() => {
        document.addEventListener('click', this.handleClickOutside);
      }, 0);
    } else {
      document.removeEventListener('click', this.handleClickOutside);
    }
  }

  public getDaysInMonth(): Date[] {
    const year = this.viewDate.getFullYear();
    const month = this.viewDate.getMonth();
    const firstDay = new Date(year, month, 1);
    const lastDay = new Date(year, month + 1, 0);
    const days: Date[] = [];

    // Adjust for week start
    const startOffset = this.weekStartsOn === 1 
      ? (firstDay.getDay() === 0 ? 6 : firstDay.getDay() - 1)
      : firstDay.getDay();

    // Add days from previous month
    for (let i = startOffset; i > 0; i--) {
      days.push(new Date(year, month, 1 - i));
    }

    // Add days of current month
    for (let i = 1; i <= lastDay.getDate(); i++) {
      days.push(new Date(year, month, i));
    }

    // Add days from next month to complete the grid (6 rows)
    const remainingDays = 42 - days.length;
    for (let i = 1; i <= remainingDays; i++) {
      days.push(new Date(year, month + 1, i));
    }

    return days;
  }

  public isToday(date: Date): boolean {
    const today = new Date();
    return date.getDate() === today.getDate() &&
           date.getMonth() === today.getMonth() &&
           date.getFullYear() === today.getFullYear();
  }

  public isSelected(date: Date): boolean {
    if (!this.selectedDate) return false;
    return date.getDate() === this.selectedDate.getDate() &&
           date.getMonth() === this.selectedDate.getMonth() &&
           date.getFullYear() === this.selectedDate.getFullYear();
  }

  public isDisabled(date: Date): boolean {
    // Check min date
    if (this.minDate) {
      const min = new Date(this.minDate);
      if (date < min) return true;
    }

    // Check max date
    if (this.maxDate) {
      const max = new Date(this.maxDate);
      if (date > max) return true;
    }

    // Check disabled dates
    if (this.disabledDates && this.disabledDates.length > 0) {
      return this.disabledDates.some(disabledStr => {
        try {
          const disabled = new Date(disabledStr);
          return date.getDate() === disabled.getDate() &&
                 date.getMonth() === disabled.getMonth() &&
                 date.getFullYear() === disabled.getFullYear();
        } catch {
          return false;
        }
      });
    }

    return false;
  }

  public getEventsForDate(date: Date): IDateEvent[] {
    if (!this.events || this.events.length === 0) return [];
    
    const dateStr = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
    return this.events.filter(event => event.date === dateStr);
  }

  public selectDate(date: Date): void {
    this.selectedDate = new Date(
      date.getFullYear(),
      date.getMonth(),
      date.getDate(),
      this.selectedHour,
      this.selectedMinute
    );
    
    this.value = this.formatValueWithTimezone(this.selectedDate);
    this.changeSubject.next(this);
    
    if (!this.enableTime) {
      this.isOpened = false;
    }
  }

  public selectToday(): void {
    const today = new Date();
    this.selectedDate = today;
    this.viewDate = new Date(today);
    this.selectedHour = today.getHours();
    this.selectedMinute = today.getMinutes();
    
    this.value = this.formatValueWithTimezone(this.selectedDate);
    this.changeSubject.next(this);
    
    if (!this.enableTime) {
      this.isOpened = false;
    }
  }

  public clear(): void {
    this.value = '';
    this.selectedDate = null;
    this.changeSubject.next(this);
    this.isOpened = false;
  }

  public previousMonth(): void {
    this.viewDate = new Date(this.viewDate.getFullYear(), this.viewDate.getMonth() - 1, 1);
  }

  public nextMonth(): void {
    this.viewDate = new Date(this.viewDate.getFullYear(), this.viewDate.getMonth() + 1, 1);
  }

  public handleHourInput(e: InputEvent): void {
    const input = e.target as HTMLInputElement;
    let value = parseInt(input.value) || 0;
    
    if (this.timeFormat === '12h') {
      value = Math.max(1, Math.min(12, value));
      // Convert to 24h format
      if (this.selectedHour >= 12 && value !== 12) {
        this.selectedHour = value + 12;
      } else if (this.selectedHour < 12 && value === 12) {
        this.selectedHour = 0;
      } else {
        this.selectedHour = value;
      }
    } else {
      this.selectedHour = Math.max(0, Math.min(23, value));
    }
    
    this.updateSelectedDateTime();
  }

  public handleMinuteInput(e: InputEvent): void {
    const input = e.target as HTMLInputElement;
    let value = parseInt(input.value) || 0;
    value = Math.max(0, Math.min(59, value));
    
    if (this.minuteIncrement && this.minuteIncrement > 1) {
      value = Math.round(value / this.minuteIncrement) * this.minuteIncrement;
    }
    
    this.selectedMinute = value;
    this.updateSelectedDateTime();
  }

  public setAMPM(period: 'am' | 'pm'): void {
    if (period === 'am' && this.selectedHour >= 12) {
      this.selectedHour -= 12;
    } else if (period === 'pm' && this.selectedHour < 12) {
      this.selectedHour += 12;
    }
    this.updateSelectedDateTime();
  }

  private updateSelectedDateTime(): void {
    if (this.selectedDate) {
      this.selectedDate = new Date(
        this.selectedDate.getFullYear(),
        this.selectedDate.getMonth(),
        this.selectedDate.getDate(),
        this.selectedHour,
        this.selectedMinute
      );
      this.value = this.formatValueWithTimezone(this.selectedDate);
      this.changeSubject.next(this);
    }
  }

  public handleTimezoneChange(e: Event): void {
    const select = e.target as HTMLSelectElement;
    this.timezone = select.value;
    this.updateSelectedDateTime();
  }

  private formatValueWithTimezone(date: Date): string {
    if (!this.enableTimezone) {
      return date.toISOString();
    }
    
    // Format the date with timezone offset
    const formatter = new Intl.DateTimeFormat('en-US', {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
      hour12: false,
      timeZone: this.timezone,
      timeZoneName: 'short'
    });
    
    const parts = formatter.formatToParts(date);
    const dateParts: any = {};
    parts.forEach(part => {
      dateParts[part.type] = part.value;
    });
    
    // Create ISO-like format with timezone
    const isoString = `${dateParts.year}-${dateParts.month}-${dateParts.day}T${dateParts.hour}:${dateParts.minute}:${dateParts.second}`;
    
    // Get timezone offset
    const tzOffset = this.getTimezoneOffset(date, this.timezone);
    return `${isoString}${tzOffset}`;
  }

  private getTimezoneOffset(date: Date, timezone: string): string {
    // Create a date in the target timezone
    const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timezone }));
    const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
    
    const offsetMinutes = (tzDate.getTime() - utcDate.getTime()) / (1000 * 60);
    const hours = Math.floor(Math.abs(offsetMinutes) / 60);
    const minutes = Math.abs(offsetMinutes) % 60;
    const sign = offsetMinutes >= 0 ? '+' : '-';
    
    return `${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
  }

  public handleKeydown(e: KeyboardEvent): void {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      this.toggleCalendar();
    } else if (e.key === 'Escape' && this.isOpened) {
      e.preventDefault();
      this.isOpened = false;
    }
  }

  public clearValue(e: Event): void {
    e.stopPropagation();
    this.value = '';
    this.selectedDate = null;
    this.changeSubject.next(this);
  }

  public handleManualInput(e: InputEvent): void {
    const input = e.target as HTMLInputElement;
    const inputValue = input.value.trim();
    
    if (!inputValue) {
      // Clear the value if input is empty
      this.value = '';
      this.selectedDate = null;
      return;
    }

    const parsedDate = this.parseManualDate(inputValue);
    if (parsedDate && !isNaN(parsedDate.getTime())) {
      // Update internal state without triggering re-render of input
      this.value = parsedDate.toISOString();
      this.selectedDate = parsedDate;
      this.viewDate = new Date(parsedDate);
      this.selectedHour = parsedDate.getHours();
      this.selectedMinute = parsedDate.getMinutes();
      this.changeSubject.next(this);
    }
  }

  public handleInputBlur(e: FocusEvent): void {
    const input = e.target as HTMLInputElement;
    const inputValue = input.value.trim();
    
    if (!inputValue) {
      this.value = '';
      this.selectedDate = null;
      this.changeSubject.next(this);
      return;
    }

    const parsedDate = this.parseManualDate(inputValue);
    if (parsedDate && !isNaN(parsedDate.getTime())) {
      this.value = parsedDate.toISOString();
      this.selectedDate = parsedDate;
      this.viewDate = new Date(parsedDate);
      this.selectedHour = parsedDate.getHours();
      this.selectedMinute = parsedDate.getMinutes();
      this.changeSubject.next(this);
      // Update the input with formatted date
      input.value = this.formatDate(this.value);
    } else {
      // Revert to previous valid value on blur if parsing failed
      input.value = this.formatDate(this.value);
    }
  }

  private parseManualDate(input: string): Date | null {
    if (!input) return null;

    // Split date and time parts if present
    const parts = input.split(' ');
    let datePart = parts[0];
    let timePart = parts[1] || '';

    let parsedDate: Date | null = null;

    // Try different date formats
    // Format 1: YYYY-MM-DD (ISO-like)
    const isoMatch = datePart.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
    if (isoMatch) {
      const [_, year, month, day] = isoMatch;
      parsedDate = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
    }

    // Format 2: DD.MM.YYYY (European)
    if (!parsedDate) {
      const euMatch = datePart.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/);
      if (euMatch) {
        const [_, day, month, year] = euMatch;
        parsedDate = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
      }
    }

    // Format 3: MM/DD/YYYY (US)
    if (!parsedDate) {
      const usMatch = datePart.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
      if (usMatch) {
        const [_, month, day, year] = usMatch;
        parsedDate = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
      }
    }

    // If no date was parsed, return null
    if (!parsedDate || isNaN(parsedDate.getTime())) {
      return null;
    }

    // Parse time if present (HH:MM format)
    if (timePart) {
      const timeMatch = timePart.match(/^(\d{1,2}):(\d{2})$/);
      if (timeMatch) {
        const [_, hours, minutes] = timeMatch;
        parsedDate.setHours(parseInt(hours));
        parsedDate.setMinutes(parseInt(minutes));
      }
    } else if (!this.enableTime) {
      // If time is not enabled and not provided, use current time
      const now = new Date();
      parsedDate.setHours(now.getHours());
      parsedDate.setMinutes(now.getMinutes());
      parsedDate.setSeconds(0);
      parsedDate.setMilliseconds(0);
    }

    return parsedDate;
  }

  public getValue(): string {
    return this.value;
  }

  public setValue(value: string): void {
    this.value = value;
    if (value) {
      try {
        const date = new Date(value);
        if (!isNaN(date.getTime())) {
          this.selectedDate = date;
          this.viewDate = new Date(date);
          this.selectedHour = date.getHours();
          this.selectedMinute = date.getMinutes();
        }
      } catch {
        // Invalid date
      }
    }
  }
}