// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. help module for scripts/src/ci.ts.
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.

// Moved mechanically from scripts/src/ci.ts:3273-3409 for #903.

import { randomUUID } from "node:crypto";
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, posix as posixPath } from "node:path";
import { blockedCatalogArtifactIds, catalogSummary, findCiCatalogArtifact, loadCiCatalog, supportedSourceBuildArtifactIds, type CiCatalogArtifact, type CiSourceBuildCatalogArtifact, type CiUpstreamImageCatalogArtifact } from "../ci-catalog";
import { runCommand } from "../command";
import { type UniDeskConfig, repoRoot, rootPath } from "../config";
import { ensureGithubSshIdentityForProvider, gitSshHttpConnectProxySource } from "../deploy-ssh-identity";
import { jobWithTail, listJobs, readJob, startJob } from "../jobs";
import { coreInternalFetch } from "../microservices";
import {
  artifactRegistryReadonlyResultFromCommand,
  buildArtifactRegistryReadonlyProbe,
  parseArtifactRegistryOptions,
  type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";

import { g14CiPipelineManifest, tektonPipelineReleaseUrl, tektonPipelineVersion, tektonTriggersInterceptorsUrl, tektonTriggersReleaseUrl, tektonTriggersVersion } from "./types";

export function catalogArtifactDescriptor(artifact: CiCatalogArtifact): Record<string, unknown> {
  if (artifact.kind === "source-build") {
    return {
      serviceId: artifact.serviceId,
      kind: artifact.kind,
      status: artifact.status,
      producer: artifact.producer,
      source: artifact.source,
      image: artifact.image,
      ...(artifact.notes === undefined ? {} : { notes: artifact.notes }),
      ...(artifact.blockedReason === undefined ? {} : { blockedReason: artifact.blockedReason }),
    };
  }
  return {
    serviceId: artifact.serviceId,
    kind: artifact.kind,
    status: artifact.status,
    producer: artifact.producer,
    upstream: artifact.upstream,
    blockedReason: artifact.blockedReason,
    ...(artifact.notes === undefined ? {} : { notes: artifact.notes }),
  };
}

export function ciHelp(): Record<string, unknown> {
  const catalog = loadCiCatalog();
  return {
    command: "ci install|status|github-ssh-identity|run|publish-backend-core|publish-user-service|run-dev-e2e|logs|cleanup-runs|cleanup-failed-pods",
    description: "Manage native k3s Tekton CI on D601 or G14. CI may publish commit-pinned image artifacts, but it intentionally does not deploy CD.",
    examples: [
      "bun scripts/cli.ts ci install",
      "bun scripts/cli.ts ci install --skip-prewarm",
      "bun scripts/cli.ts ci install --skip-prewarm --skip-tekton-install",
      "bun scripts/cli.ts ci install-status latest",
      "bun scripts/cli.ts ci install --wait --skip-prewarm --skip-tekton-install",
      "bun scripts/cli.ts ci plan --target D601",
      "bun scripts/cli.ts ci github-ssh-identity plan --target D518",
      "bun scripts/cli.ts ci github-ssh-identity ensure --target D518 --confirm",
      "bun scripts/cli.ts ci install --target G14",
      "bun scripts/cli.ts ci run --revision <commit>",
      "bun scripts/cli.ts ci run --target G14 --revision <commit>",
      "bun scripts/cli.ts ci publish-backend-core --commit <full-sha>",
      "bun scripts/cli.ts ci publish-user-service --service baidu-netdisk --commit <full-sha>",
      "bun scripts/cli.ts ci publish-user-service --service mdtodo --commit <full-sha>",
      "bun scripts/cli.ts ci publish-user-service --service claudeqq --commit <full-sha>",
      "bun scripts/cli.ts ci publish-user-service --service code-queue --commit <full-sha>",
      "bun scripts/cli.ts ci publish-user-service --service decision-center --commit <full-sha>",
      "bun scripts/cli.ts ci publish-user-service --service frontend --commit <full-sha>",
      "bun scripts/cli.ts ci run-dev-e2e --wait-ms 600000",
      "bun scripts/cli.ts ci logs <runId-or-pipelineRun> [--target G14] [--tail-lines 80]",
      "bun scripts/cli.ts ci cleanup-runs --target D601 --min-age-minutes 60 --limit 50 --dry-run",
      "bun scripts/cli.ts ci cleanup-runs --target D601 --min-age-minutes 60 --limit 50 --confirm",
      "bun scripts/cli.ts ci cleanup-failed-pods --target D601 --namespace unidesk-ci --min-age-minutes 60 --dry-run",
      "bun scripts/cli.ts ci cleanup-failed-pods --target D601 --namespace unidesk-ci --min-age-minutes 60 --confirm",
    ],
    tekton: {
      pipelineVersion: tektonPipelineVersion,
      triggersVersion: tektonTriggersVersion,
      sources: {
        pipeline: tektonPipelineReleaseUrl,
        triggers: tektonTriggersReleaseUrl,
        interceptors: tektonTriggersInterceptorsUrl,
      },
      targets: {
        default: "D601",
        g14: {
          providerId: "G14",
          pipelineManifest: g14CiPipelineManifest,
          nodeSelector: "unidesk.ai/node-id=G14",
        },
      },
    },
    install: {
      defaultMode: "async-job",
      waitFlag: "Use --wait only for explicit synchronous debugging; the default returns a /root/.unidesk/.state/jobs job immediately with install-status follow-up.",
      statusCommand: "bun scripts/cli.ts ci install-status <jobId|latest>",
      prewarmDefault: true,
      skipPrewarm: "Use --skip-prewarm only to refresh Tekton/CI manifests when runtime images are already present or prewarm is blocked by provider root/containerd permissions.",
      skipTektonInstall: "Use --skip-tekton-install with --skip-prewarm only when Tekton is already installed and only UniDesk CI manifests/triggers need refreshing.",
    },
    backendCoreArtifact: {
      producer: "D601 CI",
      registry: "127.0.0.1:5000/unidesk/backend-core:<commit>",
      cdCommand: "bun scripts/cli.ts deploy apply --env prod --service backend-core --commit <full-sha>",
    },
    userServiceArtifact: {
      producer: "D601 CI",
      command: "bun scripts/cli.ts ci publish-user-service --service <service-id> --commit <full-sha>",
      transports: {
        auto: "uses direct-docker for repo-owned code-queue artifacts and Tekton for the remaining services",
        tekton: "D601 Tekton PipelineRun through backend-core/provider control plane",
        directDocker: "repo-owned Docker artifact publish without backend-core/database dispatch; no deploy apply, rollout, restart, or active-task mutation",
      },
      supportedServices: supportedSourceBuildArtifactIds("ci publish-user-service"),
      blockedServices: blockedCatalogArtifactIds(),
      registry: "127.0.0.1:5000/unidesk/<service-id>:<commit>",
      outputFields: ["serviceId", "sourceCommit", "sourceRepo", "dockerfile", "imageRef", "tag", "digest", "digestRef"],
      summaryContract: catalog.summaryContract,
      catalogSummary: catalogSummary(),
      catalog: catalog.artifacts.map(catalogArtifactDescriptor),
      boundary: "artifact producer only; no prod deploy and no production namespace mutation",
      frontendNext: [
        "bun scripts/cli.ts deploy apply --env dev --service frontend --commit <full-sha>",
        "bun scripts/cli.ts deploy apply --env prod --service frontend --commit <full-sha>",
      ],
    },
    runDevE2E: {
      defaultTriggerMode: "commit-pinned-ssh-launcher",
      desiredState: "origin/master:deploy.json#environments.dev",
      scriptSource: "origin/master:deploy.json#environments.dev.ci",
    },
    logs: {
      defaultCapture: "ssh-stream",
      dispatchTaskFallback: "--dispatch-task",
      tailLines: "1..2000",
      reason: "stream capture avoids backend-core task-result compactJson truncating CI log text before the CLI can extract failure hints",
    },
    cleanupRuns: {
      defaultMode: "dry-run",
      namespace: "unidesk-ci",
      selector: "status.phase in Succeeded,Failed",
      guard: "--confirm required for deletion; Running/Pending pods are never selected",
    },
    cleanupFailedPods: {
      defaultNamespace: "unidesk-ci",
      defaultMinAgeMinutes: 60,
      defaultLimit: 50,
      scope: "Kubernetes pods with status.phase=Failed only; does not touch Running/Pending pods, workloads, PVCs, Secrets, or Tekton resources",
      defaultMode: "dry-run",
    },
  };
}

export function requireRunId(value: string): string {
  if (!/^[a-z0-9]([-a-z0-9]{0,46}[a-z0-9])?$/u.test(value)) {
    throw new Error("ci run-dev-e2e run id must be DNS-safe lowercase alnum/dash, max 48 chars");
  }
  return value;
}
