import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import isoWeek from 'dayjs/plugin/isoWeek';
import { ITimeFormatConfig } from './ITimeFormatConfig';

// Enable dayjs plugins
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(isoWeek);

export class DateService {
  private timezone: string;
  private baseDate: dayjs.Dayjs;

  constructor(private config: ITimeFormatConfig, baseDate?: Date) {
    this.timezone = config.timezone;
    // Allow setting a fixed base date for demo/testing purposes
    this.baseDate = baseDate ? dayjs(baseDate) : dayjs();
  }

  /**
   * Set a fixed base date (useful for demos with static mock data)
   */
  setBaseDate(date: Date): void {
    this.baseDate = dayjs(date);
  }

  /**
   * Get the current base date (either fixed or today)
   */
  getBaseDate(): Date {
    return this.baseDate.toDate();
  }

  parseISO(isoString: string): Date {
    return dayjs(isoString).toDate();
  }

  getDayName(date: Date, format: 'short' | 'long' = 'short'): string {
    return new Intl.DateTimeFormat(this.config.locale, { weekday: format }).format(date);
  }

  /**
   * Get dates starting from a day offset
   * @param dayOffset - Day offset from base date
   * @param count - Number of consecutive days to return
   * @returns Array of date strings in YYYY-MM-DD format
   */
  getDatesFromOffset(dayOffset: number, count: number): string[] {
    const startDate = this.baseDate.add(dayOffset, 'day');
    return Array.from({ length: count }, (_, i) =>
      startDate.add(i, 'day').format('YYYY-MM-DD')
    );
  }

  /**
   * Get specific weekdays from the week containing the offset date
   * @param dayOffset - Day offset from base date
   * @param workDays - Array of ISO weekday numbers (1=Monday, 7=Sunday)
   * @returns Array of date strings in YYYY-MM-DD format
   */
  getWorkDaysFromOffset(dayOffset: number, workDays: number[]): string[] {
    // Get the date at offset, then find its week's Monday
    const targetDate = this.baseDate.add(dayOffset, 'day');
    const monday = targetDate.startOf('week').add(1, 'day');

    return workDays.map(isoDay => {
      // ISO: 1=Monday, 7=Sunday → days from Monday: 0-6
      const daysFromMonday = isoDay === 7 ? 6 : isoDay - 1;
      return monday.add(daysFromMonday, 'day').format('YYYY-MM-DD');
    });
  }

  // Legacy methods for backwards compatibility
  getWeekDates(weekOffset = 0, days = 7): string[] {
    return this.getDatesFromOffset(weekOffset * 7, days);
  }

  getWorkWeekDates(weekOffset: number, workDays: number[]): string[] {
    return this.getWorkDaysFromOffset(weekOffset * 7, workDays);
  }

  // ============================================
  // FORMATTING
  // ============================================

  formatTime(date: Date, showSeconds = false): string {
    const pattern = showSeconds ? 'HH:mm:ss' : 'HH:mm';
    return dayjs(date).format(pattern);
  }

  formatTimeRange(start: Date, end: Date): string {
    return `${this.formatTime(start)} - ${this.formatTime(end)}`;
  }

  formatDate(date: Date): string {
    return dayjs(date).format('YYYY-MM-DD');
  }

  getDateKey(date: Date): string {
    return this.formatDate(date);
  }

  // ============================================
  // COLUMN KEY
  // ============================================

  /**
   * Build a uniform columnKey from grouping segments
   * Handles any combination of date, resource, team, etc.
   *
   * @example
   * buildColumnKey({ date: '2025-12-09' }) → "2025-12-09"
   * buildColumnKey({ date: '2025-12-09', resource: 'EMP001' }) → "2025-12-09:EMP001"
   */
  buildColumnKey(segments: Record<string, string>): string {
    // Always put date first if present, then other segments alphabetically
    const date = segments.date;
    const others = Object.entries(segments)
      .filter(([k]) => k !== 'date')
      .sort(([a], [b]) => a.localeCompare(b))
      .map(([, v]) => v);

    return date ? [date, ...others].join(':') : others.join(':');
  }

  /**
   * Parse a columnKey back into segments
   * Assumes format: "date:resource:..." or just "date"
   */
  parseColumnKey(columnKey: string): { date: string; resource?: string } {
    const parts = columnKey.split(':');
    return {
      date: parts[0],
      resource: parts[1]
    };
  }

  /**
   * Extract dateKey from columnKey (first segment)
   */
  getDateFromColumnKey(columnKey: string): string {
    return columnKey.split(':')[0];
  }

  // ============================================
  // TIME CALCULATIONS
  // ============================================

  timeToMinutes(timeString: string): number {
    const parts = timeString.split(':').map(Number);
    const hours = parts[0] || 0;
    const minutes = parts[1] || 0;
    return hours * 60 + minutes;
  }

  minutesToTime(totalMinutes: number): string {
    const hours = Math.floor(totalMinutes / 60);
    const minutes = totalMinutes % 60;
    return dayjs().hour(hours).minute(minutes).format('HH:mm');
  }

  getMinutesSinceMidnight(date: Date): number {
    const d = dayjs(date);
    return d.hour() * 60 + d.minute();
  }

  // ============================================
  // UTC CONVERSIONS
  // ============================================

  toUTC(localDate: Date): string {
    return dayjs.tz(localDate, this.timezone).utc().toISOString();
  }

  fromUTC(utcString: string): Date {
    return dayjs.utc(utcString).tz(this.timezone).toDate();
  }

  // ============================================
  // DATE CREATION
  // ============================================

  createDateAtTime(baseDate: Date | string, timeString: string): Date {
    const totalMinutes = this.timeToMinutes(timeString);
    const hours = Math.floor(totalMinutes / 60);
    const minutes = totalMinutes % 60;
    return dayjs(baseDate).startOf('day').hour(hours).minute(minutes).toDate();
  }

  getISOWeekDay(date: Date | string): number {
    return dayjs(date).isoWeekday();  // 1=Monday, 7=Sunday
  }
}
