{"version":3,"file":"expand-recurring-events.cjs","names":["rruleAll","validateEvent"],"sources":["../../../src/utils/expand-recurring-events/expand-recurring-events.ts"],"sourcesContent":["import dayjs from 'dayjs';\nimport * as rruleAll from 'rrule';\n\nconst RRule = (\n  'default' in rruleAll ? (rruleAll as any).default.RRule : rruleAll.RRule\n) as typeof rruleAll.RRule;\nimport { DateTimeStringValue, ScheduleEventData, ScheduleRecurrenceData } from '../../types';\nimport { validateEvent } from '../validate-event/validate-event';\n\ninterface ExpandRecurringEventsInput {\n  events: ScheduleEventData[] | undefined;\n  rangeStart: Date | string;\n  rangeEnd: Date | string;\n  expansionLimit?: number;\n}\n\nfunction getRecurrenceKey(recurringEventId: string | number, recurrenceId: DateTimeStringValue) {\n  return `${recurringEventId}::${recurrenceId}`;\n}\n\nfunction overlapsRange(\n  eventStart: dayjs.Dayjs,\n  eventEnd: dayjs.Dayjs,\n  rangeStart: dayjs.Dayjs,\n  rangeEnd: dayjs.Dayjs\n) {\n  return (\n    (eventEnd.isAfter(rangeStart) || eventEnd.isSame(rangeStart)) &&\n    (eventStart.isBefore(rangeEnd) || eventStart.isSame(rangeEnd))\n  );\n}\n\nfunction getExdateSet(recurrence: ScheduleRecurrenceData | undefined) {\n  if (!recurrence?.exdate) {\n    return new Set<string>();\n  }\n\n  const values = recurrence.exdate\n    .map((value) => dayjs(value))\n    .filter((value) => value.isValid())\n    .map((value) => value.format('YYYY-MM-DD HH:mm:ss'));\n\n  return new Set(values);\n}\n\nfunction getRRuleString(rule: string) {\n  const normalized = rule.trim();\n  if (!normalized.includes('\\n')) {\n    return normalized.startsWith('RRULE:') ? normalized.replace(/^RRULE:/, '') : normalized;\n  }\n\n  const rruleLine = normalized\n    .split('\\n')\n    .map((line) => line.trim())\n    .find((line) => line.startsWith('RRULE:'));\n\n  return rruleLine ? rruleLine.replace(/^RRULE:/, '') : normalized;\n}\n\nfunction getOccurrenceStartsInRange(\n  event: ScheduleEventData,\n  rangeStart: dayjs.Dayjs,\n  rangeEnd: dayjs.Dayjs,\n  expansionLimit: number,\n  durationMs: number\n) {\n  const dtstart = dayjs(event.recurrence?.dtstart || event.start);\n  if (!dtstart.isValid() || !event.recurrence) {\n    return [];\n  }\n\n  try {\n    const options = RRule.parseString(getRRuleString(event.recurrence.rrule));\n    options.dtstart = dtstart.toDate();\n    const rule = new RRule(options);\n\n    const searchStart = rangeStart.subtract(Math.max(0, durationMs), 'millisecond').toDate();\n    const results = rule.between(searchStart, rangeEnd.toDate(), true);\n    return results.slice(0, expansionLimit).map((value) => dayjs(value));\n  } catch {\n    return [];\n  }\n}\n\nfunction isEventInRange(event: ScheduleEventData, rangeStart: dayjs.Dayjs, rangeEnd: dayjs.Dayjs) {\n  const start = dayjs(event.start);\n  const end = dayjs(event.end);\n\n  if (!start.isValid() || !end.isValid()) {\n    return false;\n  }\n\n  return overlapsRange(start, end, rangeStart, rangeEnd);\n}\n\nfunction createGeneratedInstance(\n  event: ScheduleEventData,\n  occurrenceStart: dayjs.Dayjs,\n  durationMs: number\n): ScheduleEventData {\n  const occurrenceEnd = occurrenceStart.add(durationMs, 'millisecond');\n  const recurrenceId = occurrenceStart.format('YYYY-MM-DD HH:mm:ss');\n  const { recurrence, ...eventWithoutRecurrence } = event;\n\n  return {\n    ...eventWithoutRecurrence,\n    id: `${event.id}::${recurrenceId}`,\n    start: occurrenceStart.format('YYYY-MM-DD HH:mm:ss'),\n    end: occurrenceEnd.format('YYYY-MM-DD HH:mm:ss'),\n    recurringEventId: event.id,\n    recurrenceId,\n    recurringInstance: {\n      isRecurringInstance: true,\n      recurringEventId: event.id,\n      recurrenceId,\n      originalStart: occurrenceStart.format('YYYY-MM-DD HH:mm:ss'),\n      originalEnd: occurrenceEnd.format('YYYY-MM-DD HH:mm:ss'),\n    },\n  };\n}\n\nconst DEFAULT_EXPANSION_LIMIT = 2000;\n\nexport function expandRecurringEvents({\n  events,\n  rangeStart,\n  rangeEnd,\n  expansionLimit = DEFAULT_EXPANSION_LIMIT,\n}: ExpandRecurringEventsInput): ScheduleEventData[] {\n  if (!events || events.length === 0) {\n    return [];\n  }\n\n  const start = dayjs(rangeStart);\n  const end = dayjs(rangeEnd);\n\n  if (!start.isValid() || !end.isValid()) {\n    return [];\n  }\n\n  const regularEvents: ScheduleEventData[] = [];\n  const recurringEvents: ScheduleEventData[] = [];\n  const overrides = new Map<string, ScheduleEventData>();\n\n  for (const event of events) {\n    if (event.recurringEventId && event.recurrenceId) {\n      overrides.set(getRecurrenceKey(event.recurringEventId, event.recurrenceId), event);\n      continue;\n    }\n\n    if (event.recurrence?.rrule) {\n      recurringEvents.push(event);\n      continue;\n    }\n\n    regularEvents.push(event);\n  }\n\n  const output: ScheduleEventData[] = [];\n  const consumedOverrideIds = new Set<string | number>();\n\n  for (const event of regularEvents) {\n    if (isEventInRange(event, start, end)) {\n      output.push(validateEvent(event));\n    }\n  }\n\n  for (const event of recurringEvents) {\n    const eventStart = dayjs(event.recurrence?.dtstart || event.start);\n    const eventEnd = dayjs(event.end);\n\n    if (!eventStart.isValid() || !eventEnd.isValid()) {\n      continue;\n    }\n\n    const durationMs = eventEnd.diff(dayjs(event.start), 'millisecond');\n    if (durationMs < 0) {\n      continue;\n    }\n\n    const exdate = getExdateSet(event.recurrence);\n    const starts = getOccurrenceStartsInRange(event, start, end, expansionLimit, durationMs);\n\n    for (const occurrenceStart of starts) {\n      const recurrenceId = occurrenceStart.format('YYYY-MM-DD HH:mm:ss');\n      if (exdate.has(recurrenceId)) {\n        continue;\n      }\n\n      const overrideKey = getRecurrenceKey(event.id, recurrenceId);\n      const override = overrides.get(overrideKey);\n      if (override) {\n        if (isEventInRange(override, start, end)) {\n          output.push(validateEvent(override));\n          consumedOverrideIds.add(override.id);\n        }\n        continue;\n      }\n\n      const generated = createGeneratedInstance(event, occurrenceStart, durationMs);\n      if (overlapsRange(dayjs(generated.start), dayjs(generated.end), start, end)) {\n        output.push(validateEvent(generated));\n      }\n    }\n  }\n\n  for (const override of overrides.values()) {\n    if (consumedOverrideIds.has(override.id)) {\n      continue;\n    }\n    if (isEventInRange(override, start, end)) {\n      output.push(validateEvent(override));\n    }\n  }\n\n  return output;\n}\n\nexport { DEFAULT_EXPANSION_LIMIT };\n"],"mappings":"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"}