// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. public-exposure module for scripts/src/agentrun.ts.

// Moved mechanically from scripts/src/agentrun.ts:2593-2746 for #903.

// SPEC: PJ2026-01020108 cancel lifecycle + PJ2026-01020205 AipodSpec binding + PJ2026-01020302 session policy + PJ2026-01020305 cancel control + PJ2026-01060305/06 YAML execution policy and bounded output draft-2026-06-25-p0.
// Exposes AgentRun lane-scoped policy, AipodSpec SecretRef binding, cancel lifecycle, and bounded default output in the UniDesk CLI.
import { chmodSync, copyFileSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { spawnSync } from "node:child_process";
import { rootPath, type UniDeskConfig } from "../config";
import type { RenderedCliResult } from "../output";
import { applyLocalCaddyManagedSite } from "../pk01-caddy";
import { runSshCommandCapture, type SshCaptureResult } from "../ssh";
import { runRemoteSshCommandCapture } from "../remote";
import { startJob } from "../jobs";
import {
  AGENTRUN_CONFIG_PATH,
  agentRunLaneSummary,
  agentRunPipelineRunName,
  agentRunProviderCredentialRefs,
  resolveAgentRunLaneTarget,
  type AgentRunCancelLifecycleSpec,
  type AgentRunLaneSpec,
} from "../agentrun-lanes";
import {
  agentRunImageArtifact,
  placeholderAgentRunImage,
  renderedFilesDigest,
  renderedObjectsDigest,
  renderAgentRunControlPlaneManifests,
  renderAgentRunGitopsFiles,
  type AgentRunArtifactService,
} from "../agentrun-manifests";
import { sha256Fingerprint } from "../platform-infra-ops-library";

import type { StatusOptions } from "./options";
import { displayValue, renderTable } from "./options";
import { renderedCliResult } from "./render";
import { renderNextObjectLines, shortSha, yesNo } from "./trigger";
import { record } from "./utils";

export function agentRunControlPlaneStatusCommand(spec: AgentRunLaneSpec, options: Pick<StatusOptions, "sourceCommit" | "pipelineRun">, full: boolean): string {
  return [
    "bun scripts/cli.ts agentrun control-plane status",
    `--node ${spec.nodeId}`,
    `--lane ${spec.lane}`,
    options.pipelineRun === null ? null : `--pipeline-run ${options.pipelineRun}`,
    options.sourceCommit === null ? null : `--source-commit ${options.sourceCommit}`,
    full ? "--full" : null,
  ].filter(Boolean).join(" ");
}

function deploymentReplicaSummary(manager: Record<string, unknown>): string {
  const replicas = record(manager.replicas);
  const desired = displayValue(replicas.desired);
  const ready = displayValue(replicas.ready);
  const available = displayValue(replicas.available);
  return `${ready}/${desired} available=${available}`;
}

export function renderAgentRunControlPlaneStatusSummary(result: Record<string, unknown>): RenderedCliResult {
  const summary = record(result.summary);
  const target = record(result.target);
  const node = record(target.node);
  const runtimeTarget = record(target.runtime);
  const ciTarget = record(target.ci);
  const source = record(summary.source);
  const gitMirror = record(summary.gitMirror);
  const ci = record(summary.ci);
  const ciRun = record(ci.pipelineRun);
  const argo = record(summary.argo);
  const runtime = record(summary.runtime);
  const runtimeManager = record(runtime.manager);
  const nextAction = record(summary.nextAction);
  const next = record(result.next);
  const disclosure = record(result.disclosure);
  const timings = record(result.timings);
  const readCaptureBudget = record(result.readCaptureBudget);
  const blockers = Array.isArray(summary.blockers) ? summary.blockers.map(String) : [];
  const warnings = Array.isArray(summary.warnings) ? summary.warnings.map(String) : [];
  const migration = record(summary.migration);
  if (migration.migrated === true && migration.sourceAuthority === "gitea-pipelines-as-code") {
    const pac = record(summary.pac);
    const pacRead = record(pac.readObservation);
    const pacFirstBreak = record(pacRead.firstBreak);
    const latestPipelineRun = record(pac.latestPipelineRun);
    const artifact = record(pac.artifact);
    const taskRuns = Array.isArray(pac.taskRuns) ? pac.taskRuns.map(record) : [];
    const taskRows = taskRuns.slice(0, 8).map((taskRun) => [
      displayValue(taskRun.name),
      displayValue(taskRun.status),
      displayValue(taskRun.reason),
      displayValue(taskRun.durationSeconds),
    ]);
    const lines = [
      "AGENTRUN CONTROL-PLANE STATUS",
      renderTable(
        ["NODE", "LANE", "MODE", "SOURCE", "PIPELINE", "ALIGNED", "RUNTIME", "BLOCKERS"],
        [[
          displayValue(node.id ?? target.node ?? "-"),
          displayValue(target.lane ?? "-"),
          "PaC/Gitea",
          shortSha(summary.sourceCommit),
          displayValue(summary.expectedPipelineRun ?? "-"),
          yesNo(summary.aligned),
          yesNo(summary.runtimeAligned),
          blockers.length === 0 ? "-" : blockers.join(","),
        ]],
      ),
      "",
      renderTable(
        ["COMPONENT", "STATUS", "DETAIL"],
        [
          ["trigger", yesNo(pac.ready), `webhooks=${displayValue(pac.webhookCount)} path=${displayValue(migration.triggerPath)}`],
          ["pipelinerun", yesNo(latestPipelineRun.status === "True"), `run=${displayValue(latestPipelineRun.name)} status=${displayValue(latestPipelineRun.status)} reason=${displayValue(latestPipelineRun.reason)} duration=${displayValue(latestPipelineRun.durationSeconds)}s`],
          ["image", yesNo(artifact.imageStatus === "reused" || artifact.imageStatus === "built"), `status=${displayValue(artifact.imageStatus)} env=${displayValue(artifact.envIdentity)} digest=${shortSha(artifact.digest)} gitops=${shortSha(artifact.gitopsCommit)}`],
          ["argo", yesNo(argo.syncedToGitops), `sync=${displayValue(argo.syncStatus)} health=${displayValue(argo.healthStatus)} revision=${shortSha(argo.revision)}`],
          ["runtime", yesNo(runtimeManager.ready === true && runtimeManager.sourceMatchesExpected === true), `manager=${shortSha(runtimeManager.sourceCommit)} ready=${yesNo(runtimeManager.ready)} replicas=${deploymentReplicaSummary(runtimeManager)} secrets=${yesNo(runtime.secretsReady)} dbSecret=${yesNo(runtime.databaseSecretPresent)} localPgAbsent=${yesNo(runtime.localPostgresAbsent)}`],
        ],
      ),
      "",
      "TASKRUN DURATIONS",
      taskRows.length === 0 ? "-" : renderTable(["TASKRUN", "STATUS", "REASON", "DURATION_S"], taskRows),
      "",
      "PAC READ OBSERVATION",
      renderTable(["STAGE", "STATUS", "ELAPSED_MS", "TIMEOUT_MS", "EXIT", "FIRST_BREAK"], [[
        displayValue(pacRead.stage),
        displayValue(pacRead.status),
        displayValue(pacRead.elapsedMs),
        displayValue(pacRead.timeoutMs),
        displayValue(pacRead.exitCode),
        displayValue(pacFirstBreak.code),
      ]]),
      `  budget-config: ${displayValue(pacRead.configPath)}`,
      `  next-read-only: ${displayValue(pacRead.next)}`,
      "",
      "AGENTRUN READ CAPTURE BUDGET",
      renderTable(["TIMEOUT_MS", "CONFIG_PATH"], [[
        displayValue(readCaptureBudget.timeoutMs),
        displayValue(readCaptureBudget.configPath),
      ]]),
      "",
      warnings.length === 0 ? "WARNINGS\n-" : ["WARNINGS", ...warnings.map((warning) => `- ${warning}`)].join("\n"),
      "",
      "NEXT",
      `  action: ${displayValue(nextAction.summary ?? "-")}`,
      nextAction.command ? `  run: ${displayValue(nextAction.command)}` : null,
      `  pac: ${displayValue(next.pacStatus ?? disclosure.pacStatusCommand ?? "-")}`,
      `  full: ${displayValue(next.statusFull ?? disclosure.fullCommand ?? "bun scripts/cli.ts agentrun control-plane status --full")}`,
      "",
      `TIMINGS pac=${displayValue(timings.pacMs)}ms runtime=${displayValue(timings.runtimeMs)}ms total=${displayValue(timings.totalMs)}ms`,
    ].filter((line): line is string => line !== null);
    return renderedCliResult(result.ok !== false, "agentrun control-plane status", `${lines.join("\n")}\n`);
  }
  const sourceSnapshotMode = source.sourceAuthority === "git-mirror-snapshot" || source.snapshotPresent !== null;
  const sourceStatus = sourceSnapshotMode ? yesNo(source.snapshotPresent) : yesNo(source.workspaceClean);
  const sourceDetail = sourceSnapshotMode
    ? `branch=${displayValue(source.branch)} commit=${shortSha(source.remoteBranchCommit)} snapshot=${yesNo(source.snapshotPresent)} stage=${displayValue(source.sourceStageRef ?? "-")}`
    : `branch=${displayValue(source.branch)} commit=${shortSha(source.remoteBranchCommit)} detached=${yesNo(source.workspaceDetached)}`;
  const lines = [
    "AGENTRUN CONTROL-PLANE STATUS",
    renderTable(
      ["NODE", "LANE", "NAMESPACE", "SOURCE", "PIPELINE", "ALIGNED", "RUNTIME", "BLOCKERS"],
      [[
        displayValue(node.id ?? target.node ?? "-"),
        displayValue(target.lane ?? "-"),
        displayValue(runtimeTarget.namespace ?? "-"),
        shortSha(summary.sourceCommit),
        displayValue(summary.expectedPipelineRun ?? "-"),
        yesNo(summary.aligned),
        yesNo(summary.runtimeAligned),
        blockers.length === 0 ? "-" : blockers.join(","),
      ]],
    ),
    "",
    renderTable(
      ["COMPONENT", "STATUS", "DETAIL"],
      [
        ["source", sourceStatus, sourceDetail],
        ["git-mirror", yesNo(gitMirror.alreadySynced), `read=${yesNo(gitMirror.readReady)} write=${yesNo(gitMirror.writeReady)} gitops=${shortSha(gitMirror.gitopsCommit)}`],
        ["ci", yesNo(ciRun.status === "True"), `run=${displayValue(ciRun.name)} status=${displayValue(ciRun.status)} reason=${displayValue(ciRun.reason)} evidenceMissing=${yesNo(ci.evidenceMissing)}`],
        ["argo", yesNo(argo.syncedToGitops), `sync=${displayValue(argo.syncStatus)} health=${displayValue(argo.healthStatus)} revision=${shortSha(argo.revision)}`],
        ["runtime", yesNo(runtimeManager.ready === true && runtimeManager.sourceMatchesExpected === true), `manager=${shortSha(runtimeManager.sourceCommit)} ready=${yesNo(runtimeManager.ready)} replicas=${deploymentReplicaSummary(runtimeManager)} secrets=${yesNo(runtime.secretsReady)} dbSecret=${yesNo(runtime.databaseSecretPresent)} localPgAbsent=${yesNo(runtime.localPostgresAbsent)}`],
      ],
    ),
    "",
    "AGENTRUN READ CAPTURE BUDGET",
    renderTable(["TIMEOUT_MS", "CONFIG_PATH"], [[
      displayValue(readCaptureBudget.timeoutMs),
      displayValue(readCaptureBudget.configPath),
    ]]),
    "",
    warnings.length === 0 ? "WARNINGS\n-" : ["WARNINGS", ...warnings.map((warning) => `- ${warning}`)].join("\n"),
    "",
    "NEXT",
    `  action: ${displayValue(nextAction.summary ?? "-")}`,
    nextAction.command ? `  run: ${displayValue(nextAction.command)}` : null,
    `  full: ${displayValue(next.statusFull ?? disclosure.fullCommand ?? "bun scripts/cli.ts agentrun control-plane status --full")}`,
    "",
    `TIMINGS source=${displayValue(timings.sourceMs)}ms runtime=${displayValue(timings.runtimeMs)}ms gitMirror=${displayValue(timings.gitMirrorMs)}ms total=${displayValue(timings.totalMs)}ms`,
  ].filter((line): line is string => line !== null);
  return renderedCliResult(result.ok !== false, "agentrun control-plane status", `${lines.join("\n")}\n`);
}

export function renderAgentRunControlPlanePlanSummary(result: Record<string, unknown>): RenderedCliResult {
  const target = record(result.target);
  const node = record(target.node);
  const runtime = record(target.runtime);
  const source = record(target.source);
  const checks = Array.isArray(result.plannedChecks) ? result.plannedChecks.map(String) : [];
  const lines = [
    "AGENTRUN CONTROL-PLANE PLAN",
    renderTable(
      ["NODE", "LANE", "NAMESPACE", "SOURCE", "CHECKS", "MUTATION"],
      [[
        displayValue(node.id ?? target.node ?? "-"),
        displayValue(target.lane ?? "-"),
        displayValue(runtime.namespace ?? "-"),
        displayValue(source.branch ?? "-"),
        String(checks.length),
        "false",
      ]],
    ),
    "",
    "CHECKS",
    ...(checks.length === 0 ? ["-"] : checks.slice(0, 12).map((check) => `- ${check}`)),
    "",
    "NEXT",
    ...renderNextObjectLines(record(result.next)),
    "",
    "DETAIL",
    "  use --full for rendered target details; valuesPrinted=false",
  ];
  return renderedCliResult(result.ok !== false, "agentrun control-plane plan", `${lines.join("\n")}\n`);
}

export function renderAgentRunControlPlaneActionSummary(result: Record<string, unknown>, title: string): RenderedCliResult {
  const target = record(result.target);
  const node = record(target.node);
  const runtime = record(target.runtime);
  const summaryRows = [
    ["ok", String(result.ok !== false)],
    ["mode", displayValue(result.mode ?? "-")],
    ["dryRun", displayValue(result.dryRun ?? "-")],
    ["mutation", displayValue(result.mutation ?? "-")],
    ["namespace", displayValue(result.namespace ?? runtime.namespace ?? "-")],
  ];
  if (result.degradedReason !== undefined) summaryRows.push(["degradedReason", displayValue(result.degradedReason)]);
  const plan = record(result.plan);
  const secretRefs = record(plan.secretRefs);
  const providerCredentials = Array.isArray(plan.providerCredentials) ? plan.providerCredentials.map(record) : [];
  const planRows = [
    ["deployment", displayValue(plan.deployment ?? "-")],
    ["annotation", displayValue(plan.annotation ?? "-")],
    ["patchType", displayValue(plan.patchType ?? "-")],
    ["reason", displayValue(plan.reason ?? "-")],
    ["secretRefCount", displayValue(secretRefs.count ?? "-")],
    ["providerCredentialCount", displayValue(providerCredentials.length === 0 ? "-" : providerCredentials.length)],
  ].filter((row) => row[1] !== "-");
  const countKeys = [
    "runnerJobCount",
    "overLimitCount",
    "inactiveCandidateCount",
    "managerRetentionCandidateCount",
    "stalePendingObservationCount",
    "selectedRunnerJobCount",
    "protectedActiveRunnerCount",
    "remainingAfterSelection",
    "deletedRunnerJobCount",
    "nonTerminalRunnerPodCount",
    "nonTerminalPodCount",
    "nextRunnerRequiresManagerRetention",
  ];
  const countRows = countKeys
    .filter((key) => result[key] !== undefined)
    .map((key) => [key, displayValue(result[key])]);
  const capacity = record(result.capacity);
  const capacityBlocker = record(capacity.blocker);
  const capacityCurrent = capacity.runnerJobCount ?? result.runnerJobCount;
  const capacityLimit = capacity.maxRunners;
  const capacityRows = [
    ["usage", capacityCurrent === undefined || capacityLimit === undefined ? "-" : `${String(capacityCurrent)}/${String(capacityLimit)}`],
    ["status", displayValue(capacity.status ?? "-")],
    ["availableSlots", displayValue(capacity.availableSlots ?? "-")],
    ["blockerType", displayValue(capacityBlocker.type ?? "-")],
    ["blockerCode", displayValue(capacityBlocker.code ?? "-")],
  ].filter((row) => row[1] !== "-");
  const lines = [
    title,
    renderTable(
      ["NODE", "LANE", "NAMESPACE", "MODE", "OK"],
      [[
        displayValue(node.id ?? target.node ?? "-"),
        displayValue(target.lane ?? "-"),
        displayValue(result.namespace ?? runtime.namespace ?? "-"),
        displayValue(result.mode ?? "-"),
        String(result.ok !== false),
      ]],
    ),
    "",
    renderTable(["FIELD", "VALUE"], summaryRows),
  ];
  if (planRows.length > 0) lines.push("", "PLAN", renderTable(["FIELD", "VALUE"], planRows));
  if (providerCredentials.length > 0) {
    lines.push(
      "",
      "PROVIDER CREDENTIALS",
      renderTable(["PROFILE", "NAMESPACE", "SECRET", "KEYS", "VALUES"], providerCredentials.slice(0, 8).map((credential) => [
        displayValue(credential.profile),
        displayValue(credential.namespace),
        displayValue(credential.name),
        Array.isArray(credential.keys) ? credential.keys.map(String).join(",") : "-",
        "false",
      ])),
    );
  }
  if (capacityRows.length > 0) lines.push("", "CAPACITY", renderTable(["FIELD", "VALUE"], capacityRows));
  if (countRows.length > 0) lines.push("", renderTable(["COUNT", "VALUE"], countRows));
  const cleanupRows = (value: unknown): string[][] => (Array.isArray(value) ? value : [])
    .slice(0, 4)
    .map(record)
    .map((item) => {
      const drillDown = record(item.drillDown);
      return [
        displayValue(item.runnerJobId ?? item.name ?? "-"),
        Array.isArray(item.podPhases) ? item.podPhases.map(String).join(",") || "-" : "-",
        Array.isArray(item.waitingReasons) ? item.waitingReasons.map(String).join(",") || "-" : "-",
        displayValue(item.lastActiveAgeMs ?? "-"),
        displayValue(item.classification ?? item.protectedReason ?? "-"),
        displayValue(drillDown.runnerJob ?? "-"),
      ];
    });
  const candidates = cleanupRows(result.candidates);
  const selected = cleanupRows(result.selected);
  if (candidates.length > 0) {
    lines.push("", `CANDIDATES (showing ${String(candidates.length)}, omitted=${displayValue(result.candidateOmittedCount ?? 0)})`, renderTable(["RUNNERJOB", "POD PHASE", "WAITING", "AGE MS", "CLASSIFICATION", "DRILL-DOWN"], candidates));
  }
  if (selected.length > 0) {
    lines.push("", `SELECTED (showing ${String(selected.length)}, omitted=${displayValue(result.selectedOmittedCount ?? 0)})`, renderTable(["RUNNERJOB", "POD PHASE", "WAITING", "AGE MS", "CLASSIFICATION", "DRILL-DOWN"], selected));
  }
  const nextLines = renderNextObjectLines(record(result.next ?? result.followUp));
  if (nextLines.length > 0) lines.push("", "NEXT", ...nextLines);
  lines.push("", "DETAIL", "  use --full for capture/probe details; valuesPrinted=false");
  return renderedCliResult(result.ok !== false, String(result.command ?? "agentrun control-plane"), `${lines.join("\n")}\n`);
}
