import { DAVClient } from 'tsdav';
import ICAL from 'ical.js';

// iCloud CalDAV Server URL
const ICLOUD_CALDAV_URL = 'https://caldav.icloud.com';

export interface AppleCalendarEvent {
  id: string;
  title: string;
  startDate: string;
  endDate: string;
  description?: string;
  location?: string;
  url?: string;
  recurringEventId?: string;
  isRecurring?: boolean;
  reminders?: Array<{ method: string; minutes: number }>;
  attendees?: Array<{ email: string; displayName?: string; responseStatus?: string }>;
  attachments?: Array<{ url: string; title?: string }>;
  busyStatus?: string;
  visibility?: string;
}

/**
 * Format Date to local YYYY-MM-DD string without timezone shift.
 */
function formatDateToLocalISO(date: Date): string {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
}

/**
 * Convert reminder minutes to iCalendar TRIGGER duration string (e.g. -PT15M, -PT1H, -P1D, -P1W)
 */
function reminderMinutesToDuration(minutes: number): string {
  if (minutes === 0) return 'PT0S'; // At time of event
  const prefix = '-'; // before event
  if (minutes % 10080 === 0) return `${prefix}P${minutes / 10080}W`;
  if (minutes % 1440 === 0) return `${prefix}P${minutes / 1440}D`;
  if (minutes % 60 === 0) return `${prefix}PT${minutes / 60}H`;
  return `${prefix}PT${minutes}M`;
}

/**
 * Extract extended properties (reminders, attendees, attachments, busy status, visibility) from a VEVENT component
 */
function extractExtendedProps(vevent: any): Pick<AppleCalendarEvent, 'reminders' | 'attendees' | 'attachments' | 'busyStatus' | 'visibility'> {
  const result: Pick<AppleCalendarEvent, 'reminders' | 'attendees' | 'attachments' | 'busyStatus' | 'visibility'> = {};

  // Extract VALARM components
  const valarms = vevent.getAllSubcomponents('valarm');
  if (valarms && valarms.length > 0) {
    result.reminders = valarms.map((valarm: any) => {
      const action = valarm.getFirstPropertyValue('action') || 'DISPLAY';
      const trigger = valarm.getFirstProperty('trigger');
      let minutes = 15; // default
      if (trigger) {
        const triggerVal = trigger.getFirstValue();
        if (triggerVal && typeof triggerVal.toSeconds === 'function') {
          // ICAL.Duration - convert to minutes (negative = before event)
          minutes = Math.abs(Math.round(triggerVal.toSeconds() / 60));
        } else if (typeof triggerVal === 'string') {
          // Parse ISO duration like -PT15M
          const match = triggerVal.match(/^-?PT?(\d+)([MHDS])/i);
          if (match) {
            const val = parseInt(match[1]);
            switch (match[2].toUpperCase()) {
              case 'M': minutes = val; break;
              case 'H': minutes = val * 60; break;
              case 'D': minutes = val * 1440; break;
              case 'S': minutes = Math.round(val / 60); break;
            }
          }
          // Handle -P1W (1 week)
          const weekMatch = triggerVal.match(/^-?P(\d+)W/i);
          if (weekMatch) minutes = parseInt(weekMatch[1]) * 10080;
          // Handle -P1D (1 day)
          const dayMatch = triggerVal.match(/^-?P(\d+)D/i);
          if (dayMatch) minutes = parseInt(dayMatch[1]) * 1440;
        }
      }
      return {
        method: action.toString().toLowerCase() === 'email' ? 'email' : 'display',
        minutes,
      };
    });
  }

  // Extract ATTENDEE properties
  const attendeeProps = vevent.getAllProperties('attendee');
  if (attendeeProps && attendeeProps.length > 0) {
    result.attendees = attendeeProps.map((prop: any) => {
      const val = prop.getFirstValue() || '';
      const email = val.replace(/^mailto:/i, '');
      const cn = prop.getParameter('cn');
      const partstat = prop.getParameter('partstat');
      const statusMap: Record<string, string> = {
        'ACCEPTED': 'accepted',
        'DECLINED': 'declined',
        'TENTATIVE': 'tentative',
        'NEEDS-ACTION': 'needsAction',
      };
      return {
        email,
        displayName: cn || undefined,
        responseStatus: statusMap[partstat?.toUpperCase()] || 'needsAction',
      };
    });
  }

  // Extract ATTACH properties (URL-based)
  const attachProps = vevent.getAllProperties('attach');
  if (attachProps && attachProps.length > 0) {
    result.attachments = attachProps
      .map((prop: any) => {
        const val = prop.getFirstValue();
        if (typeof val === 'string' && (val.startsWith('http://') || val.startsWith('https://'))) {
          const fmttype = prop.getParameter('fmttype');
          const filename = prop.getParameter('filename');
          return { url: val, title: filename || undefined };
        }
        return null;
      })
      .filter(Boolean) as Array<{ url: string; title?: string }>;
    if (result.attachments.length === 0) delete result.attachments;
  }

  // Extract TRANSP (busy status)
  const transp = vevent.getFirstPropertyValue('transp');
  if (transp) {
    result.busyStatus = transp.toString().toUpperCase() === 'TRANSPARENT' ? 'free' : 'busy';
  }

  // Extract CLASS (visibility)
  const cls = vevent.getFirstPropertyValue('class');
  if (cls) {
    const clsMap: Record<string, string> = {
      'PUBLIC': 'public',
      'PRIVATE': 'private',
      'CONFIDENTIAL': 'confidential',
    };
    result.visibility = (clsMap[cls.toString().toUpperCase()] || 'default') as any;
  }

  return result;
}

