#!/usr/bin/env node
"use strict";

// src/server/radar-teardown-entry.ts
var import_node_child_process = require("node:child_process");
var import_node_fs = require("node:fs");
var import_node_path = require("node:path");

// src/server/recall/config.ts
var DEFAULT_CONFIG = {
  debounce: 3e3,
  ignore: [
    "node_modules",
    ".next",
    "dist",
    "build",
    ".turbo",
    ".git",
    ".recall",
    "coverage",
    ".pnpm-store",
    ".launchsecure/graphs",
    ".claude/settings.local.json"
  ],
  retention: {
    keepLast: 5e3,
    maxAgeDays: 30
  }
};

// src/server/radar/mcp.ts
var import_node_https = require("node:https");
var import_node_http = require("node:http");
var ProjectMcpClient = class {
  constructor(opts) {
    this.initialized = false;
    this.callId = 1;
    this.mcpUrl = new URL("/api/mcp/project", opts.serverUrl);
    this.headers = {
      "Content-Type": "application/json",
      "Accept": "application/json, text/event-stream",
      "Authorization": `Bearer ${opts.pat}`
    };
    if (opts.orgSlug) this.headers["X-Org-Slug"] = opts.orgSlug;
    if (opts.projectSlug) this.headers["X-Project-Slug"] = opts.projectSlug;
  }
  async call(toolName, args) {
    await this.ensureInitialized();
    const body = JSON.stringify({
      jsonrpc: "2.0",
      id: this.callId++,
      method: "tools/call",
      params: { name: toolName, arguments: args }
    });
    const resp = await this.send(body);
    const parsed = parseBody(resp.body);
    if (parsed.error) {
      throw new Error(`MCP ${toolName} failed: ${parsed.error.message ?? JSON.stringify(parsed.error)}`);
    }
    const text = parsed.result?.content?.[0]?.text;
    if (!text) return parsed.result;
    if (text.startsWith("\u2500\u2500 Error")) {
      const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
      const reason = lines.find((l) => !l.startsWith("\u2500\u2500")) ?? text;
      throw new Error(`MCP ${toolName} failed: ${reason}`);
    }
    try {
      return JSON.parse(text);
    } catch {
      return text;
    }
  }
  async ensureInitialized() {
    if (this.initialized) return;
    const initBody = JSON.stringify({
      jsonrpc: "2.0",
      id: this.callId++,
      method: "initialize",
      params: {
        protocolVersion: "2025-03-26",
        capabilities: {},
        clientInfo: { name: "launchpod-feedback-agent", version: "0.0.1" }
      }
    });
    const initResp = await this.send(initBody);
    if (initResp.sessionId) this.sessionId = initResp.sessionId;
    const notifBody = JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" });
    await this.send(notifBody);
    this.initialized = true;
  }
  send(body) {
    return new Promise((resolve2, reject) => {
      const headers = {
        ...this.headers,
        "Content-Length": String(Buffer.byteLength(body))
      };
      if (this.sessionId) headers["Mcp-Session-Id"] = this.sessionId;
      const requester = this.mcpUrl.protocol === "https:" ? import_node_https.request : import_node_http.request;
      const req = requester(
        {
          host: this.mcpUrl.hostname,
          port: this.mcpUrl.port || (this.mcpUrl.protocol === "https:" ? 443 : 80),
          path: this.mcpUrl.pathname + this.mcpUrl.search,
          method: "POST",
          headers
        },
        (res) => {
          const chunks = [];
          res.on("data", (c) => chunks.push(c));
          res.on("end", () => {
            const text = Buffer.concat(chunks).toString("utf-8");
            if (res.statusCode && res.statusCode >= 400) {
              reject(new Error(`MCP HTTP ${res.statusCode}: ${text}`));
              return;
            }
            const sid = res.headers["mcp-session-id"];
            resolve2({ body: text, sessionId: typeof sid === "string" ? sid : void 0 });
          });
        }
      );
      req.on("error", reject);
      req.write(body);
      req.end();
    });
  }
};
function parseBody(text) {
  if (!text) return {};
  if (text.startsWith("event:") || text.includes("\ndata: ")) {
    for (const line of text.split("\n")) {
      if (line.startsWith("data: ")) {
        try {
          return JSON.parse(line.slice(6));
        } catch {
        }
      }
    }
  }
  try {
    return JSON.parse(text);
  } catch {
    return {};
  }
}

