#!/usr/bin/env node
// RFC 5545 iCal generator for linkedin-studio plugin
// Import: import { generateIcal, generateIcalFromQueue, writeIcalFile } from './ical-generator.mjs';
// Standalone: node ical-generator.mjs --from-queue --output path/to/schedule.ics

import { writeFileSync, readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const CRLF = '\r\n';

/**
 * Escape text values per RFC 5545 Section 3.3.11.
 * Backslashes first (to avoid double-escaping), then semicolons, commas, newlines.
 */
function escapeText(str) {
  if (!str) return '';
  return str
    .replace(/\\/g, '\\\\')
    .replace(/;/g, '\;')
    .replace(/,/g, '\\,')
    .replace(/\n/g, '\\n');
}

/**
 * Fold a content line per RFC 5545 Section 3.1.
 * Lines MUST NOT be longer than 75 octets. Long lines are folded by
 * inserting a CRLF followed by a single whitespace character (space).
 */
function foldLine(line) {
  const maxOctets = 75;
  if (Buffer.byteLength(line, 'utf-8') <= maxOctets) return line;

  const parts = [];
  let remaining = line;
  let isFirst = true;

  while (Buffer.byteLength(remaining, 'utf-8') > maxOctets) {
    // Find the split point: max octets for first line, max-1 for continuations (leading space)
    const limit = isFirst ? maxOctets : maxOctets - 1;
    let splitAt = 0;
    let octetCount = 0;

    for (let i = 0; i < remaining.length; i++) {
      const charOctets = Buffer.byteLength(remaining[i], 'utf-8');
      if (octetCount + charOctets > limit) break;
      octetCount += charOctets;
      splitAt = i + 1;
    }

    parts.push((isFirst ? '' : ' ') + remaining.slice(0, splitAt));
    remaining = remaining.slice(splitAt);
    isFirst = false;
  }

  if (remaining.length > 0) {
    parts.push((isFirst ? '' : ' ') + remaining);
  }

  return parts.join(CRLF);
}

/**
 * Format a Date as iCal UTC timestamp: YYYYMMDDTHHmmssZ
 */
function formatUtcTimestamp(date) {
  const d = date || new Date();
  const pad = (n) => String(n).padStart(2, '0');
  return `${d.getUTCFullYear()}${pad(d.getUTCMonth() + 1)}${pad(d.getUTCDate())}T${pad(d.getUTCHours())}${pad(d.getUTCMinutes())}${pad(d.getUTCSeconds())}Z`;
}

/**
 * Format date + time as iCal local datetime: YYYYMMDDTHHMMSS
 */
function formatLocalDatetime(dateStr, timeStr) {
  const [y, m, d] = dateStr.split('-');
  const [h, min] = (timeStr || '09:00').split(':');
  return `${y}${m}${d}T${h}${min}00`;
}

/**
 * Add minutes to a time string (HH:MM), returns new time as HHMMSS for iCal.
 * Handles day overflow simply by capping at 23:59.
 */
function addMinutes(dateStr, timeStr, minutes) {
  const [y, m, d] = dateStr.split('-').map(Number);
  const [h, min] = (timeStr || '09:00').split(':').map(Number);
  const totalMin = h * 60 + min + minutes;
  const newH = Math.min(Math.floor(totalMin / 60), 23);
  const newMin = totalMin % 60;
  const pad = (n) => String(n).padStart(2, '0');
  return `${pad(y)}${pad(m)}${pad(d)}T${pad(newH)}${pad(newMin)}00`;
}

/**
 * Generate a minimal VTIMEZONE component.
 * Full Olson TZ database support is out of scope; we provide the structural
 * component so calendar apps recognize the TZID reference.
 */
function generateVtimezone(timezone) {
  const lines = [
    'BEGIN:VTIMEZONE',
    `TZID:${timezone}`,
    'BEGIN:STANDARD',
    `DTSTART:19701025T030000`,
    'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10',
    `TZOFFSETFROM:+0200`,
    `TZOFFSETTO:+0100`,
    `TZNAME:CET`,
    'END:STANDARD',
    'BEGIN:DAYLIGHT',
    `DTSTART:19700329T020000`,
    'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3',
    `TZOFFSETFROM:+0100`,
    `TZOFFSETTO:+0200`,
    `TZNAME:CEST`,
    'END:DAYLIGHT',
    'END:VTIMEZONE',
  ];
  return lines;
}

/**
 * Generate RFC 5545 compliant iCal string from event objects.
 *
 * @param {Array<{id, title, description, date, time, duration}>} events
 * @param {Object} [options]
 * @param {string} [options.timezone='Europe/Oslo'] - TZID for DTSTART/DTEND
 * @returns {string} Valid .ics file content with CRLF line endings
 */
export function generateIcal(events, options = {}) {
  const tz = options.timezone || 'Europe/Oslo';
  const now = formatUtcTimestamp(new Date());

  const lines = [
    'BEGIN:VCALENDAR',
    'VERSION:2.0',
    'PRODID:-//linkedin-studio//EN',
    'CALSCALE:GREGORIAN',
    'METHOD:PUBLISH',
  ];

  // Add VTIMEZONE if we have events
  if (events.length > 0) {
    lines.push(...generateVtimezone(tz));
  }

  for (const event of events) {
    const duration = event.duration || 30;
    const dtstart = formatLocalDatetime(event.date, event.time);
    const dtend = addMinutes(event.date, event.time, duration);

    lines.push(
      'BEGIN:VEVENT',
      `UID:${event.id}@linkedin-studio`,
      `DTSTAMP:${now}`,
      `DTSTART;TZID=${tz}:${dtstart}`,
      `DTEND;TZID=${tz}:${dtend}`,
      `SUMMARY:${escapeText(event.title)}`,
      `DESCRIPTION:${escapeText(event.description || '')}`,
      'BEGIN:VALARM',
      'TRIGGER:-PT15M',
      'ACTION:DISPLAY',
      `DESCRIPTION:Reminder: ${escapeText(event.title)}`,
      'END:VALARM',
      'END:VEVENT',
    );
  }

  lines.push('END:VCALENDAR');

  // Apply line folding and join with CRLF
  return lines.map(foldLine).join(CRLF) + CRLF;
}

/**
 * Transform queue entries (from queue-manager.mjs) into event format.
 *
 * @param {Array<{id, draft_path, scheduled_date, scheduled_time, pillar, format, hook_preview}>} queueEntries
 * @returns {Array<{id, title, description, date, time, duration}>}
 */
export function generateIcalFromQueue(queueEntries) {
  return queueEntries.map(entry => ({
    id: entry.id,
    title: `LinkedIn: ${entry.hook_preview || 'Scheduled post'}`,
    description: `Pillar: ${entry.pillar || '?'} | Format: ${entry.format || '?'} | Draft: ${entry.draft_path || '?'}`,
    date: entry.scheduled_date,
    time: entry.scheduled_time || '09:00',
    duration: 30,
  }));
}

/**
 * Write .ics file to disk.
 *
 * @param {string} outputPath - Path to write the .ics file
 * @param {Array} events - Event objects (from generateIcalFromQueue or direct)
 * @param {Object} [options] - Options passed to generateIcal
 */
export function writeIcalFile(outputPath, events, options) {
  const ical = generateIcal(events, options);
  writeFileSync(outputPath, ical, 'utf-8');
  return outputPath;
}

// Standalone CLI mode
if (process.argv[1] && process.argv[1].endsWith('ical-generator.mjs')) {
  const args = process.argv.slice(2);
  const fromQueue = args.includes('--from-queue');
  const outputIdx = args.indexOf('--output');
  const outputPath = outputIdx >= 0 ? args[outputIdx + 1] : null;

  if (!fromQueue || !outputPath) {
    console.log('Usage: node ical-generator.mjs --from-queue --output path/to/schedule.ics');
    process.exit(1);
  }

  // Dynamic import to avoid circular dep issues
  const { queueUpcoming } = await import('./queue-manager.mjs');
  const upcoming = queueUpcoming(14);

  if (upcoming.length === 0) {
    console.log('No upcoming scheduled posts in queue.');
    process.exit(0);
  }

  const events = generateIcalFromQueue(upcoming);
  writeIcalFile(outputPath, events);
  console.log(`Calendar file: ${outputPath} (${events.length} events)`);
}
