// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. options module for scripts/src/platform-infra-sub2api-codex.ts.

// Moved mechanically from scripts/src/platform-infra-sub2api-codex.ts:320-620 for #903.

import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import type { UniDeskConfig } from "../config";
import { rootPath } from "../config";
import type { RenderedCliResult } from "../output";
import { applyPk01CaddyBlock, prepareFrpcSecret, renderFrpcManifest, type PublicServiceExposure, type PublicServiceTarget } from "../platform-infra-public-service";
import { shortSha256Fingerprint } from "../platform-infra-ops-library";
import {
  codexPoolSentinelSummary,
  codexPoolSentinelRuntimeImage,
  readCodexPoolSentinelConfig,
  renderCodexPoolSentinelManifest,
  type CodexPoolSentinelConfig,
  type CodexPoolSentinelProfileSecret,
} from "../platform-infra-sub2api-codex-sentinel";
import { parseEnvFile, readTextFile, redactRepoPath, requiredEnvValue } from "../secrets";
import { runSshCommandCapture, type SshCaptureResult } from "../ssh";

import type { ConfirmOptions, DisclosureOptions, FeedbackOptions, SentinelImageOptions, SentinelProbeOptions, SentinelReportOptions, SyncOptions, TraceOptions } from "./types";
import { codexPoolCleanupProbes, codexPoolConfigureLocal, codexPoolExpose, codexPoolPlan, codexPoolSentinelImage, codexPoolSentinelProbe, codexPoolSentinelReport, codexPoolSync, codexPoolTrace, codexPoolValidate } from "./actions";
import { codexPoolFeedback } from "./feedback";
import { renderCodexPoolPlan } from "./render";
import { codexPoolRuntime } from "./runtime";
import { codexPoolFaults } from "./faults";
import { codexPoolProfit } from "./profit";
import { codexPoolCredits } from "./credits";
import { codexPoolAnnouncements } from "./announcements";
import { codexPoolErrorPassthrough } from "./error-passthrough";
import { codexPoolUsers } from "./users";
import { defaultCodexPoolRuntimeTargetId } from "./runtime-target";
import { codexPoolHelp } from "./types";

export async function runCodexPoolCommand(config: UniDeskConfig, args: string[]): Promise<Record<string, unknown> | RenderedCliResult> {
  const [action = "plan"] = args;
  if (action === "plan") {
    const options = parseDisclosureOptions(args.slice(1));
    const result = codexPoolPlan(options);
    return options.full || options.raw ? result : renderCodexPoolPlan(result);
  }
  if (action === "sync") return await codexPoolSync(config, parseSyncOptions(args.slice(1)));
  if (action === "validate") return await codexPoolValidate(config, parseDisclosureOptions(args.slice(1)));
  if (action === "runtime") return await codexPoolRuntime(config, args.slice(1));
  if (action === "faults") return await codexPoolFaults(config, args.slice(1));
  if (action === "profit") return await codexPoolProfit(config, args.slice(1));
  if (action === "credits") return await codexPoolCredits(config, args.slice(1));
  if (action === "announcements") return await codexPoolAnnouncements(config, args.slice(1));
  if (action === "error-passthrough") return await codexPoolErrorPassthrough(config, args.slice(1));
  if (action === "users") return await codexPoolUsers(config, args.slice(1));
  if (action === "trace") return await codexPoolTrace(config, parseTraceOptions(args.slice(1)));
  if (action === "feedback") return await codexPoolFeedback(config, parseFeedbackOptions(args.slice(1)));
  if (action === "sentinel-image") return await codexPoolSentinelImage(config, parseSentinelImageOptions(args.slice(1)));
  if (action === "sentinel-probe") return await codexPoolSentinelProbe(config, parseSentinelProbeOptions(args.slice(1)));
  if (action === "sentinel-report") return await codexPoolSentinelReport(config, parseSentinelReportOptions(args.slice(1)));
  if (action === "cleanup-probes") return await codexPoolCleanupProbes(config, parseConfirmOptions(args.slice(1)));
  if (action === "expose") return await codexPoolExpose(config, parseConfirmOptions(args.slice(1)));
  if (action === "configure-local") return await codexPoolConfigureLocal(config, parseConfirmOptions(args.slice(1)));
  return {
    ok: false,
    error: "unsupported-platform-infra-sub2api-codex-pool-command",
    args,
    help: codexPoolHelp(),
  };
}

