import { RRule, rrulestr } from "rrule";
import { CalendarEvent, ExpandedEvent, formatDateKey } from "@calchat/shared";

// Convert local time to "fake UTC" for rrule
// rrule interprets all dates as UTC internally, so we need to trick it
function toRRuleDate(date: Date): Date {
  return new Date(
    Date.UTC(
      date.getFullYear(),
      date.getMonth(),
      date.getDate(),
      date.getHours(),
      date.getMinutes(),
      date.getSeconds(),
    ),
  );
}

// Convert rrule result back to local time
function fromRRuleDate(date: Date): Date {
  return new Date(
    date.getUTCFullYear(),
    date.getUTCMonth(),
    date.getUTCDate(),
    date.getUTCHours(),
    date.getUTCMinutes(),
    date.getUTCSeconds(),
  );
}

/**
 * Expand recurring events into individual occurrences within a date range.
 * Non-recurring events are returned as-is with occurrenceStart/End = startTime/endTime.
 */
export function expandRecurringEvents(
  events: CalendarEvent[],
  rangeStart: Date,
  rangeEnd: Date,
): ExpandedEvent[] {
  const expanded: ExpandedEvent[] = [];

  for (const event of events) {
    const startTime = new Date(event.startTime);
    const endTime = new Date(event.endTime);
    const duration = endTime.getTime() - startTime.getTime();

    // For multi-day events: adjust range start back by event duration
    // to find events that start before rangeStart but extend into the range
    const adjustedRangeStart = new Date(rangeStart.getTime() - duration);

    if (!event.recurrenceRule) {
      // Non-recurring event: add if it overlaps with the range
      if (endTime >= rangeStart && startTime <= rangeEnd) {
        expanded.push({
          ...event,
          occurrenceStart: startTime,
          occurrenceEnd: endTime,
        });
      }
      continue;
    }

    // Recurring event: parse RRULE and expand
    try {
      // Strip RRULE: prefix if present (AI may include it)
      const ruleString = event.recurrenceRule.replace(/^RRULE:/i, "");
      const rule = rrulestr(
        `DTSTART:${formatRRuleDateString(startTime)}\nRRULE:${ruleString}`,
      );

      // Get occurrences within the adjusted range (using fake UTC dates)
      // Use adjustedRangeStart to catch multi-day events that start before
      // rangeStart but still extend into the range
      const occurrences = rule.between(
        toRRuleDate(adjustedRangeStart),
        toRRuleDate(rangeEnd),
        true, // inclusive
      );

      // Build set of exception dates for fast lookup
      const exceptionSet = new Set(event.exceptionDates || []);

      for (const occurrence of occurrences) {
        const occurrenceStart = fromRRuleDate(occurrence);
        const occurrenceEnd = new Date(occurrenceStart.getTime() + duration);

        // Only include if occurrence actually overlaps with the original range
        if (occurrenceEnd < rangeStart || occurrenceStart > rangeEnd) {
          continue;
        }

        // Skip if this occurrence is in the exception dates
        const dateKey = formatDateKey(occurrenceStart);
        if (exceptionSet.has(dateKey)) {
          continue;
        }

        expanded.push({
          ...event,
          occurrenceStart,
          occurrenceEnd,
        });
      }
    } catch (error) {
      // If RRULE parsing fails, include the event as a single occurrence
      console.error(
        `Failed to parse recurrence rule for event ${event.id}:`,
        error,
      );
      expanded.push({
        ...event,
        occurrenceStart: startTime,
        occurrenceEnd: endTime,
      });
    }
  }

  // Sort by occurrence start time
  expanded.sort(
    (a, b) => a.occurrenceStart.getTime() - b.occurrenceStart.getTime(),
  );

  return expanded;
}

// Format date as RRULE DTSTART string (YYYYMMDDTHHMMSS)
function formatRRuleDateString(date: Date): string {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  const hours = String(date.getHours()).padStart(2, "0");
  const minutes = String(date.getMinutes()).padStart(2, "0");
  const seconds = String(date.getSeconds()).padStart(2, "0");
  return `${year}${month}${day}T${hours}${minutes}${seconds}`;
}