export interface AppleCalendar {
  id: string;
  title: string;
  color?: string;
  isPrimary?: boolean;
}

/**
 * Create a configured DAV client for Apple iCloud
 */
const createClient = (email: string, appSpecificPassword: string) => {
  return new DAVClient({
    serverUrl: ICLOUD_CALDAV_URL,
    credentials: {
      username: email,
      password: appSpecificPassword,
    },
    authMethod: 'Basic',
    defaultAccountType: 'caldav',
  });
};

/**
 * Validate credentials by attempting to fetch calendars
 * @returns List of found calendars if successful
 */
export const validateCredentials = async (email: string, appSpecificPassword: string): Promise<AppleCalendar[]> => {
  try {
    const client = createClient(email, appSpecificPassword);
    await client.login();

    const calendars = await client.fetchCalendars();

    // Filter to VEVENT-only calendars — exclude VTODO (Reminders) collections
    const eventCalendars = calendars.filter(cal => {
      const components: string[] = (cal as any).components || [];
      // Keep if components include VEVENT, or if components is empty/undefined
      // (some calendars don't advertise components). Exclude if VTODO-only.
      if (components.length === 0) return true;
      return components.includes('VEVENT');
    });

    const mappedCalendars = eventCalendars.map(cal => ({
      id: cal.url, // Using URL as ID for CalDAV
      title: (cal.displayName as string) || 'Untitled Calendar',
      color: cal.calendarColor,
      isPrimary: false,
    }));

    console.log('[APPLE CALENDAR] Found calendars:', mappedCalendars.length, '(filtered from', calendars.length, 'total)');
    return mappedCalendars;
  } catch (error) {
    console.error('Apple Calendar validation failed:', error);
    throw new Error('Invalid credentials or unable to connect to iCloud.');
  }
};

/**
 * Get user calendars (wrapper around validateCredentials for now as they do the same)
 */
export const getUserCalendars = validateCredentials;

/**
 * Get upcoming events for a specified time period from a specific calendar
 */