// src/server/radar-teardown-entry.ts
var COMPOSE_FILE = (0, import_node_path.resolve)(process.cwd(), "docker-compose.yml");
var LAUNCH_POD_DIR = (0, import_node_path.dirname)(COMPOSE_FILE);
var COMPOSE_BASE = ["compose", "-f", COMPOSE_FILE];
var COMPOSE_SERVICE = "launch-pod";
var ENV_PATH = (0, import_node_path.join)(LAUNCH_POD_DIR, ".env");
function ensureLaunchPodCompose() {
  if (!(0, import_node_fs.existsSync)(COMPOSE_FILE)) {
    console.error(`[teardown] aborting \u2014 no docker-compose.yml at ${COMPOSE_FILE}`);
    console.error(`[teardown] run from packages/cli/docker/launch-pod/ (the directory holding the launch-pod compose).`);
    process.exit(1);
  }
  const text = (0, import_node_fs.readFileSync)(COMPOSE_FILE, "utf-8");
  const hasServices = /(^|\n)services:\s*(\n|$)/.test(text);
  const hasLaunchPod = /\n[ \t]+launch-pod:/.test(text);
  if (!hasServices || !hasLaunchPod) {
    console.error(`[teardown] aborting \u2014 ${COMPOSE_FILE} does not define a 'launch-pod' service.`);
    console.error(`[teardown] this command is only for the launch-pod container; refusing to touch this compose project.`);
    process.exit(1);
  }
}
function parseArgs(argv) {
  const out = { force: false, removeEnv: false, removeImage: false, help: false };
  for (const a of argv) {
    switch (a) {
      case "--force":
        out.force = true;
        break;
      case "--remove-env":
        out.removeEnv = true;
        break;
      case "--remove-image":
        out.removeImage = true;
        break;
      case "-h":
      case "--help":
        out.help = true;
        break;
      default:
        console.error(`unknown flag: ${a}`);
        process.exit(1);
    }
  }
  return out;
}
function printHelp() {
  console.log("usage: launch-sequencer radar:teardown [--remove-env] [--remove-image] [--force]");
  console.log("");
  console.log("  --remove-env     also delete .env (default: keep \u2014 contains live creds)");
  console.log("  --remove-image   also remove launch-pod:local image");
  console.log("  --force          skip the workspace-safety preflight (discards");
  console.log("                   uncommitted changes / unpushed commits / in-flight");
  console.log("                   analyzer sessions without warning)");
}
function sh(cmd, args, opts = {}) {
  const r = (0, import_node_child_process.spawnSync)(cmd, args, { encoding: "utf8", ...opts });
  return {
    status: r.status ?? 1,
    stdout: typeof r.stdout === "string" ? r.stdout : "",
    stderr: typeof r.stderr === "string" ? r.stderr : ""
  };
}
function workspaceSh(command) {
  const psQ = sh("docker", [...COMPOSE_BASE, "ps", "-q", COMPOSE_SERVICE]);
  if (psQ.status === 0 && psQ.stdout.trim()) {
    return sh("docker", [...COMPOSE_BASE, "exec", "-T", COMPOSE_SERVICE, "sh", "-c", command]).stdout;
  }
  return sh("docker", [...COMPOSE_BASE, "run", "--rm", "--no-deps", "--entrypoint", "sh", COMPOSE_SERVICE, "-c", command]).stdout;
}
function loadIgnoreSegs() {
  const json = workspaceSh("cat /workspace/.recall/config.json 2>/dev/null");
  if (json.trim()) {
    try {
      const j = JSON.parse(json);
      if (Array.isArray(j.ignore) && j.ignore.every((s) => typeof s === "string")) {
        return j.ignore;
      }
    } catch {
    }
  }
  return [...DEFAULT_CONFIG.ignore];
}
function pathIsNoise(p, segs) {
  for (const seg of segs) {
    if (p === seg) return true;
    if (p.startsWith(seg + "/")) return true;
    if (p.endsWith("/" + seg)) return true;
    if (p.includes("/" + seg + "/")) return true;
  }
  return false;
}
var SCAFFOLDED_MCP_PATHS = [
  "mcpServers.launch-secure.url"
];
function collectDiffPaths(a, b, prefix, out) {
  if (a === b) return;
  const aIsPrim = a === null || typeof a !== "object";
  const bIsPrim = b === null || typeof b !== "object";
  if (aIsPrim || bIsPrim) {
    if (a !== b) out.push(prefix || "$");
    return;
  }
  const aIsArr = Array.isArray(a);
  const bIsArr = Array.isArray(b);
  if (aIsArr || bIsArr) {
    if (!aIsArr || !bIsArr || a.length !== b.length) {
      out.push(prefix || "$");
      return;
    }
    const aa = a;
    const bb = b;
    for (let i = 0; i < aa.length; i++) {
      collectDiffPaths(aa[i], bb[i], prefix ? `${prefix}[${i}]` : `[${i}]`, out);
    }
    return;
  }
  const ao = a;
  const bo = b;
  for (const k of /* @__PURE__ */ new Set([...Object.keys(ao), ...Object.keys(bo)])) {
    const child = prefix ? `${prefix}.${k}` : k;
    if (!(k in ao) || !(k in bo)) {
      out.push(child);
      continue;
    }
    collectDiffPaths(ao[k], bo[k], child, out);
  }
}
function mcpJsonOnlyScaffoldedDiff() {
  const headJson = workspaceSh("git -C /workspace show HEAD:.mcp.json 2>/dev/null");
  const wtJson = workspaceSh("cat /workspace/.mcp.json 2>/dev/null");
  if (!headJson.trim() || !wtJson.trim()) return false;
  let head, wt;
  try {
    head = JSON.parse(headJson);
    wt = JSON.parse(wtJson);
  } catch {
    return false;
  }
  const diffs = [];
  collectDiffPaths(head, wt, "", diffs);
  if (diffs.length === 0) return false;
  return diffs.every((d) => SCAFFOLDED_MCP_PATHS.some((s) => d === s || d.startsWith(s + ".")));
}
function filterStatusSignal(raw, modeOnly, segs, scaffoldClean) {
  const out = [];
  for (const rawLine of raw.split("\n")) {
    const line = rawLine.replace(/\r$/, "");
    if (line.length < 3) continue;
    let p = line.slice(3);
    if (line.startsWith("R") || line.startsWith("C")) {
      const arrow = p.indexOf(" -> ");
      if (arrow >= 0) p = p.slice(arrow + 4);
    }
    if (pathIsNoise(p, segs)) continue;
    if (modeOnly.has(p)) continue;
    if (scaffoldClean.has(p)) continue;
    out.push(line);
  }
  return out;
}
function probeDocker() {
  const containerRunning = sh("docker", [...COMPOSE_BASE, "ps", "-q", COMPOSE_SERVICE]).stdout.trim().length > 0;
  const projectName = (sh("docker", [...COMPOSE_BASE, "config", "--format", "json"]).stdout.match(/"name"\s*:\s*"([^"]+)"/) ?? [])[1] ?? "";
  let workspaceVolumeExists = false;
  if (projectName) {
    const vols = sh("docker", ["volume", "ls", "-q", "--filter", `name=^${projectName}_workspace$`]);
    workspaceVolumeExists = vols.status === 0 && vols.stdout.trim().length > 0;
  }
  return { containerRunning, workspaceVolumeExists };
}
function preflightWorkspace(args) {
  if (args.force) {
    console.log("[teardown] --force given \u2014 skipping workspace-safety preflight");
    return;
  }
  const state = probeDocker();
  if (!state.containerRunning && !state.workspaceVolumeExists) {
    console.log("[teardown] no launch-pod container or workspace volume present \u2014 nothing to check");
    return;
  }
  console.log("[teardown] checking workspace for unsaved work\u2026");
  const segs = loadIgnoreSegs();
  const numstatRaw = workspaceSh("git -C /workspace diff --numstat 2>/dev/null");
  const modeOnly = /* @__PURE__ */ new Set();
  for (const line of numstatRaw.split("\n")) {
    const parts = line.split("	");
    if (parts.length >= 3 && parts[0] === "0" && parts[1] === "0") {
      modeOnly.add(parts[2]);
    }
  }
  const rawDirty = workspaceSh("git -C /workspace status --porcelain 2>/dev/null | head -200");
  const scaffoldClean = /* @__PURE__ */ new Set();
  if (mcpJsonOnlyScaffoldedDiff()) scaffoldClean.add(".mcp.json");
  const dirty = filterStatusSignal(rawDirty, modeOnly, segs, scaffoldClean);
  const unpushedRaw = workspaceSh("git -C /workspace rev-list --count @{u}..HEAD 2>/dev/null || echo 0").trim();
  const unpushed = parseInt(unpushedRaw || "0", 10);
  const inflightRaw = workspaceSh(`grep -oE '"state":[[:space:]]*"(analyzing|queued)"' /workspace/.launchpod/radar-pings.json 2>/dev/null | wc -l | tr -d ' '`).trim();
  const inflight = parseInt(inflightRaw || "0", 10);
  let blocked = false;
  if (dirty.length > 0) {
    console.log("[teardown] \u2717 uncommitted changes in /workspace (after recall-ignore filter):");
    for (const line of dirty) console.log("    " + line);
    blocked = true;
  }
  if (unpushed > 0) {
    console.log(`[teardown] \u2717 ${unpushed} unpushed commit(s) ahead of upstream`);
    blocked = true;
  }
  if (inflight > 0) {
    console.log(`[teardown] \u2717 ${inflight} in-flight or queued analyzer session(s) \u2014 output will be lost`);
    blocked = true;
  }
  if (blocked) {
    console.log("");
    console.log("[teardown] aborting \u2014 workspace has unsaved work.");
    console.log("  \u2022 commit + push from inside the container (docker compose exec launch-pod sh), then re-run, OR");
    console.log("  \u2022 re-run with --force to discard the work and tear down anyway.");
    process.exit(2);
  }
  console.log("[teardown] \u2713 workspace is clean (only recall-ignored noise present) \u2014 safe to tear down");
}
function parseEnvFile(path) {
  const env = {};
  for (const raw of (0, import_node_fs.readFileSync)(path, "utf8").split("\n")) {
    const line = raw.trim();
    if (!line || line.startsWith("#")) continue;
    const eq = line.indexOf("=");
    if (eq < 0) continue;
    const key = line.slice(0, eq).trim();
    let value = line.slice(eq + 1).trim();
    if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
      value = value.slice(1, -1);
    }
    if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) env[key] = value;
  }
  return env;
}
async function releaseWebhook() {
  const psQ = sh("docker", [...COMPOSE_BASE, "ps", "-q", COMPOSE_SERVICE]);
  if (psQ.status !== 0 || !psQ.stdout.trim()) {
    console.log("[teardown] container not running \u2014 skipping webhook release");
    console.log("[teardown] (any prior registration is now orphaned \u2014 clean up via cloud LS settings UI)");
    return;
  }
  const radarJson = workspaceSh("cat /workspace/.launchpod/radar.json 2>/dev/null");
  let endpointId = "";
  try {
    const j = JSON.parse(radarJson);
    endpointId = j.endpointId ?? j.registration?.endpointId ?? j.webhook?.endpointId ?? "";
  } catch {
  }
  if (!endpointId) {
    console.log("[teardown] no endpoint_id found in /workspace/.launchpod/radar.json \u2014 skipping release");
    return;
  }
  if (!(0, import_node_fs.existsSync)(ENV_PATH)) {
    console.log(`[teardown] no .env found at ${ENV_PATH} \u2014 can't release webhook (manual cleanup needed)`);
    return;
  }
  const env = parseEnvFile(ENV_PATH);
  const pat = env.LS_PAT;
  const orgSlug = env.LS_ORG_SLUG;
  const projectSlug = env.LS_PROJECT_SLUG;
  const serverUrl = env.LS_SERVER_URL || "https://launchsecure-v2.vercel.app";
  if (!pat || !orgSlug || !projectSlug) {
    console.log("[teardown] .env is missing LS_PAT / LS_ORG_SLUG / LS_PROJECT_SLUG \u2014 skipping release");
    return;
  }
  console.log(`[teardown] releasing endpoint ${endpointId} from ${serverUrl}`);
  try {
    const mcp = new ProjectMcpClient({ serverUrl, pat, orgSlug, projectSlug });
    await mcp.call("launchpod_webhook_release", { endpoint_id: endpointId });
    console.log("[teardown] \u2713 webhook released");
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    console.log(`[teardown] \u26A0 webhook release failed (${msg}) \u2014 delete manually at:`);
    console.log(`           ${serverUrl}/${orgSlug}/projects/${projectSlug}/settings`);
  }
}
function dockerComposeDown() {
  console.log(`[teardown] docker compose -f ${COMPOSE_FILE} down -v`);
  const down = (0, import_node_child_process.spawnSync)("docker", [...COMPOSE_BASE, "down", "-v"], { encoding: "utf8" });
  const combined = (down.stdout || "") + (down.stderr || "");
  for (const line of combined.split("\n")) {
    if (/Removing|Removed|Stopping|Stopped/.test(line)) console.log("  " + line.trim());
  }
  if (down.status !== 0) {
    console.error(`[teardown] docker compose down -v failed (status ${down.status})`);
    process.exit(down.status ?? 1);
  }
}
function cleanupLocalArtifacts(args) {
  if (args.removeImage) {
    console.log("[teardown] removing image launch-pod:local");
    const rmi = sh("docker", ["rmi", "launch-pod:local"]);
    console.log(rmi.status === 0 ? "  removed" : "  not present");
  }
  for (const f of (0, import_node_fs.readdirSync)(LAUNCH_POD_DIR)) {
    if (f.startsWith("launchsecure-launch-kit-") && f.endsWith(".tgz")) {
      console.log("[teardown] removing launch-kit tarball(s)");
      try {
        (0, import_node_fs.unlinkSync)((0, import_node_path.join)(LAUNCH_POD_DIR, f));
      } catch {
      }
    }
  }
  if (args.removeEnv && (0, import_node_fs.existsSync)(ENV_PATH)) {
    console.log("[teardown] removing .env (--remove-env)");
    try {
      (0, import_node_fs.unlinkSync)(ENV_PATH);
    } catch {
    }
  } else if ((0, import_node_fs.existsSync)(ENV_PATH)) {
    console.log("[teardown] keeping .env (use --remove-env to delete it next time)");
  }
}
async function main() {
  const args = parseArgs(process.argv.slice(2));
  if (args.help) {
    printHelp();
    return;
  }
  ensureLaunchPodCompose();
  preflightWorkspace(args);
  await releaseWebhook();
  dockerComposeDown();
  cleanupLocalArtifacts(args);
  console.log("[teardown] done.");
}
main().catch((err) => {
  console.error("[teardown] fatal:", err);
  process.exit(1);
});
