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

// Moved mechanically from scripts/src/artifact-registry.ts:1-248 for #903.

import { createHash } from "node:crypto";
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { runCommand, type CommandResult } from "../command";
import { readConfig, type UniDeskConfig, repoRoot, rootPath } from "../config";
import { startJob } from "../jobs";
import {
  compareDeployJsonExecutorMirrors,
  deployJsonCommitImage,
  deployJsonDriftResult,
  deployJsonSourceOfTruth,
  hasDeployJsonExecutorContract,
  k3sManifestExecutorMirror,
  parseDeployJsonServiceContractBase64,
  readDeployJsonServiceContractFromFile,
  type DeployJsonExecutorMirror,
  type DeployJsonServiceContract,
} from "../deploy-json-contract";
import { k3sGuardShellLines } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import { artifactRegistryTargetSummary, resolveArtifactRegistryTarget, type TargetConfigSource } from "../ops/targets";

import { composeFile, sha256 } from "./bundle";

export type ArtifactRegistryAction = "plan" | "render" | "status" | "health" | "install" | "deploy-backend-core" | "deploy-service";

export type ArtifactDeployEnvironment = "prod" | "dev";

export interface ArtifactRegistryOptions {
  targetId: string;
  environment: ArtifactDeployEnvironment | null;
  providerId: string;
  mode: string;
  host: string;
  port: number;
  image: string;
  repositoryPrefix: string;
  baseDir: string;
  storageDir: string;
  unitName: string;
  composeProject: string;
  serviceName: string;
  containerName: string;
  timeoutMs: number;
  dryRun: boolean;
  runNow: boolean;
  commit: string | null;
  targetImage: string | null;
  serviceId: string | null;
  sourceRepo: string;
  sourceRepoExplicit: boolean;
  deployRef: string | null;
  deployJsonService: DeployJsonServiceContract | null;
  consumerTarget: Record<string, unknown>;
  consumerCatalogRef: string;
  configSource: TargetConfigSource;
}

export interface ArtifactRegistryCommandRuntime {
  env?: NodeJS.ProcessEnv;
  runRemoteScriptForTest?: (options: ArtifactRegistryOptions, script: string, timeoutMs: number) => CommandResult;
  runCliForTest?: (command: string[], timeoutMs: number) => CommandResult;
}

export interface RenderedFile {
  path: string;
  mode: string;
  sha256: string;
  content: string;
}

export interface RenderedBundle {
  files: RenderedFile[];
  paths: {
    unit: string;
    compose: string;
    config: string;
    storage: string;
    baseDir: string;
  };
}

export interface ArtifactRegistryReadonlyProbe {
  action: "status" | "health";
  providerId: string;
  script: string;
  timeoutMs: number;
  healthMode: boolean;
  options: ArtifactRegistryOptions;
  remoteCommandShape: string;
}

export type ArtifactRegistryFailureClassification =
  | "control-plane-missing"
  | "local-docker-required"
  | "provider-ssh-command-missing"
  | "ssh-helper-command-shape-incompatible"
  | "remote-command-timeout"
  | "registry-not-installed"
  | "registry-unhealthy";

export function artifactRegistryOptionsFromTarget(target = resolveArtifactRegistryTarget(null)): ArtifactRegistryOptions {
  return {
    targetId: target.targetId,
    environment: null,
    providerId: target.providerId,
    mode: target.mode,
    host: target.host,
    port: target.port,
    image: target.image,
    repositoryPrefix: target.repositoryPrefix,
    baseDir: target.baseDir,
    storageDir: target.storageDir,
    unitName: target.unitName,
    composeProject: target.composeProject,
    serviceName: target.serviceName,
    containerName: target.containerName,
    timeoutMs: target.timeoutMs,
    dryRun: false,
    runNow: false,
    commit: null,
    targetImage: null,
    serviceId: null,
    sourceRepo: target.sourceRepo,
    sourceRepoExplicit: false,
    deployRef: null,
    deployJsonService: null,
    consumerTarget: target.consumerTarget,
    consumerCatalogRef: target.consumerCatalogRef,
    configSource: target.configSource,
  };
}

export const defaultOptions: ArtifactRegistryOptions = artifactRegistryOptionsFromTarget();

