import { sha256Hex } from './crypto'; function escapeIcsText(s: string): string { return String(s) .replace(/\\/g, '\\\\') .replace(/\n/g, '\\n') .replace(/\r/g, '') .replace(/,/g, '\\,') .replace(/;/g, '\\;'); } function foldIcsLine(line: string): string { // 75 octetos; para simplicidad contamos caracteres (UTF-8 simple en nuestro caso) const max = 75; if (line.length <= max) return line; const parts: string[] = []; let i = 0; while (i < line.length) { const chunk = line.slice(i, i + max); parts.push(i === 0 ? chunk : ' ' + chunk); i += max; } return parts.join('\r\n'); } function padTaskId(id: number, width: number = 4): string { const s = String(Math.max(0, Math.floor(id))); if (s.length >= width) return s; return '0'.repeat(width - s.length) + s; } function ymdToBasic(ymd: string): string { // Espera YYYY-MM-DD const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(ymd); if (!m) return ''; return `${m[1]}${m[2]}${m[3]}`; } function addDays(ymd: string, days: number): string { const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(ymd); if (!m) return ymd; const d = new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]))); d.setUTCDate(d.getUTCDate() + days); const yyyy = String(d.getUTCFullYear()).padStart(4, '0'); const mm = String(d.getUTCMonth() + 1).padStart(2, '0'); const dd = String(d.getUTCDate()).padStart(2, '0'); return `${yyyy}-${mm}-${dd}`; } export type IcsEvent = { id: number; description: string; due_date: string; // YYYY-MM-DD group_name?: string | null; prefix?: string; // ej: "T" para [T0123] }; export async function buildIcsCalendar(title: string, events: IcsEvent[]): Promise<{ body: string; etag: string }> { const lines: string[] = []; lines.push('BEGIN:VCALENDAR'); lines.push('VERSION:2.0'); lines.push('PRODID:-//TaskWhatsApp//Calendar//ES'); lines.push('CALSCALE:GREGORIAN'); lines.push('METHOD:PUBLISH'); lines.push(`X-WR-CALNAME:${escapeIcsText(title)}`); lines.push('X-WR-TIMEZONE:UTC'); for (const ev of events) { const idPad = padTaskId(ev.id); const summary = `[${ev.prefix || 'T'}${idPad}] ${ev.description}`; const dtStart = ymdToBasic(ev.due_date); const dtEnd = ymdToBasic(addDays(ev.due_date, 1)); const uid = `task-${ev.id}@tw`; lines.push('BEGIN:VEVENT'); lines.push(foldIcsLine(`UID:${uid}`)); lines.push(foldIcsLine(`SUMMARY:${escapeIcsText(summary)}`)); lines.push(`DTSTART;VALUE=DATE:${dtStart}`); lines.push(`DTEND;VALUE=DATE:${dtEnd}`); if (ev.group_name) { lines.push(foldIcsLine(`CATEGORIES:${escapeIcsText(ev.group_name || '')}`)); } lines.push('END:VEVENT'); } lines.push('END:VCALENDAR'); const body = lines.join('\r\n') + '\r\n'; const etag = await sha256Hex(body); return { body, etag: `W/"${etag}"` }; }