export function parseSyncOptions(args: string[]): SyncOptions {
  validateOptions(args, new Set(["--confirm", "--prune-removed", "--full", "--raw", "--target"]));
  const disclosure = parseDisclosureOptions(stripBooleanOptions(args, new Set(["--confirm", "--prune-removed"])));
  return { ...disclosure, confirm: args.includes("--confirm"), pruneRemoved: args.includes("--prune-removed") };
}

export function parseConfirmOptions(args: string[]): ConfirmOptions {
  validateOptions(args, new Set(["--confirm", "--full", "--raw", "--target"]));
  const disclosure = parseDisclosureOptions(stripBooleanOptions(args, new Set(["--confirm"])));
  return { ...disclosure, confirm: args.includes("--confirm") };
}

export function parseSentinelImageOptions(args: string[]): SentinelImageOptions {
  const [actionRaw = "status", ...rest] = args;
  if (actionRaw !== "status" && actionRaw !== "build") throw new Error("sentinel-image usage: status|build [--dry-run|--confirm] [--full|--raw]");
  let confirm = false;
  let explicitDryRun = false;
  const disclosureArgs: string[] = [];
  for (let index = 0; index < rest.length; index += 1) {
    const arg = rest[index]!;
    if (arg === "--confirm") {
      confirm = true;
      continue;
    }
    if (arg === "--dry-run") {
      explicitDryRun = true;
      continue;
    }
    if (arg === "--full" || arg === "--raw" || arg === "--target" || arg.startsWith("--target=")) {
      disclosureArgs.push(arg);
      if (arg === "--target") {
        const value = rest[index + 1];
        if (value === undefined || value.startsWith("--")) throw new Error("--target requires a value");
        disclosureArgs.push(value);
        index += 1;
      }
      continue;
    }
    throw new Error(`unsupported option: ${arg}`);
  }
  if (confirm && explicitDryRun) throw new Error("sentinel-image accepts only one of --confirm or --dry-run");
  const disclosure = parseDisclosureOptions(disclosureArgs);
  return {
    ...disclosure,
    action: actionRaw,
    confirm,
    dryRun: actionRaw === "status" ? true : explicitDryRun || !confirm,
  };
}

export function parseSentinelProbeOptions(args: string[]): SentinelProbeOptions {
  const accounts: string[] = [];
  const disclosureArgs: string[] = [];
  let confirm = false;
  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index]!;
    if (arg === "--confirm") {
      confirm = true;
      continue;
    }
    if (arg === "--full" || arg === "--raw" || arg === "--target" || arg.startsWith("--target=")) {
      disclosureArgs.push(arg);
      if (arg === "--target") {
        const value = args[index + 1];
        if (value === undefined || value.startsWith("--")) throw new Error("--target requires a value");
        disclosureArgs.push(value);
        index += 1;
      }
      continue;
    }
    if (arg === "--account") {
      const value = args[index + 1];
      if (value === undefined || value.startsWith("--")) throw new Error("--account requires an account name");
      accounts.push(...splitAccountNames(value));
      index += 1;
      continue;
    }
    if (arg.startsWith("--account=")) {
      accounts.push(...splitAccountNames(arg.slice("--account=".length)));
      continue;
    }
    throw new Error(`unsupported option: ${arg}`);
  }
  const uniqueAccounts = [...new Set(accounts)];
  if (uniqueAccounts.length === 0) throw new Error("sentinel-probe requires --account <accountName>");
  for (const account of uniqueAccounts) validateSub2ApiAccountSelector(account, "--account");
  const disclosure = parseDisclosureOptions(disclosureArgs);
  return { ...disclosure, confirm, accounts: uniqueAccounts };
}

export function parseSentinelReportOptions(args: string[]): SentinelReportOptions {
  let events = 20;
  let explicitEvents = false;
  const disclosureArgs: string[] = [];
  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index]!;
    if (arg === "--full" || arg === "--raw" || arg === "--target" || arg.startsWith("--target=")) {
      disclosureArgs.push(arg);
      if (arg === "--target") {
        const value = args[index + 1];
        if (value === undefined || value.startsWith("--")) throw new Error("--target requires a value");
        disclosureArgs.push(value);
        index += 1;
      }
      continue;
    }
    if (arg === "--events") {
      const value = args[index + 1];
      if (value === undefined || value.startsWith("--")) throw new Error("--events requires a positive integer");
      events = readReportEventLimit(value, "--events");
      explicitEvents = true;
      index += 1;
      continue;
    }
    if (arg.startsWith("--events=")) {
      events = readReportEventLimit(arg.slice("--events=".length), "--events");
      explicitEvents = true;
      continue;
    }
    throw new Error(`unsupported option: ${arg}`);
  }
  const disclosure = parseDisclosureOptions(disclosureArgs);
  if (disclosure.full && !explicitEvents) events = 80;
  return { ...disclosure, events };
}