export const getUpcomingEvents = async (
  email: string,
  appSpecificPassword: string,
  calendarUrl: string,
  timeMin: string,
  timeMax: string
): Promise<AppleCalendarEvent[]> => {
  try {
    const client = createClient(email, appSpecificPassword);
    await client.login();

    const calendars = await client.fetchCalendars();
    const targetCalendar = calendars.find(c => c.url === calendarUrl);

    if (!targetCalendar) {
      throw new Error(`Calendar not found: ${calendarUrl}`);
    }

    const events = await client.fetchCalendarObjects({
      calendar: targetCalendar,
      timeRange: {
        start: new Date(timeMin).toISOString(),
        end: new Date(timeMax).toISOString(),
      },
    });

    const parsedEvents: AppleCalendarEvent[] = [];
    const minTime = new Date(timeMin).getTime();
    const maxTime = new Date(timeMax).getTime();

    events.forEach(eventObj => {
      const data = (eventObj as any).data;
      if (!data) return;

      try {
        const jcalData = ICAL.parse(data);
        const comp = new ICAL.Component(jcalData);
        const vevents = comp.getAllSubcomponents('vevent');

        // Separate base events (with RRULE) from recurrence exceptions (with RECURRENCE-ID)
        const baseEvents: any[] = [];
        const exceptions: Map<string, any[]> = new Map();

        vevents.forEach((vevent: any) => {
          const recurrenceId = vevent.getFirstPropertyValue('recurrence-id');
          if (recurrenceId) {
            // This is a recurrence exception - collect it
            const event = new ICAL.Event(vevent);
            const uid = event.uid;
            if (!exceptions.has(uid)) exceptions.set(uid, []);
            exceptions.get(uid)!.push(vevent);
          } else {
            baseEvents.push(vevent);
          }
        });

        baseEvents.forEach((vevent: any) => {
          const event = new ICAL.Event(vevent);
          const rrule = vevent.getFirstPropertyValue('rrule');

          if (rrule) {
            // Recurring event - expand occurrences within the time range
            const uid = event.uid;
            const exceptionVevents = exceptions.get(uid) || [];
            const exceptionDates = new Set<string>();

            // First, process exceptions that fall in our range
            const isAllDayRecurring = event.startDate.isDate === true;

            exceptionVevents.forEach((exVevent: any) => {
              const exEvent = new ICAL.Event(exVevent);
              const recId = exVevent.getFirstPropertyValue('recurrence-id');
              if (recId) {
                exceptionDates.add(recId.toJSDate().toISOString());
              }

              const exStart = exEvent.startDate.toJSDate();
              const exEnd = exEvent.endDate.toJSDate();
              const exIsAllDay = exEvent.startDate.isDate === true;

              if (exEnd.getTime() >= minTime && exStart.getTime() <= maxTime) {
                parsedEvents.push({
                  id: `caldav::${eventObj.url}::${exEvent.uid}::${exStart.toISOString()}`,
                  title: exEvent.summary || 'Untitled Event',
                  startDate: exIsAllDay ? formatDateToLocalISO(exStart) : exStart.toISOString(),
                  endDate: exIsAllDay ? formatDateToLocalISO(exEnd) : exEnd.toISOString(),
                  description: exEvent.description,
                  location: exEvent.location,
                  url: exVevent.getFirstPropertyValue('url')?.toString() || undefined,
                  recurringEventId: exEvent.uid,
                  isRecurring: true,
                  ...extractExtendedProps(exVevent),
                });
              }
            });

            // Then expand the recurrence rule
            try {
              const duration = event.endDate.toJSDate().getTime() - event.startDate.toJSDate().getTime();
              const iter = event.iterator();
              let next;
              let safetyCount = 0;

              while ((next = iter.next()) && safetyCount < 500) {
                safetyCount++;
                const occStart = next.toJSDate();
                const occEnd = new Date(occStart.getTime() + duration);

                // Stop if we've gone past the range
                if (occStart.getTime() > maxTime) break;

                // Skip if before range
                if (occEnd.getTime() < minTime) continue;

                // Skip if this occurrence is overridden by an exception
                if (exceptionDates.has(occStart.toISOString())) continue;

                parsedEvents.push({
                  id: `caldav::${eventObj.url}::${event.uid}::${occStart.toISOString()}`,
                  title: event.summary || 'Untitled Event',
                  startDate: isAllDayRecurring ? formatDateToLocalISO(occStart) : occStart.toISOString(),
                  endDate: isAllDayRecurring ? formatDateToLocalISO(occEnd) : occEnd.toISOString(),
                  description: event.description,
                  location: event.location,
                  url: vevent.getFirstPropertyValue('url')?.toString() || undefined,
                  recurringEventId: event.uid,
                  isRecurring: true,
                  ...extractExtendedProps(vevent),
                });
              }
            } catch (expandErr: any) {
              console.error(`[APPLE CALENDAR] Error expanding recurrence for "${event.summary}":`, expandErr);
            }
          } else {
            // Simple non-recurring event
            const start = event.startDate.toJSDate();
            const end = event.endDate.toJSDate();
            const isAllDay = event.startDate.isDate === true;

            if (end.getTime() < minTime || start.getTime() > maxTime) return;

            parsedEvents.push({
              id: `caldav::${eventObj.url}::${event.uid || 'unknown'}`,
              title: event.summary || 'Untitled Event',
              startDate: isAllDay ? formatDateToLocalISO(start) : start.toISOString(),
              endDate: isAllDay ? formatDateToLocalISO(end) : end.toISOString(),
              description: event.description,
              location: event.location,
              url: vevent.getFirstPropertyValue('url')?.toString() || undefined,
              ...extractExtendedProps(vevent),
            });
          }
        });
      } catch (parseErr: any) {
        console.error(`[APPLE CALENDAR] Error parsing event data for calendar ${calendarUrl}:`, parseErr);
      }
    });

    return parsedEvents;
  } catch (error: any) {
    console.error(`[APPLE CALENDAR] Error fetching events for ${calendarUrl}:`, error);
    return [];
  }
};