export function artifactRegistryOptionsTargetSummary(options: ArtifactRegistryOptions): Record<string, unknown> {
  return artifactRegistryTargetSummary({
    targetId: options.targetId,
    providerId: options.providerId,
    mode: options.mode,
    host: options.host,
    port: options.port,
    endpoint: `http://${options.host}:${options.port}`,
    image: options.image,
    repositoryPrefix: options.repositoryPrefix,
    baseDir: options.baseDir,
    storageDir: options.storageDir,
    unitName: options.unitName,
    composeProject: options.composeProject,
    serviceName: options.serviceName,
    containerName: options.containerName,
    timeoutMs: options.timeoutMs,
    sourceRepo: options.sourceRepo,
    consumerTarget: options.consumerTarget,
    consumerCatalogRef: options.consumerCatalogRef,
    configSource: options.configSource,
  });
}

export const supportedArtifactConsumerServices = [
  "auth-broker",
  "backend-core",
  "baidu-netdisk",
  "claudeqq",
  "code-queue",
  "code-queue-mgr",
  "decision-center",
  "findjob",
  "frontend",
  "k3sctl-adapter",
  "mdtodo",
  "met-nonlinear",
  "oa-event-flow",
  "pipeline",
  "project-manager",
] as const;

export type SupportedArtifactConsumerService = typeof supportedArtifactConsumerServices[number];

export const legacyDeployBackendCoreDisabled = true;

export interface ArtifactConsumerSpec {
  serviceId: SupportedArtifactConsumerService;
  environment?: ArtifactDeployEnvironment;
  kind: "compose" | "d601-compose" | "d601-k3s";
  registryRepository: string;
  sourceRepo?: string;
  dockerfile: string;
  targets: Partial<Record<ArtifactDeployEnvironment, ArtifactConsumerTarget>>;
  prodLiveApply: "enabled" | "supervisor-only" | "unsupported";
  prodLiveBlockReason?: string;
  devLiveApply?: "enabled" | "supervisor-only";
  devLiveBlockReason?: string;
  runtimeVerification?: "strict" | "blocked";
  runtimeVerificationBlockReason?: string;
}

export interface ArtifactConsumerTarget {
  targetImage: string;
  targetCommitImage: (commit: string) => string;
  deployRef: string;
  compose?: {
    serviceName: string;
    containerName: string;
    deployEnvPrefix: string;
    healthProbeCommand: string;
    requireHealthCommit: boolean;
    workDir?: string;
    composeFile?: string;
    projectHint?: string;
    requiredRuntimeSecrets?: RuntimeSecretRequirement[];
    authHealthGate?: AuthHealthGate;
    syntheticHealthDeployProof?: SyntheticHealthDeployProof;
  };
  k3s?: {
    namespace: string;
    manifestRepoPath: string;
    deploymentName: string;
    serviceName: string;
    servicePort: number;
    containerName: string;
    healthPath: string;
    extraDeployments?: Array<string | { name: string; containerName?: string }>;
    applySelector?: string;
    podLabelSelector?: string;
  };
}

export interface RuntimeSecretRequirement {
  sourceEnvName: string;
  containerEnvName: string;
}

export interface AuthHealthGate {
  requiredFields: string[];
  recoveryHint: string;
}

export interface SyntheticHealthDeployProof {
  kind: "compose-container-runtime-metadata";
  description: string;
}

export interface ComposeArtifactRuntime {
  workDir: string;
  composeFile: string;
  composeEnvFile: string;
  envFile: string;
  project: string;
  command: string[];
}

export interface RuntimeSecretPresence {
  sourceEnvName: string;
  containerEnvName: string;
  present: boolean;
  length: number;
}

export interface RuntimeSecretSource {
  kind: "compose-env-file";
  path: string;
  exists: boolean;
  configSource: "config.providerGateway.upgrade.hostProjectRoot + config.providerGateway.upgrade.composeEnvFile";
  workDir: string;
  composeEnvFile: string;
  composeService: string | null;
  containerName: string | null;
  valuesPrinted: false;
}

export interface RuntimeSecretRequirementStatus {
  sourceEnvName: string;
  containerEnvName: string;
  present: boolean;
  valuePrinted: false;
}

export interface RuntimeSecretContract {
  check: "runtime-secret-presence";
  secretSource: RuntimeSecretSource;
  requiredSecretsPresent: boolean;
  missingSecretKeys: string[];
  recommendedAction: string;
  valuesPrinted: false;
  requirements: RuntimeSecretRequirementStatus[];
  dryRunDisposition: "not-required" | "ready-for-live-apply" | "secret-source-blocked";
}
