<?php

declare(strict_types=1);

namespace PpPhp\Core\Calendar;

/**
 * CalendarIcs — minimal iCalendar (RFC 5545) export/import so the calendar
 * interoperates with Google Calendar, Outlook, Apple Calendar, etc.
 *
 * export(): events[] → a VCALENDAR string (one VEVENT each).
 * parse():  a VCALENDAR string → event arrays shaped for CalendarService::createEvent.
 * Line folding, escaping and both DATE and DATE-TIME value types are handled.
 */
final class CalendarIcs
{
    /** @param list<array<string,mixed>> $events rows with title/description/location/starts_at/ends_at/all_day/rrule */
    public static function export(array $events, string $calName = 'Calendar'): string
    {
        $lines = [
            'BEGIN:VCALENDAR',
            'VERSION:2.0',
            'PRODID:-//PPPHP//Calendar//EN',
            'CALSCALE:GREGORIAN',
            'X-WR-CALNAME:' . self::esc($calName),
        ];
        foreach ($events as $e) {
            $allDay = (int) ($e['all_day'] ?? $e['allDay'] ?? 0) === 1;
            $start = (string) ($e['starts_at'] ?? $e['start'] ?? '');
            $end = (string) ($e['ends_at'] ?? $e['end'] ?? '');
            if ($start === '') continue;
            $uid = ($e['id'] ?? uniqid()) . '@ppphp';
            $lines[] = 'BEGIN:VEVENT';
            $lines[] = 'UID:' . $uid;
            $lines[] = 'DTSTAMP:' . gmdate('Ymd\THis\Z');
            $lines[] = 'DTSTART' . self::dt($start, $allDay);
            if ($end !== '') $lines[] = 'DTEND' . self::dt($end, $allDay);
            $lines[] = 'SUMMARY:' . self::esc((string) ($e['title'] ?? ''));
            if (($e['description'] ?? '') !== '') $lines[] = 'DESCRIPTION:' . self::esc((string) $e['description']);
            if (($e['location'] ?? '') !== '') $lines[] = 'LOCATION:' . self::esc((string) $e['location']);
            if (($e['rrule'] ?? '') !== '') $lines[] = 'RRULE:' . preg_replace('/^RRULE:/i', '', (string) $e['rrule']);
            $lines[] = 'END:VEVENT';
        }
        $lines[] = 'END:VCALENDAR';
        return implode("\r\n", array_map([self::class, 'fold'], $lines)) . "\r\n";
    }

    /** @return list<array<string,mixed>> event arrays for CalendarService::createEvent */
    public static function parse(string $ics): array
    {
        $ics = str_replace(["\r\n", "\r"], "\n", $ics);
        // Unfold continued lines (leading space/tab).
        $ics = preg_replace('/\n[ \t]/', '', $ics);
        $events = [];
        $cur = null;
        foreach (explode("\n", $ics) as $line) {
            $line = rtrim($line, "\r");
            if ($line === 'BEGIN:VEVENT') { $cur = []; continue; }
            if ($line === 'END:VEVENT') { if ($cur && !empty($cur['start'])) $events[] = $cur; $cur = null; continue; }
            if ($cur === null || strpos($line, ':') === false) continue;
            [$name, $value] = explode(':', $line, 2);
            $params = explode(';', $name);
            $key = strtoupper($params[0]);
            $isDate = stripos($name, 'VALUE=DATE') !== false && stripos($name, 'DATE-TIME') === false;
            switch ($key) {
                case 'SUMMARY':     $cur['title'] = self::unesc($value); break;
                case 'DESCRIPTION': $cur['description'] = self::unesc($value); break;
                case 'LOCATION':    $cur['location'] = self::unesc($value); break;
                case 'DTSTART':     $cur['start'] = self::parseDt($value); $cur['allDay'] = $isDate || strlen(trim($value)) === 8; break;
                case 'DTEND':       $cur['end'] = self::parseDt($value); break;
                case 'RRULE':       $cur['rrule'] = 'RRULE:' . trim($value); break;
            }
        }
        return $events;
    }

    private static function dt(string $mysqlDt, bool $allDay): string
    {
        $ts = strtotime($mysqlDt) ?: time();
        return $allDay ? (';VALUE=DATE:' . date('Ymd', $ts)) : (':' . date('Ymd\THis', $ts));
    }

    private static function parseDt(string $v): string
    {
        $v = trim($v);
        if (preg_match('/^(\d{4})(\d{2})(\d{2})(?:T(\d{2})(\d{2})(\d{2}))?(Z)?$/', $v, $m)) {
            $y = $m[1]; $mo = $m[2]; $d = $m[3];
            if (!empty($m[4])) {
                $s = "$y-$mo-$d {$m[4]}:{$m[5]}:{$m[6]}";
                if (!empty($m[7])) { $ts = strtotime($s . ' UTC'); return date('Y-m-d H:i:s', $ts ?: time()); }
                return $s;
            }
            return "$y-$mo-$d";
        }
        $ts = strtotime($v);
        return $ts ? date('Y-m-d H:i:s', $ts) : $v;
    }

    private static function esc(string $s): string
    {
        return str_replace(["\\", "\n", ",", ";"], ["\\\\", "\\n", "\\,", "\\;"], $s);
    }
    private static function unesc(string $s): string
    {
        return str_replace(["\\n", "\\N", "\\,", "\\;", "\\\\"], ["\n", "\n", ",", ";", "\\"], $s);
    }

    /** Fold lines to 75 octets per RFC 5545. */
    private static function fold(string $line): string
    {
        if (strlen($line) <= 73) return $line;
        $out = ''; $cur = '';
        foreach (str_split($line, 1) as $ch) {
            if (strlen($cur) >= 73) { $out .= $cur . "\r\n "; $cur = ''; }
            $cur .= $ch;
        }
        return $out . $cur;
    }
}