export function parseTraceOptions(args: string[]): TraceOptions {
  let requestId: string | null = null;
  let pageToken: string | null = null;
  let since = "24h";
  let tail = 20_000;
  let contextSeconds = 300;
  let showLines = false;
  const disclosureArgs: string[] = [];
  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index]!;
    if (arg === "--full" || arg === "--raw" || arg === "--target" || arg.startsWith("--target=")) {
      disclosureArgs.push(arg);
      if (arg === "--target") {
        const value = args[index + 1];
        if (value === undefined || value.startsWith("--")) throw new Error("--target requires a value");
        disclosureArgs.push(value);
        index += 1;
      }
      continue;
    }
    if (arg === "--show-lines") {
      showLines = true;
      continue;
    }
    if (arg === "--request-id" || arg === "--id") {
      const value = args[index + 1];
      if (value === undefined || value.startsWith("--")) throw new Error(`${arg} requires a request id`);
      requestId = value.trim();
      index += 1;
      continue;
    }
    if (arg.startsWith("--request-id=")) {
      requestId = arg.slice("--request-id=".length).trim();
      continue;
    }
    if (arg.startsWith("--id=")) {
      requestId = arg.slice("--id=".length).trim();
      continue;
    }
    if (arg === "--since") {
      const value = args[index + 1];
      if (value === undefined || value.startsWith("--")) throw new Error("--since requires a kubectl duration such as 24h or 90m");
      since = parseKubectlDuration(value);
      index += 1;
      continue;
    }
    if (arg.startsWith("--since=")) {
      since = parseKubectlDuration(arg.slice("--since=".length));
      continue;
    }
    if (arg === "--tail") {
      const value = args[index + 1];
      if (value === undefined || value.startsWith("--")) throw new Error("--tail requires an integer");
      tail = parseTraceLimit(value, "--tail", 100, 200_000);
      index += 1;
      continue;
    }
    if (arg.startsWith("--tail=")) {
      tail = parseTraceLimit(arg.slice("--tail=".length), "--tail", 100, 200_000);
      continue;
    }
    if (arg === "--context-seconds") {
      const value = args[index + 1];
      if (value === undefined || value.startsWith("--")) throw new Error("--context-seconds requires an integer");
      contextSeconds = parseTraceLimit(value, "--context-seconds", 0, 3600);
      index += 1;
      continue;
    }
    if (arg.startsWith("--context-seconds=")) {
      contextSeconds = parseTraceLimit(arg.slice("--context-seconds=".length), "--context-seconds", 0, 3600);
      continue;
    }
    throw new Error(`unsupported option: ${arg}`);
  }
  if (requestId === null || requestId.length === 0) throw new Error("trace requires --request-id <requestId>");
  if (!/^[A-Za-z0-9_.:-]{8,128}$/u.test(requestId)) throw new Error("--request-id has an unsupported format");
  const disclosure = parseDisclosureOptions(disclosureArgs);
  return { ...disclosure, requestId, since, tail, contextSeconds, showLines };
}

