#!/usr/bin/env node

/**
 * Orchestrator — V2 Protocol Stack → Factory Pipeline Bridge
 *
 * Minimal orchestrator that:
 *   1. Accepts user input (natural language)
 *   2. Generates a Task Tree + Plan record (AGENTS.md §1–2)
 *   3. Routes to the correct factory chain via Factory Router
 *   4. Captures Governance-compliant heartbeat into progress.log
 *   5. Returns structured result summary
 *
 * Does NOT:
 *   - Re-implement AGENTS.md / GOVERNANCE.md rules
 *   - Run state machines
 *   - Replace existing CLI entry points
 *
 * Usage:
 *   node scripts/orchestrator.mjs --input "做一个宠物管理 App" [--output <dir>]
 *   node scripts/orchestrator.mjs --input "做一个在线教育平台" --domain education
 *
 * @module orchestrator
 */

import { writeFileSync, appendFileSync, existsSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { mkdirSync } from "node:fs";
import { fileURLToPath } from "node:url";

const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = resolve(__dirname, "..");

// ── Constants ──────────────────────────────────────

const DEFAULT_OUTPUT = resolve(ROOT, "fullstack");
const PROGRESS_LOG_PATH = resolve(ROOT, "progress.log");

// ── CLI ────────────────────────────────────────────

function parseArgs() {
  const args = process.argv.slice(2);
  const opts = { input: null, output: null, domain: null, verbose: false, help: false };
  for (let i = 0; i < args.length; i++) {
    if (args[i] === "--input" && args[i + 1]) opts.input = args[++i];
    else if (args[i] === "--output" && args[i + 1]) opts.output = args[++i];
    else if (args[i] === "--domain" && args[i + 1]) opts.domain = args[++i];
    else if (args[i] === "--verbose") opts.verbose = true;
    else if (args[i] === "--help" || args[i] === "-h") opts.help = true;
  }
  return opts;
}

async function main() {
  const opts = parseArgs();

  if (opts.help) {
    console.log(`
Orchestrator — V2 Protocol Stack ↔ Factory Pipeline

Usage:
  node scripts/orchestrator.mjs --input <text> [options]

Options:
  --input <text>     用户需求描述（必填）
  --output <dir>      输出目录（default: fullstack/）
  --domain <name>    强制领域（override auto-detect）
  --verbose           详细输出
  --help              显示帮助

Examples:
  node scripts/orchestrator.mjs --input "做一个宠物管理 App"
  node scripts/orchestrator.mjs --input "做一个在线教育平台" --output ./my-project
`);
    return;
  }

  if (!opts.input) {
    console.error("Error: --input is required. Use --help for usage.");
    process.exit(1);
  }

  try {
    const result = await run(opts.input, opts);
    console.log(JSON.stringify(result.summary, null, 2));
  } catch (e) {
    console.error(`Orchestrator failed: ${e.message}`);
    // Write failure heartbeat
    writeHeartbeat({ status: "FAILED", remaining: [], risk: e.message, next_action: "investigate failure" });
    process.exit(1);
  }
}

// ── Main Pipeline ─────────────────────────────────

/**
 * Orchestrator run — the unified entry point.
 *
 * @param {string} input — User's requirement description
 * @param {object} opts
 * @param {string} [opts.output] — Output directory
 * @param {string} [opts.domain] — Force domain override
 * @returns {Promise<{summary: object, plan: object, taskTree: object, heartbeat: object}>}
 */
export async function run(input, opts = {}) {
  const startTs = new Date().toISOString();
  const plan = buildPlan(input);

  // ── 1. Context Init & Heartbeat ──
  writeHeartbeat(plan.heartbeat);

  // ── 2. V2 Task Tree ──
  const taskTree = buildTaskTree(input, opts.domain);

  // ── 3. SF-01: Project Intake (PRD generation) ──
  const sf01 = await import("./project-intake-agent.mjs");
  writeHeartbeat({ ...plan.heartbeat, status: "IN_PROGRESS", completed: ["T1"], remaining: plan.heartbeat.remaining.slice(1), next_action: "SF-02: generate architecture" });

  const prd = sf01.generatePRD(input);
  if (prd.error === "EMPTY_INPUT") {
    throw new Error("PRD generation failed: empty input");
  }
  if (prd.error === "EXTRACTION_FAILED") {
    throw new Error(prd.message);
  }

  // ── 4. SF-02: Architecture generation ──
  const sf02 = await import("./architecture-agent.mjs");
  writeHeartbeat({ ...plan.heartbeat, status: "IN_PROGRESS", completed: ["T1", "T2"], remaining: plan.heartbeat.remaining.slice(2), next_action: "SF-03~06: fullstack compose" });

  const arch = sf02.generateArchitecture(prd);
  if (arch.error) {
    throw new Error(`Architecture generation failed: ${arch.message}`);
  }

  // ── 5. Factory Router: route to fullstack composer ──
  const router = await import("./factory-router.mjs");
  const routeResult = router.route(prd, { arch });
  const factoryMod = await router.loadFactory(routeResult.factoryFn);

  writeHeartbeat({ ...plan.heartbeat, status: "IN_PROGRESS", completed: ["T1", "T2", "T3"], remaining: plan.heartbeat.remaining.slice(3), next_action: "writing output" });

  const result = await factoryMod.composeFullstack(prd, arch);

  // ── 6. Write output ──
  const outputDir = opts.output ? resolve(opts.output) : DEFAULT_OUTPUT;
  factoryMod.writeFullstack(result, outputDir);

  // ── 7. Governance: final heartbeat ──
  writeHeartbeat({ ...plan.heartbeat, status: "ARCHIVED", completed: ["T1", "T2", "T3", "T4"], remaining: [], next_action: "none" });

  const endTs = new Date().toISOString();
  const durationMs = Date.now() - new Date(startTs).getTime();

  // ── 8. Summary ──
  const summary = {
    projectName: prd.projectName,
    chineseName: prd.chineseName || prd.projectName,
    domain: prd.domain,
    factory: routeResult.factory,
    outputDir,
    stats: result.stats,
    startTs,
    endTs,
    durationMs,
  };

  return { summary, plan, taskTree, heartbeat: plan.heartbeat };
}

// ── V2 Protocol Artifacts ──────────────────────────

/**
 * Build Task Tree per AGENTS.md §1.
 *
 * @param {string} input
 * @param {string} [domainOverride]
 * @returns {object} Task Tree
 */
function buildTaskTree(input, domainOverride) {
  return {
    root: {
      id: "T0",
      name: "Full-stack Product Generation",
      depends_on: null,
      description: `Generate full-stack product from: "${input.slice(0, 80)}"`,
    },
    tasks: [
      { id: "T1", name: "SF-01 Product Strategy", depends_on: ["T0"], description: "Parse requirements → PRD", parallelizable: false },
      { id: "T2", name: "SF-02 Architecture Design", depends_on: ["T1"], description: "PRD → Architecture JSON", parallelizable: false },
      { id: "T3", name: "SF-03~06 Full-stack Generation", depends_on: ["T2"], description: "Arch → NestJS + React code", parallelizable: false },
      { id: "T4", name: "Governance Verification", depends_on: ["T3"], description: "Verify output, write audit", parallelizable: false },
    ],
    parallelGroups: [],
    injectedRisks: [
      "Domain match may fallback to generic → verify PRD.domain",
      "EXTRACTION_FAILED if input too vague → need detailed input",
    ],
  };
}

/**
 * Build Plan per AGENTS.md §2.
 *
 * @param {string} input
 * @returns {object} Plan + initial heartbeat
 */
function buildPlan(input) {
  const subtasks = [
    "T1: SF-01 Product Strategy — parse input, match domain, generate PRD",
    "T2: SF-02 Architecture — generate arch.json from PRD",
    "T3: SF-03~06 Full-stack — compose NestJS + React project",
    "T4: Write output files and finalize",
  ];

  const heartbeat = {
    ts: new Date().toISOString(),
    status: "IN_PROGRESS",
    completed: [],
    remaining: ["T1", "T2", "T3", "T4"],
    risk: "",
    next_action: "SF-01: generate PRD",
  };

  return {
    goal: `Generate a full-stack web application from: "${input.slice(0, 100)}"`,
    subtasks,
    risks: [
      "Input may be too vague → EXTRACTION_FAILED",
      "Domain template may not fully match → generic fallback",
    ],
    verificationStrategy: {
      build: true,
      tests: true,
      lint: false,
    },
    heartbeat,
  };
}

// ── Governance Logging ─────────────────────────────

/**
 * Append a heartbeat entry to progress.log.
 *
 * Format: JSON Lines (GOVERNANCE.md §2.4)
 *
 * @param {object} entry
 */
function writeHeartbeat(entry) {
  try {
    const d = dirname(PROGRESS_LOG_PATH);
    mkdirSync(d, { recursive: true });
    appendFileSync(PROGRESS_LOG_PATH, JSON.stringify(entry) + "\n");
  } catch (e) {
    console.error(`[orchestrator] heartbeat write failed: ${e.message}`);
  }
}

// ── CLI Bootstrap ─────────────────────────────────

if (process.argv[1] && (import.meta.url === `file://${process.argv[1]}` || import.meta.url.endsWith(process.argv[1]))) {
  main();
}
