#!/usr/bin/env node
/**
 * @file plugin-open.js
 * @description Builds the dashboard UI bundle for a plugin install and prints
 * the dashboard URL. `scripts/plugin-bootstrap.js` calls `buildClient` eagerly
 * on session start, so client-only routes (e.g. `/run`) work the moment
 * `claude` is started, not just the API and MCP tools. `/ccam-open` re-runs
 * this standalone — a no-op when the bundle already matches — as a fallback
 * for `--force` rebuilds or a first build that failed during bootstrap.
 *
 * The client source is copied out of the (replaced-on-update) plugin cache into
 * the runtime dir before building, so neither the cache nor the checkout is
 * written to, and the output lands where the server already serves from
 * (`DASHBOARD_CLIENT_DIST`).
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */

const fs = require("fs");
const path = require("path");
const { spawnSync } = require("child_process");

const boot = require("./plugin-bootstrap");
const { resolveDashboardPort } = require("../server/lib/server-info");

const PLUGIN_ROOT = path.resolve(__dirname, "..");
const NPM = process.platform === "win32" ? "npm.cmd" : "npm";

function run(cmd, args, cwd, stdio) {
  const res = spawnSync(cmd, args, { cwd, stdio, shell: process.platform === "win32" });
  return res.status === 0;
}

/**
 * Build the bundle into `<runtime>/client-dist`. Idempotent: an existing bundle
 * is left alone unless `force` is set.
 *
 * `logPath`, when given, redirects npm's output there instead of inheriting the
 * caller's stdio — used when this runs inside the detached bootstrap worker,
 * whose own stdout is discarded, so a build failure is still debuggable.
 *
 * @returns {"ready"|"built"|"failed"}
 */
function buildClient({ rt = boot.runtimeDir(), root = PLUGIN_ROOT, force = false, logPath } = {}) {
  const dist = path.join(rt, "client-dist");
  if (!force && fs.existsSync(path.join(dist, "index.html"))) return "ready";

  const logFd = logPath ? fs.openSync(logPath, "a") : null;
  const stdio = logFd === null ? "inherit" : ["ignore", logFd, logFd];
  try {
    const src = path.join(rt, "client-src");
    fs.rmSync(src, { recursive: true, force: true });
    fs.cpSync(path.join(root, "client"), src, {
      recursive: true,
      filter: (p) => !/[\\/](node_modules|dist)$/.test(p),
    });

    if (!run(NPM, ["install", "--no-audit", "--no-fund"], src, stdio)) return "failed";
    if (!run(NPM, ["run", "build"], src, stdio)) return "failed";

    fs.rmSync(dist, { recursive: true, force: true });
    fs.cpSync(path.join(src, "dist"), dist, { recursive: true });
    return "built";
  } finally {
    if (logFd !== null) fs.closeSync(logFd);
  }
}

function dashboardUrl() {
  try {
    return `http://localhost:${resolveDashboardPort()}`;
  } catch {
    return "http://localhost:4820";
  }
}

if (require.main === module) {
  const result = buildClient({ force: process.argv.includes("--force") });
  if (result === "failed") {
    console.error("Client build failed — the API and MCP tools still work.");
    process.exitCode = 1;
  } else {
    if (result === "built") console.log("Dashboard UI built.");
    console.log(dashboardUrl());
  }
}

module.exports = { buildClient, dashboardUrl };
