"use strict";
/**
 * Main-thread side of the wallet-worker RPC.
 *
 * Spawns ONE Node `worker_threads` worker, holds a handle for the lifetime of
 * the cds-serve process, and exposes a typed async API that maps to the
 * worker's message handlers.
 *
 * RPC shape: per-call `MessageChannel`. We send `{ kind: 'rpc', method, args,
 * port: port1 }` (transferring port1's MessagePort to the worker), then await
 * a single message on `port2` carrying `{ ok, result | error }`.
 *
 * Push events from the worker — `state-save` and `log` — are handled by
 * listeners registered via `setStateSaveSink(...)` and the default log relay.
 * The save sink is where we wire CAP `db.run` from the main thread (which is
 * NOT blocked by the wallet SDK because the SDK now lives in the worker).
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerPrivateStateProvider = registerPrivateStateProvider;
exports.unregisterPrivateStateProvider = unregisterPrivateStateProvider;
exports.startWalletWorker = startWalletWorker;
exports.stopWalletWorker = stopWalletWorker;
exports.setStateSaveSink = setStateSaveSink;
exports.walletInit = walletInit;
exports.walletWaitForSyncedState = walletWaitForSyncedState;
exports.walletSerializeState = walletSerializeState;
exports.walletEvict = walletEvict;
exports.walletPing = walletPing;
exports.walletRegisterDustGeneration = walletRegisterDustGeneration;
exports.walletDeregisterDustGeneration = walletDeregisterDustGeneration;
exports.walletTransferNight = walletTransferNight;
exports.walletUnshieldNight = walletUnshieldNight;
exports.walletShieldNight = walletShieldNight;
exports.walletGetBalance = walletGetBalance;
exports.walletEstimateTransferFee = walletEstimateTransferFee;
exports.walletEstimateSwapFee = walletEstimateSwapFee;
exports.walletDeployContract = walletDeployContract;
exports.walletSubmitContractCall = walletSubmitContractCall;
exports.__resetWalletWorkerForTests = __resetWalletWorkerForTests;
const node_worker_threads_1 = require("node:worker_threads");
const node_path_1 = __importDefault(require("node:path"));
const format_error_1 = require("../utils/format-error");
let client = null;
/**
 * Per-submission private-state provider registry (Phase 2b).
 *
 * The wallet worker invokes the SDK's `deployContract` / `findDeployedContract`
 * inside the worker. The SDK's PrivateStateProvider hook is proxied from the
 * worker back to the main thread via `private-state-rpc` messages — that's
 * where the real CapDbPrivateStateProvider (with CAP DB access + encryption)
 * lives. Each in-flight submission registers its provider here under a fresh
 * `proxyId` so concurrent deploy/call invocations don't collide on a shared
 * `currentContractAddress`.
 */
const privateStateProviders = new Map();
function registerPrivateStateProvider(proxyId, provider) {
    privateStateProviders.set(proxyId, provider);
}
function unregisterPrivateStateProvider(proxyId) {
    privateStateProviders.delete(proxyId);
}
/**
 * Locate the compiled worker entry. tsc emits the worker .js next to its .ts
 * source under `srv/midnight/`, the same place this client sits at runtime.
 */
function resolveWorkerEntry() {
    // After tsc-build the runtime file is `srv/midnight/wallet-worker.js`
    // sitting next to this compiled client. In dev (`cds watch` with TS) the
    // compiled file is still available alongside the .ts source because the
    // build:plugin step writes JS in-place. Use __dirname so we don't depend
    // on cwd.
    return node_path_1.default.join(__dirname, 'wallet-worker.js');
}
/**
 * Start the worker. Idempotent: a second call returns the existing client.
 * Resolves when the worker has emitted its `ready` message.
 */
