import type { CalendarEvent } from "@/lib/jmap/types";

const MAX_LINE_OCTETS = 74;

function foldLine(line: string): string {
  if (line.length <= MAX_LINE_OCTETS) return line;
  const chunks: string[] = [line.slice(0, MAX_LINE_OCTETS)];
  let pos = MAX_LINE_OCTETS;
  while (pos < line.length) {
    chunks.push(" " + line.slice(pos, pos + MAX_LINE_OCTETS - 1));
    pos += MAX_LINE_OCTETS - 1;
  }
  return chunks.join("\r\n");
}

// RFC 5545 §3.3.11 - escape backslash, semicolon, comma, and newline in TEXT values.
function escapeText(value: string): string {
  return value
    .replace(/\\/g, "\\\\")
    .replace(/;/g, "\\;")
    .replace(/,/g, "\\,")
    .replace(/\r\n|\r|\n/g, "\\n");
}

function stripDateSeparators(value: string): string {
  return value.replace(/[-:]/g, "").replace(/\.\d{3}/, "");
}

function dateOnly(value: string): string {
  return value.replace(/-/g, "").substring(0, 8);
}

function formatNow(): string {
  return stripDateSeparators(new Date().toISOString().replace(/\.\d{3}/, ""));
}

function pushDateProp(
  lines: string[],
  prop: "DTSTART" | "DTEND",
  value: string,
  showWithoutTime: boolean,
  tz?: string | null,
): void {
  if (showWithoutTime) {
    lines.push(`${prop};VALUE=DATE:${dateOnly(value)}`);
    return;
  }
  if (value.endsWith("Z")) {
    lines.push(`${prop}:${stripDateSeparators(value)}`);
    return;
  }
  const basic = stripDateSeparators(value);
  if (tz) {
    lines.push(`${prop};TZID=${tz}:${basic}`);
  } else {
    lines.push(`${prop}:${basic}`);
  }
}

function pushFrequencyRule(lines: string[], event: CalendarEvent): void {
  const rule = event.recurrenceRules?.[0];
  if (!rule) return;
  const parts: string[] = [`FREQ=${rule.frequency.toUpperCase()}`];
  if (rule.interval && rule.interval > 1) parts.push(`INTERVAL=${rule.interval}`);
  if (rule.count != null) parts.push(`COUNT=${rule.count}`);
  if (rule.until) parts.push(`UNTIL=${stripDateSeparators(rule.until)}`);
  if (rule.byDay?.length) {
    const days = rule.byDay
      .map((d) => `${d.nthOfPeriod ?? ""}${d.day.toUpperCase()}`)
      .join(",");
    parts.push(`BYDAY=${days}`);
  }
  if (rule.byMonthDay?.length) parts.push(`BYMONTHDAY=${rule.byMonthDay.join(",")}`);
  if (rule.byMonth?.length) parts.push(`BYMONTH=${rule.byMonth.join(",")}`);
  lines.push(`RRULE:${parts.join(";")}`);
}

function pushAlerts(lines: string[], event: CalendarEvent): void {
  if (!event.alerts) return;
  for (const alert of Object.values(event.alerts)) {
    const trigger = alert.trigger;
    if (!trigger) continue;
    lines.push("BEGIN:VALARM");
    lines.push(`ACTION:${(alert.action || "display").toUpperCase()}`);
    if (trigger["@type"] === "OffsetTrigger") {
      const related = trigger.relativeTo === "end" ? ";RELATED=END" : "";
      lines.push(`TRIGGER${related}:${trigger.offset}`);
    } else if (trigger["@type"] === "AbsoluteTrigger") {
      lines.push(`TRIGGER;VALUE=DATE-TIME:${stripDateSeparators(trigger.when)}`);
    }
    lines.push(`DESCRIPTION:${escapeText(event.title || "Reminder")}`);
    lines.push("END:VALARM");
  }
}

export function eventToICS(event: CalendarEvent): string {
  const now = formatNow();
  const lines: string[] = [
    "BEGIN:VCALENDAR",
    "PRODID:-//JMAP-Webmail//EN",
    "VERSION:2.0",
    "CALSCALE:GREGORIAN",
    "METHOD:PUBLISH",
    "BEGIN:VEVENT",
    `UID:${event.uid}`,
    `DTSTAMP:${now}`,
  ];

  if (event.created) lines.push(`CREATED:${stripDateSeparators(event.created)}`);
  if (event.updated) lines.push(`LAST-MODIFIED:${stripDateSeparators(event.updated)}`);
  if (event.sequence != null) lines.push(`SEQUENCE:${event.sequence}`);

  if (event.start) {
    pushDateProp(lines, "DTSTART", event.start, event.showWithoutTime, event.timeZone);
  }
  if (event.utcEnd) {
    pushDateProp(lines, "DTEND", event.utcEnd, event.showWithoutTime, event.timeZone);
  } else if (event.duration) {
    lines.push(`DURATION:${event.duration}`);
  }

  if (event.title) lines.push(`SUMMARY:${escapeText(event.title)}`);
  if (event.description) lines.push(`DESCRIPTION:${escapeText(event.description)}`);
  if (event.status) lines.push(`STATUS:${event.status.toUpperCase()}`);
  if (event.privacy) lines.push(`CLASS:${event.privacy.toUpperCase()}`);
  if (event.freeBusyStatus) {
    lines.push(`TRANSP:${event.freeBusyStatus === "free" ? "TRANSPARENT" : "OPAQUE"}`);
  }

  if (event.locations) {
    const first = Object.values(event.locations)[0];
    if (first?.name) lines.push(`LOCATION:${escapeText(first.name)}`);
  }
  if (event.virtualLocations) {
    for (const loc of Object.values(event.virtualLocations)) {
      if (loc.uri) lines.push(`URL:${loc.uri}`);
    }
  }

  if (event.participants) {
    const organizer = Object.values(event.participants).find((p) => p.roles?.owner);
    if (organizer) {
      const email = organizer.email || organizer.sendTo?.imip?.replace("mailto:", "");
      if (email) {
        const cn = organizer.name ? `;CN=${escapeText(organizer.name)}` : "";
        lines.push(`ORGANIZER${cn}:mailto:${email}`);
      }
    }
    for (const p of Object.values(event.participants)) {
      if (p.roles?.owner) continue;
      const email = p.email || p.sendTo?.imip?.replace("mailto:", "");
      if (!email) continue;
      const cn = p.name ? `;CN=${escapeText(p.name)}` : "";
      const partstat = p.participationStatus
        ? `;PARTSTAT=${p.participationStatus.toUpperCase()}`
        : ";PARTSTAT=NEEDS-ACTION";
      const rsvp = p.expectReply ? ";RSVP=TRUE" : "";
      lines.push(`ATTENDEE${cn}${partstat}${rsvp}:mailto:${email}`);
    }
  }

  pushFrequencyRule(lines, event);
  pushAlerts(lines, event);

  lines.push("END:VEVENT");
  lines.push("END:VCALENDAR");

  return lines.map(foldLine).join("\r\n") + "\r\n";
}

function sanitizeFilename(name: string): string {
  const cleaned = name.replace(/[\\/:*?"<>|]/g, "_").trim();
  return cleaned || "event";
}

export function downloadEventICS(event: CalendarEvent): void {
  const ics = eventToICS(event);
  const blob = new Blob([ics], { type: "text/calendar;charset=utf-8" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = `${sanitizeFilename(event.title || "event")}.ics`;
  document.body.appendChild(a);
  a.click();
  a.remove();
  setTimeout(() => URL.revokeObjectURL(url), 0);
}
