import { spawn } from "node:child_process";

const MACOS_CONSENT_SCRIPT = `on run argv
  set proposalText to item 1 of argv
  set answer to display dialog proposalText with title "Talons device security" buttons {"Deny", "Allow this exact action"} default button "Deny" cancel button "Deny" with icon caution
  if button returned of answer is not "Allow this exact action" then error number -128
  return "allowed"
end run`;

/**
 * The MCP protocol can prove that an app called a tool, not that a human
 * clicked. The local MVP therefore adds a second, device-native approval that
 * an arbitrary MCP client cannot forge with the widget challenge alone.
 */
export function createNativeConsentBroker({ platform = process.platform, timeoutMs = 120_000 } = {}) {
  return async function confirmNativeConsent({ job }) {
    if (platform !== "darwin") {
      throw new Error("Mutating actions are disabled: this MVP has no native consent broker for this operating system.");
    }
    const message = consentMessage(job);
    await runOsascript(message, timeoutMs);
    return { consentedBy: "macos-native-consent-dialog" };
  };
}

function consentMessage(job) {
  const consequences = Array.isArray(job?.spec?.consequences) ? job.spec.consequences : [];
  return [
    "Allow this exact security action?",
    "",
    `Device: ${job?.spec?.deviceId || "unknown"}`,
    `Capability: ${job?.spec?.capabilityId || "unknown"}`,
    `Parameters: ${JSON.stringify(job?.spec?.parameters || {})}`,
    `Expires: ${job?.consent?.expiresAt || "unknown"}`,
    `Spec SHA-256: ${job?.specDigest || "unknown"}`,
    "",
    "Consequences:",
    ...consequences.map((item) => `- ${item}`),
  ].join("\n");
}

function runOsascript(message, timeoutMs) {
  return new Promise((resolve, reject) => {
    const child = spawn("/usr/bin/osascript", ["-e", MACOS_CONSENT_SCRIPT, "--", message], {
      stdio: ["ignore", "pipe", "pipe"],
    });
    let stderr = "";
    child.stderr.setEncoding("utf8");
    child.stderr.on("data", (chunk) => {
      if (stderr.length < 4_096) stderr += chunk;
    });
    const timer = setTimeout(() => {
      child.kill("SIGTERM");
      reject(new Error("Native consent dialog expired without approval."));
    }, timeoutMs);
    child.once("error", (error) => {
      clearTimeout(timer);
      reject(error);
    });
    child.once("exit", (code) => {
      clearTimeout(timer);
      if (code === 0) resolve();
      else reject(new Error(code === 1 || /User canceled/u.test(stderr)
        ? "The user denied the native consent dialog."
        : "Native consent could not be verified."));
    });
  });
}