async function startWalletWorker() {
    if (client) {
        await client.readyPromise;
        return;
    }
    const entry = resolveWorkerEntry();
    const worker = new node_worker_threads_1.Worker(entry, {
        // The `web-worker` npm polyfill (used by wallet-sdk-prover-client for
        // the WASM prover) checks `worker_threads.isMainThread` at import
        // time. In a Node worker that's false, so it falls into its
        // `workerThread()` branch which destructures `threads.workerData`.
        // If we pass nothing, workerData is null and the destructure throws.
        // Passing `{}` makes web-worker's `if (!workerData.mod)` succeed and
        // redirect to its `mainThread()` code path, which is the one that
        // actually exports the Worker constructor that the SDK expects.
        workerData: {},
        // Inherit NODE_OPTIONS so the wallet SDK gets the 12 GB heap the user
        // sets for `npm run dev`. Without this the worker would default to
        // 4 GB and OOM during the shielded chain scan.
        resourceLimits: undefined,
        // stdout/stderr from the worker should surface to the main process.
        stderr: false,
        stdout: false
    });
    const readyPromise = new Promise((resolve, reject) => {
        const onReady = (msg) => {
            if (msg?.kind === 'ready') {
                worker.off('message', onReady);
                resolve();
            }
        };
        worker.on('message', onReady);
        worker.once('error', err => reject(err));
        worker.once('exit', code => {
            if (code !== 0)
                reject(new Error(`wallet-worker exited with code ${code} before ready`));
        });
    });
    client = { worker, readyPromise };
    // Push events from worker (state-save, log, private-state-rpc). These are
    // NOT replies to main-initiated RPC calls — those go via MessageChannel
    // ports allocated per call.
    worker.on('message', (msg) => {
        if (msg?.kind === 'state-save') {
            client?.stateSaveSink?.(msg);
        }
        else if (msg?.kind === 'log') {
            if (msg.level === 'warn')
                console.warn(msg.message);
            else
                console.log(msg.message);
        }
        else if (msg?.kind === 'private-state-rpc') {
            dispatchPrivateStateRpc(msg);
        }
    });
    worker.on('error', err => {
        console.error('[wallet-worker-client] worker error:', err);
    });
    worker.on('exit', code => {
        console.warn(`[wallet-worker-client] worker exited code=${code}`);
        client = null;
    });
    await readyPromise;
    console.log('[wallet-worker-client] worker ready');
}
/**
 * Stop the worker. Safe to call multiple times. Waits up to `timeoutMs` for
 * graceful exit before terminating.
 */
async function stopWalletWorker(timeoutMs = 5000) {
    if (!client)
        return;
    const w = client.worker;
    client = null;
    try {
        await Promise.race([
            new Promise(resolve => w.once('exit', () => resolve())),
            new Promise(resolve => setTimeout(resolve, timeoutMs))
        ]);
    }
    finally {
        // Force-terminate if still alive.
        try {
            await w.terminate();
        }
        catch { }
    }
}
/**
 * Register the callback that receives push 'state-save' events from the
 * worker. Called by the persistence layer at startup.
 */
function setStateSaveSink(sink) {
    if (!client) {
        throw new Error('wallet-worker not started; call startWalletWorker() first');
    }
    client.stateSaveSink = sink;
}
/**
 * Generic RPC helper. Allocates a MessageChannel per call, posts the request
 * with port1 transferred to the worker, awaits the single reply on port2.
 *
 * Error shape: worker posts `{ ok: false, error: { name, message } }` so we
 * can rehydrate `err.name` here — TransactionSubmitter's classifySubmissionError
 * branches on `err.name === 'TxFailedError'` etc. Falls back to a plain
 * string for older message shapes (none currently, but cheap defensive).
 */
function rpc(method, args) {
    if (!client) {
        return Promise.reject(new Error('wallet-worker not started'));
    }
    return new Promise((resolve, reject) => {
        const { port1, port2 } = new node_worker_threads_1.MessageChannel();
        port2.once('message', (msg) => {
            port2.close();
            if (msg?.ok) {
                resolve(msg.result);
                return;
            }
            const payload = msg?.error;
            if (payload && typeof payload === 'object' && typeof payload.message === 'string') {
                const err = new Error(payload.message);
                if (typeof payload.name === 'string' && payload.name)
                    err.name = payload.name;
                reject(err);
            }
            else {
                reject(new Error(typeof payload === 'string' ? payload : 'worker rpc failed'));
            }
        });
        port2.once('messageerror', err => {
            port2.close();
            reject(err);
        });
        client.worker.postMessage({ kind: 'rpc', method, args, port: port1 }, [port1] // transfer ownership of port1
        );
    });
}
/**
 * Handle a `private-state-rpc` message from the worker.
 *
 * - `setContractAddress` is fire-and-forget (worker sends no `port`). The SDK
 *   contract is synchronous; ordering on parentPort guarantees the next
 *   async set/get arrives AFTER the address has been applied here.
 * - All other methods reply on the supplied MessagePort.
 */
function dispatchPrivateStateRpc(msg) {
    const { proxyId, method, args, port } = msg;
    const provider = privateStateProviders.get(proxyId);
    if (method === 'setContractAddress') {
        // No port — set synchronously, log on error.
        if (!provider) {
            console.warn(`[wallet-worker-client] setContractAddress: unknown proxyId=${String(proxyId).slice(0, 16)}`);
            return;
        }
        try {
            provider.setContractAddress(...args);
        }
        catch (err) {
            console.warn('[wallet-worker-client] setContractAddress failed:', (0, format_error_1.formatErr)(err));
        }
        return;
    }
    if (!port) {
        console.warn(`[wallet-worker-client] private-state-rpc missing port for method=${method}`);
        return;
    }
    if (!provider) {
        port.postMessage({
            ok: false,
            error: { name: 'PrivateStateProxyMissing', message: `Unknown proxyId=${String(proxyId).slice(0, 16)}` }
        });
        port.close();
        return;
    }
    (async () => {
        try {
            const result = await dispatchPrivateStateMethod(provider, method, args);
            port.postMessage({ ok: true, result });
        }
        catch (err) {
            port.postMessage({
                ok: false,
                error: {
                    name: err?.name ?? 'Error',
                    message: (0, format_error_1.formatErr)(err)
                }
            });
        }
        finally {
            port.close();
        }
    })();
}
/**
 * Type-safe dispatch into CapDbPrivateStateProvider for the 8 async methods
 * the SDK uses. A `switch` over the known method names lets TypeScript check
 * each call signature; the worker can only request methods we explicitly
 * support, and an unknown name produces a stable error rather than a runtime
 * "fn is not a function" from a duck-typed lookup.
 */