/**
 * Create a new event in the specified calendar
 */
export const createEvent = async (
  email: string,
  appSpecificPassword: string,
  calendarUrl: string,
  eventData: {
    title: string;
    description?: string;
    location?: string;
    url?: string;
    recurrence?: string;
    start: { dateTime?: string; date?: string };
    end: { dateTime?: string; date?: string };
    reminders?: Array<{ method: string; minutes: number }>;
    attendees?: Array<{ email: string; displayName?: string }>;
    attachments?: Array<{ url: string; title?: string }>;
    busyStatus?: string;
    visibility?: string;
  }
): Promise<AppleCalendarEvent> => {
  try {
    const client = createClient(email, appSpecificPassword);
    await client.login();

    const calendars = await client.fetchCalendars();
    const targetCalendar = calendars.find(c => c.url === calendarUrl);

    if (!targetCalendar) {
      throw new Error(`Calendar not found: ${calendarUrl}`);
    }

    // Generate iCal string
    const now = new Date();
    const uid = crypto.randomUUID();

    // Construct VCALENDAR/VEVENT manually to ensure compatibility
    // ical.js is great for parsing but sometimes verbose for creation
    // A simple template works well for basic events

    const dtStamp = now.toISOString().replace(/[-:.]/g, '').substring(0, 15) + 'Z';

    let dtStart = '';
    let dtEnd = '';
    let dtStartParam = '';
    let dtEndParam = '';

    if (eventData.start.date) {
      // All-day event
      dtStart = eventData.start.date.replace(/-/g, '');
      dtEnd = eventData.end.date ? eventData.end.date.replace(/-/g, '') : dtStart; // Fallback
      dtStartParam = ';VALUE=DATE';
      dtEndParam = ';VALUE=DATE';

      // For all-day events, end date is exclusive, so if they are same, add 1 day
      // But typically UI handles this. Let's assume input is correct.
    } else if (eventData.start.dateTime) {
      // Timed event
      dtStart = new Date(eventData.start.dateTime).toISOString().replace(/[-:.]/g, '').substring(0, 15) + 'Z';
      dtEnd = eventData.end.dateTime
        ? new Date(eventData.end.dateTime).toISOString().replace(/[-:.]/g, '').substring(0, 15) + 'Z'
        : dtStart;
    }

    const description = eventData.description ? `DESCRIPTION:${eventData.description.replace(/\n/g, '\\n')}\r\n` : '';
    const location = eventData.location ? `LOCATION:${eventData.location.replace(/,/g, '\\,')}\r\n` : '';
    const url = eventData.url ? `URL:${eventData.url}\r\n` : '';
    let rruleLine = '';
    if (eventData.recurrence) {
      const rruleMap: Record<string, string> = {
        daily: 'RRULE:FREQ=DAILY',
        weekly: 'RRULE:FREQ=WEEKLY',
        biweekly: 'RRULE:FREQ=WEEKLY;INTERVAL=2',
        monthly: 'RRULE:FREQ=MONTHLY',
        yearly: 'RRULE:FREQ=YEARLY',
      };
      if (rruleMap[eventData.recurrence]) {
        rruleLine = `${rruleMap[eventData.recurrence]}\r\n`;
      }
    }

    // Generate VALARM blocks for reminders
    let valarmLines = '';
    if (eventData.reminders && eventData.reminders.length > 0) {
      for (const reminder of eventData.reminders) {
        const action = reminder.method === 'email' ? 'EMAIL' : 'DISPLAY';
        const dur = reminderMinutesToDuration(reminder.minutes);
        valarmLines += `BEGIN:VALARM\r\nACTION:${action}\r\nTRIGGER:${dur}\r\n`;
        if (action === 'DISPLAY') valarmLines += `DESCRIPTION:Reminder\r\n`;
        valarmLines += `END:VALARM\r\n`;
      }
    }

    // Generate ATTENDEE lines
    let attendeeLines = '';
    if (eventData.attendees && eventData.attendees.length > 0) {
      for (const att of eventData.attendees) {
        const cn = att.displayName ? `;CN=${att.displayName}` : '';
        attendeeLines += `ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION${cn}:mailto:${att.email}\r\n`;
      }
    }

    // Generate ATTACH lines (URL-based)
    let attachLines = '';
    if (eventData.attachments && eventData.attachments.length > 0) {
      for (const att of eventData.attachments) {
        attachLines += `ATTACH:${att.url}\r\n`;
      }
    }

    // TRANSP (busy status)
    const transpLine = eventData.busyStatus === 'free' ? 'TRANSP:TRANSPARENT\r\n' : eventData.busyStatus ? 'TRANSP:OPAQUE\r\n' : '';

    // CLASS (visibility)
    let classLine = '';
    if (eventData.visibility && eventData.visibility !== 'default') {
      classLine = `CLASS:${eventData.visibility.toUpperCase()}\r\n`;
    }

    const iCalString = `BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//My Weekly ToDo List//EN
BEGIN:VEVENT
UID:${uid}
DTSTAMP:${dtStamp}
DTSTART${dtStartParam}:${dtStart}
DTEND${dtEndParam}:${dtEnd}
SUMMARY:${eventData.title}
${description}${location}${url}${rruleLine}${transpLine}${classLine}${attendeeLines}${attachLines}${valarmLines}END:VEVENT
END:VCALENDAR`;

    console.log('[APPLE CALENDAR] Creating event with iCal:', iCalString);

    const filename = `${uid}.ics`;

    await client.createCalendarObject({
      calendar: targetCalendar,
      filename,
      iCalString
    });

    return {
      id: `${uid}-${filename}`, // Composite ID to help with updates later if needed, but usually UID is enough
      title: eventData.title,
      startDate: eventData.start.dateTime || eventData.start.date || '',
      endDate: eventData.end.dateTime || eventData.end.date || '',
      description: eventData.description,
      location: eventData.location,
      url: eventData.url
    };
  } catch (error) {
    console.error('[APPLE CALENDAR] Error creating event:', error);
    throw error;
  }
};

