#!/usr/bin/env node
/**
 * @file check-mcp-build.js
 * @description `mcp/build/` is committed so a plugin install has a working MCP
 * server the instant the session opens — Claude Code starts plugin MCP servers
 * immediately and offers no "not ready yet" retry, so an async bootstrap cannot
 * win that race. The cost is that the artifact can drift from `mcp/src`.
 *
 * Freshness is tracked by hashing the CONTENT of `mcp/src` (plus the manifests
 * and tsconfig) into `mcp/build/.srchash`. Modification times are useless here:
 * a fresh clone or checkout stamps every file with the same time in arbitrary
 * order.
 *
 * Run by the pre-commit hook and by `/ccam-doctor`.
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */

const fs = require("fs");
const path = require("path");
const crypto = require("crypto");

const HASH_FILE = ".srchash";

/** Every file whose content the build depends on, in a stable order. */
function sourceFiles(mcpDir) {
  const files = [];
  const walk = (dir) => {
    let entries;
    try {
      entries = fs
        .readdirSync(dir, { withFileTypes: true })
        .sort((a, b) => (a.name < b.name ? -1 : 1));
    } catch {
      return;
    }
    for (const e of entries) {
      const full = path.join(dir, e.name);
      if (e.isDirectory()) walk(full);
      else files.push(full);
    }
  };
  walk(path.join(mcpDir, "src"));
  for (const f of ["package.json", "package-lock.json", "tsconfig.json"]) {
    const full = path.join(mcpDir, f);
    if (fs.existsSync(full)) files.push(full);
  }
  return files;
}

function sourceHash(root = path.resolve(__dirname, "..")) {
  const mcpDir = path.join(root, "mcp");
  const h = crypto.createHash("sha256");
  for (const f of sourceFiles(mcpDir)) {
    h.update(path.relative(mcpDir, f).replace(/\\/g, "/"));
    h.update(fs.readFileSync(f));
  }
  return h.digest("hex");
}

/**
 * @returns {{ok:boolean, reason:string, expected:string, recorded:string|null}}
 */
function mcpBuildStatus(root = path.resolve(__dirname, "..")) {
  const buildDir = path.join(root, "mcp", "build");
  const expected = sourceHash(root);
  if (!fs.existsSync(path.join(buildDir, "index.js"))) {
    return { ok: false, reason: "mcp/build/index.js is missing", expected, recorded: null };
  }
  let recorded = null;
  try {
    recorded = fs.readFileSync(path.join(buildDir, HASH_FILE), "utf8").trim();
  } catch {
    return { ok: false, reason: "mcp/build has no recorded source hash", expected, recorded: null };
  }
  return recorded === expected
    ? { ok: true, reason: "up to date", expected, recorded }
    : {
        ok: false,
        reason: "mcp/build is stale — mcp/src has changed since it was built",
        expected,
        recorded,
      };
}

/** Stamp the current source hash into the build directory. */
function writeHash(root = path.resolve(__dirname, "..")) {
  const buildDir = path.join(root, "mcp", "build");
  fs.mkdirSync(buildDir, { recursive: true });
  fs.writeFileSync(path.join(buildDir, HASH_FILE), sourceHash(root) + "\n", "utf8");
}

if (require.main === module) {
  if (process.argv.includes("--write")) {
    writeHash();
    console.log("mcp/build/.srchash updated");
  } else {
    const status = mcpBuildStatus();
    if (status.ok) {
      console.log("mcp/build is up to date");
    } else {
      console.error(`${status.reason}\nRun: npm run mcp:build`);
      process.exitCode = 1;
    }
  }
}

module.exports = { sourceHash, mcpBuildStatus, writeHash };