async function dispatchPrivateStateMethod(provider, method, args) {
    switch (method) {
        case 'set': return provider.set(args[0], args[1]);
        case 'get': return provider.get(args[0]);
        case 'remove': return provider.remove(args[0]);
        case 'clear': return provider.clear();
        case 'setSigningKey': return provider.setSigningKey(args[0], args[1]);
        case 'getSigningKey': return provider.getSigningKey(args[0]);
        case 'removeSigningKey': return provider.removeSigningKey(args[0]);
        case 'clearSigningKeys': return provider.clearSigningKeys();
        default:
            throw new Error(`Unsupported private-state RPC method: '${method}'`);
    }
}
// ---- Typed RPC surface ----------------------------------------------------
function walletInit(args) {
    return rpc('init', args);
}
function walletWaitForSyncedState(sessionId, timeoutMs) {
    return rpc('waitForSyncedState', { sessionId, timeoutMs });
}
function walletSerializeState(sessionId) {
    return rpc('serializeState', { sessionId });
}
function walletEvict(sessionId) {
    return rpc('evict', { sessionId });
}
function walletPing() {
    return rpc('ping', {});
}
/**
 * End-to-end NIGHT-UTXO registration for DUST generation.
 * Single RPC that wraps wait-sync → filter → register → finalize → submit.
 * `syncTimeoutMs: 0` (or omitted) waits indefinitely for sync; provide a
 * positive number to bound the wait for tests.
 */
function walletRegisterDustGeneration(args) {
    return rpc('registerDustGeneration', args);
}
/**
 * Symmetric pair to `walletRegisterDustGeneration`. Deregisters all
 * registered NIGHT UTXOs so they become spendable. Per-UTXO narrowing
 * is a follow-up; today this is all-or-nothing.
 */
function walletDeregisterDustGeneration(args) {
    return rpc('deregisterDustGeneration', args);
}
/**
 * Send NIGHT to any Midnight address. Ledger is auto-detected from the
 * receiver's Bech32m prefix (mn_shield-addr_ vs mn_addr_).
 *
 * Amount is a decimal string parseable as bigint (NIGHT atoms) — strings
 * avoid the precision pitfalls of JS Number when atom counts exceed 2^53.
 */
function walletTransferNight(args) {
    return rpc('transferNight', args);
}
/**
 * Move NIGHT from shielded → unshielded ledger. Always targets the
 * wallet's own unshielded address (no third-party recipient parameter
 * for this primitive).
 */
function walletUnshieldNight(args) {
    return rpc('unshieldNight', args);
}
/** Symmetric counterpart: unshielded → shielded for own funds. */
function walletShieldNight(args) {
    return rpc('shieldNight', args);
}
/**
 * Snapshot of the wallet's balances. Read-only — no transaction is
 * built or submitted. All amounts are decimal-string bigint to avoid
 * Number precision loss.
 */
function walletGetBalance(args) {
    return rpc('getBalance', args);
}
/**
 * Pre-flight fee estimate for `walletTransferNight`. Builds the recipe
 * but does NOT finalize (no proof generation) or submit. Returns dust
 * atoms as decimal string.
 */
function walletEstimateTransferFee(args) {
    return rpc('estimateTransferFee', args);
}
/**
 * Pre-flight fee estimate for shield/unshield ledger shifts. Builds the
 * `initSwap` recipe without finalizing. `direction` selects which way.
 */
function walletEstimateSwapFee(args) {
    return rpc('estimateSwapFee', args);
}
/**
 * Deploy a Compact-emitted contract through the wallet worker. The worker
 * owns the SDK and the wallet facade; private-state CRUD round-trips back to
 * the main-side provider registered under `proxyId`.
 *
 * Returns primitives only (no SDK objects cross the thread boundary).
 */
function walletDeployContract(args) {
    return rpc('deployContract', args);
}
/**
 * Invoke a circuit on a deployed contract through the worker. Same wiring as
 * `walletDeployContract`. Returns the submission txHash + on-chain status.
 */
function walletSubmitContractCall(args) {
    return rpc('submitContractCall', args);
}
/** Test-only: reset the singleton so subsequent calls re-spawn. */
function __resetWalletWorkerForTests() {
    client = null;
    privateStateProviders.clear();
}