/**
 * Update an existing event
 */
export const updateEvent = async (
  email: string,
  appSpecificPassword: string,
  calendarUrl: string,
  eventId: string, // This might be composite or just UID
  eventData: {
    title?: string;
    description?: string;
    location?: string;
    url?: string;
    start?: { dateTime?: string; date?: string };
    end?: { dateTime?: string; date?: string };
    reminders?: Array<{ method: string; minutes: number }>;
    attendees?: Array<{ email: string; displayName?: string }>;
    attachments?: Array<{ url: string; title?: string }>;
    busyStatus?: string;
    visibility?: string;
  }
): Promise<AppleCalendarEvent> => {
  try {
    const client = createClient(email, appSpecificPassword);
    await client.login();

    let targetObject: any = null;

    // Try O(1) path first with new caldav:: ID format
    const parsed = parseCaldavId(eventId);
    if (parsed) {
      // Fetch single object directly by URL (O(1))
      const objects = await client.fetchCalendarObjects({
        calendar: { url: calendarUrl } as any,
        objectUrls: [parsed.objectUrl],
      });
      targetObject = objects?.[0] || null;
      if (!targetObject) {
        throw new Error('Event not found on server at URL: ' + parsed.objectUrl);
      }
    } else {
      // Legacy fallback: O(n) scan for old-format IDs
      const calendars = await client.fetchCalendars();
      const targetCalendar = calendars.find(c => c.url === calendarUrl);

      if (!targetCalendar) {
        throw new Error(`Calendar not found: ${calendarUrl}`);
      }

      const uid = eventId.split('-')[0];

      const allObjects = await client.fetchCalendarObjects({
        calendar: targetCalendar,
      });

      targetObject = allObjects.find(obj => {
        if (obj.data) {
          return obj.data.includes(`UID:${uid}`);
        }
        return obj.url.includes(uid);
      });

      if (!targetObject) {
        throw new Error('Event not found on server');
      }
    }

    // Now we have the object.
    // Parse existing iCal to preserve other fields
    const jcal = ICAL.parse(targetObject.data);
    const comp = new ICAL.Component(jcal);
    const vevent = comp.getFirstSubcomponent('vevent');

    if (!vevent) {
      throw new Error('No VEVENT found in calendar object');
    }
    const event = new ICAL.Event(vevent);

    // Update fields
    if (eventData.title) event.summary = eventData.title;
    if (eventData.description) event.description = eventData.description;
    if (eventData.location) event.location = eventData.location;

    if (eventData.url !== undefined) {
      if (eventData.url) {
        vevent.updatePropertyWithValue('url', eventData.url);
      } else {
        vevent.removeProperty('url');
      }
    }

    if (eventData.start) {
      if (eventData.start.date) {
        event.startDate = ICAL.Time.fromJSDate(new Date(eventData.start.date), true);
        event.startDate.isDate = true;
      } else if (eventData.start.dateTime) {
        event.startDate = ICAL.Time.fromJSDate(new Date(eventData.start.dateTime), true);
        event.startDate.isDate = false;
      }
    }

    if (eventData.end) {
      if (eventData.end.date) {
        event.endDate = ICAL.Time.fromJSDate(new Date(eventData.end.date), true);
        event.endDate.isDate = true;
      } else if (eventData.end.dateTime) {
        event.endDate = ICAL.Time.fromJSDate(new Date(eventData.end.dateTime), true);
        event.endDate.isDate = false;
      }
    }

    // Update reminders (VALARM)
    if (eventData.reminders !== undefined) {
      // Remove existing VALARMs
      const existingAlarms = vevent.getAllSubcomponents('valarm');
      existingAlarms.forEach((a: any) => vevent.removeSubcomponent(a));
      // Add new ones
      for (const reminder of eventData.reminders) {
        const valarm = new ICAL.Component('valarm');
        valarm.addPropertyWithValue('action', reminder.method === 'email' ? 'EMAIL' : 'DISPLAY');
        const dur = ICAL.Duration.fromString(reminderMinutesToDuration(reminder.minutes));
        valarm.addPropertyWithValue('trigger', dur);
        if (reminder.method !== 'email') {
          valarm.addPropertyWithValue('description', 'Reminder');
        }
        vevent.addSubcomponent(valarm);
      }
    }

    // Update attendees
    if (eventData.attendees !== undefined) {
      // Remove existing ATTENDEEs
      vevent.removeAllProperties('attendee');
      for (const att of eventData.attendees) {
        const prop = new ICAL.Property('attendee');
        prop.setValue(`mailto:${att.email}`);
        prop.setParameter('cutype', 'INDIVIDUAL');
        prop.setParameter('role', 'REQ-PARTICIPANT');
        prop.setParameter('partstat', 'NEEDS-ACTION');
        if (att.displayName) prop.setParameter('cn', att.displayName);
        vevent.addProperty(prop);
      }
    }

    // Update attachments
    if (eventData.attachments !== undefined) {
      vevent.removeAllProperties('attach');
      for (const att of eventData.attachments) {
        vevent.addPropertyWithValue('attach', att.url);
      }
    }

    // Update TRANSP (busy status)
    if (eventData.busyStatus !== undefined) {
      if (eventData.busyStatus === 'free') {
        vevent.updatePropertyWithValue('transp', 'TRANSPARENT');
      } else if (eventData.busyStatus) {
        vevent.updatePropertyWithValue('transp', 'OPAQUE');
      }
    }

    // Update CLASS (visibility)
    if (eventData.visibility !== undefined) {
      if (eventData.visibility && eventData.visibility !== 'default') {
        vevent.updatePropertyWithValue('class', eventData.visibility.toUpperCase());
      } else {
        vevent.removeProperty('class');
      }
    }

    // Bump sequence
    event.sequence = (event.sequence || 0) + 1;
    if (vevent) {
      vevent.updatePropertyWithValue('dtstamp', ICAL.Time.now());
    }

    const updatedIcalString = comp.toString();
    console.log('[APPLE CALENDAR] Updating event with iCal:', updatedIcalString);

    // tsdav types might be slightly off in the d.ts compared to usage or we need to check the actual signature
    // In d.ts: updateCalendarObject(params: { calendarObject: DAVCalendarObject ... }) -> Promise<Response>
    // But it doesn't seem to take 'data' in the d.ts signature shown earlier?
    // Wait, let's look at d.ts again.
    // updateCalendarObject: (params: { calendarObject: ..., headers?: ... })
    // It DOES NOT show `data` or `etag` in the params in the d.ts signature shown earlier?
    // Let's check line 117 of d.ts:
    // updateCalendarObject: (params: { calendarObject: ... })
    // This implies the data must be SET on the calendarObject before calling? 
    // OR the d.ts is incomplete/wrong.

    // Let's assume we need to update the object locally then call update?
    // Or maybe we use the `davRequest` or `updateObject` lower level if `updateCalendarObject` limits us.
    // Actually, `updateObject` takes `url`, `data`, `etag`.

    // Let's try using `client.updateObject` directly which is more raw but allows data.
    // `targetObject.url` is what we need.

    await client.updateObject({
      url: targetObject.url,
      data: updatedIcalString,
      etag: targetObject.etag
    } as any); // Cast to any to bypass type definition mismatch

    return {
      id: eventId,
      title: event.summary,
      startDate: event.startDate.toString(),
      endDate: event.endDate.toString(),
      description: event.description,
      location: event.location,
      url: vevent.getFirstPropertyValue('url')?.toString() || undefined
    };

  } catch (error) {
    console.error('[APPLE CALENDAR] Error updating event:', error);
    throw error;
  }
};