export function parseFeedbackOptions(args: string[]): FeedbackOptions {
  let user: string | null = null;
  let window: FeedbackOptions["window"] = "30m";
  let requestId: string | null = null;
  let pageToken: string | null = null;
  let json = false;
  const targetArgs: string[] = [];
  const windows = new Set<FeedbackOptions["window"]>(["5m", "30m", "1h", "6h", "24h", "7d", "30d"]);
  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index]!;
    if (arg === "--json") {
      json = true;
      continue;
    }
    if (arg === "--target" || arg.startsWith("--target=")) {
      targetArgs.push(arg);
      if (arg === "--target") {
        const value = args[index + 1];
        if (value === undefined || value.startsWith("--")) throw new Error("--target requires a value");
        targetArgs.push(value);
        index += 1;
      }
      continue;
    }
    const readValue = (option: string): string => {
      const value = args[index + 1];
      if (value === undefined || value.startsWith("--")) throw new Error(`${option} requires a value`);
      index += 1;
      return value.trim();
    };
    if (arg === "--user") user = readValue(arg);
    else if (arg.startsWith("--user=")) user = arg.slice("--user=".length).trim();
    else if (arg === "--window") {
      const value = readValue(arg) as FeedbackOptions["window"];
      if (!windows.has(value)) throw new Error("--window must be one of 5m, 30m, 1h, 6h, 24h, 7d, 30d");
      window = value;
    } else if (arg.startsWith("--window=")) {
      const value = arg.slice("--window=".length) as FeedbackOptions["window"];
      if (!windows.has(value)) throw new Error("--window must be one of 5m, 30m, 1h, 6h, 24h, 7d, 30d");
      window = value;
    } else if (arg === "--request-id") requestId = readValue(arg);
    else if (arg.startsWith("--request-id=")) requestId = arg.slice("--request-id=".length).trim();
    else if (arg === "--page-token") pageToken = readValue(arg);
    else if (arg.startsWith("--page-token=")) pageToken = arg.slice("--page-token=".length).trim();
    else throw new Error(`unsupported option: ${arg}`);
  }
  if (user === null || user.length === 0) throw new Error("feedback requires --user <email-or-id>");
  if (user.length > 320 || /[\r\n]/u.test(user)) throw new Error("--user must be a single email or positive user id");
  if (requestId !== null && (requestId.length === 0 || requestId.length > 256 || /[\r\n]/u.test(requestId))) {
    throw new Error("--request-id must be a non-empty stable request id up to 256 characters");
  }
  if (pageToken !== null && (pageToken.length === 0 || pageToken.length > 1024 || !/^[A-Za-z0-9_-]+$/u.test(pageToken))) {
    throw new Error("--page-token must be a URL-safe cursor token");
  }
  if (requestId !== null && pageToken !== null) throw new Error("--request-id and --page-token cannot be combined");
  return { user, window, requestId, pageToken, json, targetId: parseTargetId(targetArgs) };
}

export function parseKubectlDuration(raw: string): string {
  const value = raw.trim();
  if (!/^[1-9][0-9]*(?:s|m|h)$/u.test(value)) throw new Error("--since must be a kubectl duration such as 24h, 90m, or 300s");
  return value;
}

export function parseTraceLimit(raw: string, option: string, min: number, max: number): number {
  const value = Number(raw);
  if (!Number.isInteger(value) || value < min || value > max) throw new Error(`${option} must be an integer from ${min} to ${max}`);
  return value;
}

export function readReportEventLimit(raw: string, option: string): number {
  const value = Number(raw);
  if (!Number.isInteger(value) || value < 1 || value > 200) throw new Error(`${option} must be an integer from 1 to 200`);
  return value;
}

export function parseDisclosureOptions(args: string[]): DisclosureOptions {
  validateOptions(args, new Set(["--full", "--raw", "--target"]));
  const raw = args.includes("--raw");
  return { full: raw || args.includes("--full"), raw, targetId: parseTargetId(args) };
}

export function parseTargetId(args: string[]): string {
  let targetId: string | null = null;
  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index]!;
    if (arg === "--target") {
      const value = args[index + 1];
      if (value === undefined || value.startsWith("--")) throw new Error("--target requires a value");
      targetId = value;
      index += 1;
      continue;
    }
    if (arg.startsWith("--target=")) targetId = arg.slice("--target=".length);
  }
  const resolvedTargetId = targetId ?? defaultCodexPoolRuntimeTargetId();
  if (!/^[A-Za-z0-9._-]+$/u.test(resolvedTargetId)) throw new Error("--target must be a simple target id");
  return resolvedTargetId;
}

export function splitAccountNames(value: string): string[] {
  return value.split(",").map((item) => item.trim()).filter(Boolean);
}

export function validateSub2ApiAccountSelector(value: string, option: string): void {
  if (value.length === 0 || value.length > 256) throw new Error(`${option} must be a non-empty account name up to 256 characters`);
  if (/[\r\n]/u.test(value)) throw new Error(`${option} must not contain newlines`);
  if (!/^[^<>"'`\\]+$/u.test(value)) throw new Error(`${option} contains unsupported characters`);
}

export function validateOptions(args: string[], booleanOptions: Set<string>): void {
  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index]!;
    if (arg === "--target") {
      index += 1;
      continue;
    }
    if (arg.startsWith("--target=") && booleanOptions.has("--target")) continue;
    if (booleanOptions.has(arg)) continue;
    throw new Error(`unsupported option: ${arg}`);
  }
}

export function stripBooleanOptions(args: string[], stripped: Set<string>): string[] {
  return args.filter((arg) => !stripped.has(arg));
}

export interface Sub2ApiRuntimeConfig {
  defaultTargetId: string;
  appSecretName: string;
  secretsRoot: string;
  appSourceRef: string;
  sentinelEnabledOnTargets: string[];
  targets: Record<string, unknown>[];
}
