/**
 * mailto: URL handling per RFC 6068.
 *
 * Click flow when mailx is registered as the OS-level mailto handler:
 *
 *   1. User clicks mailto link in browser/Word/Outlook.
 *   2. Windows runs the registered command — `node mailx.js --mailto "%1"`.
 *   3. CLI calls writePendingMailto() with the parsed fields.
 *   4. If a daemon is running, it picks up the file via fs.watch and emits
 *      `openMailto` to the WebView; the client populates compose.
 *   5. If no daemon is running, the CLI spawns one; the daemon scans the
 *      pending file at startup before reaching the event loop.
 *
 * The file-drop indirection avoids having to hold an IPC socket open in
 * the daemon — file-watch is enough for the click rate (one click = one
 * file write) and works the same way the outbox queue does.
 */

import fs from "node:fs";
import path from "node:path";

export interface MailtoData {
    to: string[];
    cc: string[];
    bcc: string[];
    subject: string;
    body: string;
    inReplyTo: string;
    /** Wall-clock when the file was written; lets the daemon ignore stale
     *  files left behind by a crashed CLI invocation. */
    writtenAt: number;
}

/** Parse a mailto: URL per RFC 6068. Returns the parsed fields with empty
 *  defaults for anything missing. Throws if the input doesn't start with
 *  `mailto:` so the CLI can surface a clear error. */
export function parseMailto(url: string): MailtoData {
    const trimmed = (url || "").trim();
    if (!/^mailto:/i.test(trimmed)) {
        throw new Error(`Not a mailto: URL — got ${trimmed.substring(0, 40)}`);
    }
    // Split path and query. RFC 6068 allows commas in the path (multiple
    // recipients) and the standard URL encoding for both halves.
    const noScheme = trimmed.replace(/^mailto:/i, "");
    const qIdx = noScheme.indexOf("?");
    const pathPart = qIdx === -1 ? noScheme : noScheme.substring(0, qIdx);
    const queryPart = qIdx === -1 ? ""        : noScheme.substring(qIdx + 1);

    const decode = (s: string): string => {
        try { return decodeURIComponent(s.replace(/\+/g, " ")); }
        catch { return s; } // tolerate malformed %XX — better than crashing
    };

    const splitAddrs = (s: string): string[] =>
        s.split(",").map(a => decode(a).trim()).filter(Boolean);

    const out: MailtoData = {
        to: pathPart ? splitAddrs(pathPart) : [],
        cc: [], bcc: [],
        subject: "", body: "", inReplyTo: "",
        writtenAt: Date.now(),
    };

    if (!queryPart) return out;
    for (const pair of queryPart.split("&")) {
        const eq = pair.indexOf("=");
        if (eq === -1) continue;
        const key = pair.substring(0, eq).toLowerCase();
        const val = decode(pair.substring(eq + 1));
        switch (key) {
            case "to":          out.to.push(...splitAddrs(pair.substring(eq + 1))); break;
            case "cc":          out.cc.push(...splitAddrs(pair.substring(eq + 1))); break;
            case "bcc":         out.bcc.push(...splitAddrs(pair.substring(eq + 1))); break;
            case "subject":     out.subject = val; break;
            case "body":        out.body = val; break;
            case "in-reply-to": out.inReplyTo = val; break;
            // RFC 6068 also lists `references` and arbitrary headers; mailx
            // doesn't propagate those today (compose UI doesn't surface
            // refs from a fresh-link click).
        }
    }
    return out;
}

export function getPendingMailtoFile(): string {
    const home = process.env.USERPROFILE || process.env.HOME || ".";
    return path.join(home, ".rmfmail", "pending-mailto.json");
}

/** Write the pending-mailto file atomically. Atomic-rename so the daemon's
 *  fs.watch never sees a partial write. */
export function writePendingMailto(data: MailtoData): void {
    const target = getPendingMailtoFile();
    fs.mkdirSync(path.dirname(target), { recursive: true });
    const tmp = target + ".tmp";
    fs.writeFileSync(tmp, JSON.stringify(data, null, 2));
    fs.renameSync(tmp, target);
}

/** Read + delete the pending-mailto file. One-shot — caller acts on the
 *  result, file is gone whether or not the action succeeded. Returns null
 *  if no file present (or unparseable / older than 5 min). */
export function consumePendingMailto(): MailtoData | null {
    const target = getPendingMailtoFile();
    let raw: string;
    try { raw = fs.readFileSync(target, "utf-8"); }
    catch { return null; }
    // Always unlink, even on parse failure — a corrupt file shouldn't wedge
    // the next click.
    try { fs.unlinkSync(target); } catch { /* */ }
    let parsed: MailtoData;
    try { parsed = JSON.parse(raw) as MailtoData; }
    catch { return null; }
    // Tolerate older client writes that skipped writtenAt; treat as fresh.
    const age = Date.now() - (parsed.writtenAt || Date.now());
    if (age > 5 * 60_000) {
        // Stale file from a crashed CLI invocation that wrote it but never
        // got the daemon to start. Drop it; user can click again.
        return null;
    }
    return parsed;
}