/**
 * Delete an event
 */
/**
 * Parse the new caldav:: ID format to extract the object URL and UID.
 * Format: "caldav::<objectUrl>::<uid>[::occurrence-iso]"
 * Returns null for legacy format IDs.
 */
function parseCaldavId(eventId: string): { objectUrl: string; uid: string } | null {
  if (!eventId.startsWith('caldav::')) return null;
  const parts = eventId.split('::');
  if (parts.length >= 3) {
    return { objectUrl: parts[1], uid: parts[2] };
  }
  return null;
}

export const deleteEvent = async (
  email: string,
  appSpecificPassword: string,
  calendarUrl: string,
  eventId: string
): Promise<void> => {
  try {
    const client = createClient(email, appSpecificPassword);
    await client.login();

    // Try O(1) path first with new caldav:: ID format
    const parsed = parseCaldavId(eventId);
    if (parsed) {
      await client.deleteObject({
        url: parsed.objectUrl,
        etag: undefined,
      } as any);
      console.log('[APPLE CALENDAR] Event deleted via direct URL (O(1))');
      return;
    }

    // Legacy fallback: O(n) scan for old-format IDs
    const calendars = await client.fetchCalendars();
    const targetCalendar = calendars.find(c => c.url === calendarUrl);

    if (!targetCalendar) {
      throw new Error(`Calendar not found: ${calendarUrl}`);
    }

    const uid = eventId.split('-')[0];

    const allObjects = await client.fetchCalendarObjects({
      calendar: targetCalendar,
    });

    const targetObject = allObjects.find(obj => {
      if (obj.data) {
        return obj.data.includes(`UID:${uid}`);
      }
      return obj.url.includes(uid);
    });

    if (!targetObject) {
      console.warn('[APPLE CALENDAR] Event to delete not found, maybe already deleted?');
      return;
    }

    await client.deleteObject({
      url: targetObject.url,
      etag: targetObject.etag
    } as any);

    console.log('[APPLE CALENDAR] Event deleted via legacy scan');

  } catch (error) {
    console.error('[APPLE CALENDAR] Error deleting event:', error);
    throw error;
  }
};

