/**
 * Main-thread client for the sync factor worker.
 *
 * Spawns `sync-worker.js`, performs the init handshake, and returns a
 * `SyncProxy` that is a DROP-IN for `ImapManager` from the main thread's point
 * of view: it's an EventEmitter (so `bin`'s `.on(...)` forwarders and the
 * service's `.emit(...)` keep working locally), and any *other* method call is
 * forwarded to the worker over the bus as `{method, args}` and returns the
 * worker's result as a Promise.
 *
 * Two method names are handled locally instead of being forwarded:
 *   - the EventEmitter surface (on/once/off/emit/…) — local event routing.
 *   - getBodyStore() — returns a local FileMessageStore (a path wrapper; the
 *     bytes live on disk, so main can read them without crossing the bus).
 *
 * Worker → main events arrive on the bus topic `sync:event` and are re-emitted
 * on the local EventEmitter, so a `syncProgress` raised inside the worker fires
 * bin's listener exactly as before.
 */

import { Worker } from "node:worker_threads";
import { EventEmitter } from "node:events";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import { WorkerBus, type MessagePortLike } from "@bobfrankston/mailx-bus";
import { FileMessageStore } from "@bobfrankston/mailx-store";
import type { ImapManager } from "@bobfrankston/mailx-imap";

export interface SpawnedSyncWorker {
    /** Drop-in for ImapManager. Method calls return Promises (forwarded to the
     *  worker); event methods are local. */
    proxy: ImapManager;
    worker: Worker;
    close(): Promise<void>;
}

const EMITTER_METHODS = new Set([
    "on", "once", "off", "addListener", "removeListener", "removeAllListeners",
    "emit", "listeners", "rawListeners", "listenerCount", "eventNames",
    "prependListener", "prependOnceListener", "setMaxListeners", "getMaxListeners",
]);

export async function spawnSyncWorker(opts: { configDir: string; storePath: string }): Promise<SpawnedSyncWorker> {
    const here = dirname(fileURLToPath(import.meta.url));
    const workerPath = join(here, "sync-worker.js");
    const worker = new Worker(workerPath);
    worker.unref();

    await new Promise<void>((resolve, reject) => {
        const onMessage = (msg: any): void => {
            if (!msg || typeof msg !== "object") return;
            if (msg.kind === "init-ok") { worker.off("message", onMessage); resolve(); }
            else if (msg.kind === "init-error") { worker.off("message", onMessage); reject(new Error(`sync-worker init: ${msg.error}`)); }
        };
        worker.on("message", onMessage);
        worker.on("error", (e: Error) => { worker.off("message", onMessage); reject(e); });
        worker.postMessage({ kind: "init", configDir: opts.configDir, storePath: opts.storePath });
    });

    const bus = new WorkerBus(worker as unknown as MessagePortLike);
    const emitter = new EventEmitter();
    emitter.setMaxListeners(0);
    const localBodyStore = new FileMessageStore(opts.storePath);

    // Worker events → local re-emit (drives bin's WebView forwarders).
    bus.subscribe<{ event: string; args: any[] }>("sync:event", ({ event, args }) => {
        try { emitter.emit(event, ...(args || [])); } catch (e: any) { console.error(`  [sync-worker] event '${event}' handler threw: ${e?.message || e}`); }
    });

    const proxy = new Proxy(emitter, {
        get(target, prop: string) {
            if (typeof prop === "string" && EMITTER_METHODS.has(prop)) {
                const v = (target as any)[prop];
                return typeof v === "function" ? v.bind(target) : v;
            }
            if (prop === "getBodyStore") return () => localBodyStore;
            if (prop === "then") return undefined; // not a thenable
            // Everything else is an ImapManager method → forward to the worker.
            // structured-clone turns a returned Node Buffer into a plain
            // Uint8Array; re-wrap so callers that test Buffer.isBuffer / hand
            // the bytes to simpleParser (fetchMessageBody) still work.
            return (...args: any[]) => bus.request("sync:call", { method: prop, args }).then((r: any) =>
                (r instanceof Uint8Array && !Buffer.isBuffer(r)) ? Buffer.from(r.buffer, r.byteOffset, r.byteLength) : r);
        },
    }) as unknown as ImapManager;

    return {
        proxy,
        worker,
        async close(): Promise<void> { await worker.terminate(); },
    };
}
