#!/usr/bin/env node
/**
 * Fail the build when a client API call can never reach the daemon.
 *
 * `api-client.ts` invokes the host bridge as `ipc().foo?.(…)`. If the bridge
 * has no `foo`, optional chaining makes that expression evaluate to
 * `undefined` — no request, no error, no log line. The UI carries on and
 * reports success. That is how right-drag **Copy** was a silent no-op for its
 * entire life: `copyMessages` existed in api-client, in jsonrpc, and in the
 * service — but never in `mailxapi.js`, so every copy quietly did nothing
 * while the status bar said "Copying 3 to Drafts…" (Bob 2026-08-09: "when I
 * used right mouse copy it didn't copy to drafts"). The same audit found
 * `autocomplete` and `cancelServerSearch` dead the same way.
 *
 * The rule this enforces: if the DAEMON can handle it and the CLIENT calls
 * it, the BRIDGE must carry it. A method missing from all three is fine
 * (Android-only surface); a method the client calls with no handler anywhere
 * is a warning, not an error, since some are deliberately optional.
 */
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

const root = path.resolve(fileURLToPath(import.meta.url), "..", "..");
const read = (p) => fs.readFileSync(path.join(root, p), "utf8");

const api = read("client/lib/api-client.ts");
const bridge = read("client/lib/mailxapi.js");
const rpc = read("packages/mailx-service/jsonrpc.ts");

const called = new Set();
for (const m of api.matchAll(/ipc\(\)\.(\w+)\s*\??\.?\s*\(/g)) called.add(m[1]);

const inBridge = new Set();
for (const m of bridge.matchAll(/^\s+(\w+)\s*:\s*(?:async\s+)?function/gm)) inBridge.add(m[1]);

const handled = new Set();
for (const m of rpc.matchAll(/case\s+"(\w+)"\s*:/g)) handled.add(m[1]);

const broken = [];    // client calls it, daemon handles it, bridge doesn't carry it
const orphan = [];    // client calls it, nothing handles it anywhere
for (const name of [...called].sort()) {
    if (inBridge.has(name)) continue;
    (handled.has(name) ? broken : orphan).push(name);
}

if (orphan.length) {
    console.log(`  [bridge-contract] ${orphan.length} client call(s) with no bridge and no daemon handler (optional surface): ${orphan.join(", ")}`);
}

if (broken.length) {
    console.error(`\n  [bridge-contract] FAIL — these have a daemon handler but no entry in client/lib/mailxapi.js,`);
    console.error(`  so every call silently evaluates to undefined and the feature does nothing:\n`);
    for (const n of broken) console.error(`      ${n}`);
    console.error(`\n  Add each to mailxapi.js as:  ${broken[0]}: function(...) { return callNode("${broken[0]}", { ... }); },\n`);
    process.exit(1);
}

console.log(`  [bridge-contract] ok — ${called.size} client calls, all daemon-handled ones present in the bridge`);
