#!/usr/bin/env node
/**
 * @file plugin-doctor.js
 * @description Reports the health of a plugin-installed CCAM: Node version,
 * bootstrap state, runtime dependencies, server liveness, duplicate hook
 * entries (the one failure that silently doubles every token and cost figure),
 * the `ccam` CLI launcher and its PATH, and whether the committed MCP build
 * still matches `mcp/src`. Read-only — it diagnoses, it never repairs.
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */

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

const boot = require("./plugin-bootstrap");
const { getSettingsPath, getDataDir } = require("../server/lib/claude-home");
const { mcpBuildStatus } = require("./check-mcp-build");

const PLUGIN_ROOT = path.resolve(__dirname, "..");

/** @returns {{level:"ok"|"warn"|"fail", label:string, detail:string}[]} */
function diagnose() {
  const rt = boot.runtimeDir();
  const out = [];
  const add = (level, label, detail) => out.push({ level, label, detail });

  add(
    boot.nodeVersionOk() ? "ok" : "fail",
    "Node",
    boot.nodeVersionOk()
      ? `v${process.versions.node}`
      : `v${process.versions.node} — CCAM needs >= ${boot.MIN_NODE.join(".")} (node:sqlite)`
  );

  add("ok", "Plugin root", PLUGIN_ROOT);
  add("ok", "Data dir", getDataDir());

  const state = boot.readState(rt);
  if (!state) {
    add(
      "warn",
      "Bootstrap",
      `no state recorded — start a new session, or check ${rt}/bootstrap.log`
    );
  } else {
    const moved = state.pluginRoot !== PLUGIN_ROOT;
    add(
      moved ? "warn" : "ok",
      "Bootstrap",
      moved
        ? `recorded against a previous plugin version (${state.pluginRoot}) — run /ccam-update`
        : `last run ${state.updatedAt}`
    );
  }

  const deps = fs.existsSync(path.join(rt, "node_modules"));
  add(
    deps ? "ok" : "fail",
    "Runtime deps",
    deps ? path.join(rt, "node_modules") : "missing — run /ccam-update"
  );

  const live = boot.liveServers();
  add(
    live.length ? "ok" : "fail",
    "Server",
    live.length
      ? `listening on ${live.map((s) => `http://localhost:${s.port}`).join(", ")}`
      : `not running — see ${path.join(rt, "server.log")}`
  );

  const dup = countLegacyHookEntries();
  add(
    dup ? "fail" : "ok",
    "Hooks",
    dup
      ? `${dup} entr${dup === 1 ? "y" : "ies"} in ${getSettingsPath()} duplicate the plugin's hooks — ` +
          `every event is counted twice. Remove them (a new session does it automatically).`
      : "provided by the plugin only"
  );

  add(...cliStatus());

  const mcp = mcpBuildStatus(PLUGIN_ROOT);
  add(
    mcp.ok ? "ok" : "fail",
    "MCP build",
    mcp.ok ? "matches mcp/src" : `${mcp.reason} — run npm run mcp:build`
  );

  const dist = process.env.DASHBOARD_CLIENT_DIST || path.join(rt, "client-dist");
  add(
    fs.existsSync(path.join(dist, "index.html")) ? "ok" : "warn",
    "Dashboard UI",
    fs.existsSync(path.join(dist, "index.html")) ? dist : "not built yet — run /ccam-open"
  );

  return out;
}

function countLegacyHookEntries() {
  try {
    const settings = JSON.parse(fs.readFileSync(getSettingsPath(), "utf8"));
    if (!settings.hooks) return 0;
    const isLegacy = boot.isCheckoutHookEntry();
    return Object.values(settings.hooks)
      .filter(Array.isArray)
      .reduce((n, entries) => n + entries.filter(isLegacy).length, 0);
  } catch {
    return 0;
  }
}

function cliStatus() {
  const dir = path.join(os.homedir(), ".local", "bin");
  const file = path.join(dir, process.platform === "win32" ? "ccam.cmd" : "ccam");
  if (!fs.existsSync(file)) return ["warn", "ccam CLI", `no launcher at ${file}`];
  const onPath = (process.env.PATH || "")
    .split(path.delimiter)
    .some((p) => path.resolve(p) === path.resolve(dir));
  return onPath
    ? ["ok", "ccam CLI", file]
    : [
        "warn",
        "ccam CLI",
        `${file} exists but ${dir} is not on PATH — add: export PATH="${dir}:$PATH"`,
      ];
}

function report() {
  const marks = { ok: "OK  ", warn: "WARN", fail: "FAIL" };
  const rows = diagnose();
  for (const r of rows) console.log(`${marks[r.level]}  ${r.label.padEnd(14)} ${r.detail}`);
  return rows.some((r) => r.level === "fail") ? 1 : 0;
}

if (require.main === module) process.exitCode = report();

module.exports = { diagnose, report, countLegacyHookEntries };
