/**
 * Sync factor — Node worker thread.
 *
 * Hosts the entire network/sync backend (`ImapManager`: IMAP + Gmail sync,
 * body prefetch, the outbox SMTP/IMAP-append drain, IDLE) on its OWN thread,
 * with its OWN write connection to the SQLite file. The main thread keeps the
 * IPC pump and fast local reads (read-worker); it drives this worker through
 * a proxy (`sync-worker-client.ts`) and receives ImapManager's events back
 * over the bus.
 *
 * **Why.** Until now sync ran on the main event loop. A hung Dovecot FETCH, a
 * 30 MB body pull, a dead-network reconnect storm, a 5-minute outbox hang —
 * any of them froze the main loop, which starved the IPC relay, so clicks and
 * the message list timed out even though the read-worker had the answer ready.
 * On its own thread, sync can stall or thrash forever and the UI never feels
 * it (Bob 2026-06-13: "shouldn't it be a worker for any such operation").
 *
 * **DB.** Opens with `{skipMigrations:true}` — main built the schema before we
 * spawned. WAL + busy_timeout let this writer and the main thread's
 * local-first writer coexist; the read-worker reads concurrently.
 *
 * **RPC.** One generic handler: the proxy sends `{method, args}` and we invoke
 * `imapManager[method](...args)`. Return values must be structured-clone-safe
 * (they are — JSON-ish status objects, tokens, bodies-as-strings). Methods
 * that hand back live objects (createPublicClient) are NOT called this way;
 * the service uses dedicated server-side folder-op methods instead.
 *
 * **Events.** We wrap `imapManager.emit` to also `bus.publish("sync:event")`,
 * so every event ImapManager/Reconciler raise on this thread reaches the main
 * proxy, which re-emits them locally for bin's WebView forwarders.
 */
import { parentPort } from "node:worker_threads";
import * as fs from "node:fs";
import { join } from "node:path";
import { WorkerBus } from "@bobfrankston/mailx-bus";
import { MailxDB, FileMessageStore, Store } from "@bobfrankston/mailx-store";
import { ImapManager } from "@bobfrankston/mailx-imap";
import { NodeTcpTransport } from "@bobfrankston/node-tcp-transport";
if (!parentPort) {
    throw new Error("sync-worker: must be spawned as a worker, parentPort is null");
}
let imapManager = null;
let initialized = false;
parentPort.once("message", async (msg) => {
    if (msg?.kind !== "init") {
        parentPort.postMessage({ kind: "init-error", error: `expected "init", got ${JSON.stringify(msg)}` });
        return;
    }
    try {
        redirectWorkerConsole(msg.configDir);
        const db = new MailxDB(msg.configDir, { skipMigrations: true });
        const store = new Store(db, new FileMessageStore(msg.storePath));
        imapManager = new ImapManager(store, () => new NodeTcpTransport());
        registerHandlers(imapManager);
        initialized = true;
        parentPort.postMessage({ kind: "init-ok" });
    }
    catch (e) {
        parentPort.postMessage({ kind: "init-error", error: e?.message || String(e) });
    }
});
/** Route this worker's console into the daemon's daily log file.
 *
 *  The main thread monkey-patches ITS console to the log stream
 *  (bin/mailx.ts), but a worker_thread's console writes to the process's
 *  real stdout — which is `stdio:"ignore"` in detached daemon mode. Every
 *  [outbox]/[smtp]/[sent-sweep] line from the sync worker therefore
 *  vanished, which blinded the 2026-07-01 resend-loop investigation ("no
 *  [outbox] lines in the log" while the loop appended a duplicate to Sent
 *  every 5 minutes). Same daily file, `[w]` tag; append-mode writes
 *  interleave safely with the main thread's stream. */
function redirectWorkerConsole(configDir) {
    try {
        const logDir = join(configDir, "logs");
        const pad2 = (n) => n.toString().padStart(2, "0");
        const pad3 = (n) => n.toString().padStart(3, "0");
        const localDate = () => {
            const d = new Date();
            return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
        };
        const ts = () => {
            const d = new Date();
            return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}.${pad3(d.getMilliseconds())}`;
        };
        let curDate = localDate();
        let stream = fs.createWriteStream(join(logDir, `rmfmail-${curDate}.log`), { flags: "a" });
        const write = (level, a) => {
            // Roll to the new day's file when the date flips (matches the
            // daemon's per-day naming; the daemon itself re-opens on restart).
            const d = localDate();
            if (d !== curDate) {
                try {
                    stream.end();
                }
                catch { /* ignore */ }
                curDate = d;
                stream = fs.createWriteStream(join(logDir, `rmfmail-${curDate}.log`), { flags: "a" });
            }
            try {
                stream.write(`${ts()} [w]${level} ${a.map(x => typeof x === "string" ? x : String(x)).join(" ")}\n`);
            }
            catch { /* ignore */ }
        };
        console.log = (...a) => write("", a);
        console.error = (...a) => write(" ERROR", a);
        console.warn = (...a) => write(" WARN", a);
    }
    catch { /* logging must never break the worker */ }
}
function registerHandlers(mgr) {
    const bus = new WorkerBus(parentPort);
    // Forward every ImapManager/Reconciler event to the main proxy. Wrap emit
    // so we don't have to enumerate the event list (it drifts); the proxy
    // re-emits locally and bin's forwarders push to the WebView.
    const origEmit = mgr.emit.bind(mgr);
    mgr.emit = (event, ...args) => {
        try {
            bus.publish("sync:event", { event, args });
        }
        catch { /* bus down — local listeners still fire */ }
        return origEmit(event, ...args);
    };
    // Generic method dispatch. Anything the main thread used to call on
    // `imapManager` now arrives here.
    bus.register("sync:call", async ({ method, args }) => {
        const fn = mgr[method];
        if (typeof fn !== "function") {
            throw new Error(`sync-worker: ImapManager has no method '${method}'`);
        }
        return await fn.apply(mgr, args || []);
    });
}
// Pre-init guard: a request that races construction gets a clear error, not a hang.
parentPort.on("message", (msg) => {
    if (!initialized && msg && typeof msg === "object" && msg.kind === "request") {
        parentPort.postMessage({
            kind: "reply",
            id: msg.id,
            error: "sync-worker: not initialized — send {kind:'init'} first",
        });
    }
});
//# sourceMappingURL=sync-worker.js.map