/**
 * Agent execution helpers — spawn Rust agent subprocesses and manage
 * parallel execution with a concurrency-limited worker pool.
 */

import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { runRustAgent } from "../../agent/rust-agent-adapter";
import { serverConfig } from "../../agent/config";

/**
 * Resolve the path to the verify-mermaid-cli.mjs script.
 * It lives at the root of the @optage/sdk package.
 */
function resolveVerifyMermaidScript(): string {
  // This file is at packages/sdk/src/wiki/agents/runner.ts
  // The CLI script is at packages/sdk/verify-mermaid-cli.mjs
  // When bundled, __dirname resolves to packages/sdk/dist/
  // When running from source, it's packages/sdk/src/wiki/agents/
  try {
    const thisDir = dirname(fileURLToPath(import.meta.url));
    // Try dist-relative path first (bundled by tsup)
    const distPath = resolve(thisDir, "..", "verify-mermaid-cli.mjs");
    const srcPath = resolve(thisDir, "..", "..", "..", "verify-mermaid-cli.mjs");
    // Check which exists (the bundled version is in dist/, script is at package root)
    const fs = require("fs");
    if (fs.existsSync(distPath)) return distPath;
    if (fs.existsSync(srcPath)) return srcPath;
  } catch {
    // import.meta.url not available in CJS
  }
  // Fallback: env var or relative to cwd
  return process.env.VERIFY_MERMAID_SCRIPT || resolve(process.cwd(), "node_modules/@optage/sdk/verify-mermaid-cli.mjs");
}

/**
 * Resolve the working directory for the verify-mermaid-cli.mjs script.
 * It needs access to node_modules containing mermaid.
 */
function resolveVerifyMermaidWorkDir(): string {
  if (process.env.VERIFY_MERMAID_WORK_DIR) {
    return process.env.VERIFY_MERMAID_WORK_DIR;
  }
  // Walk up from the script to find the nearest directory with node_modules/mermaid
  const scriptPath = resolveVerifyMermaidScript();
  const { existsSync } = require("fs");
  let dir = dirname(scriptPath);
  for (let i = 0; i < 5; i++) {
    if (existsSync(resolve(dir, "node_modules", "mermaid"))) {
      return dir;
    }
    dir = dirname(dir);
  }
  return dirname(scriptPath);
}

/* ------------------------------------------------------------------ */
/*  buildAgentEnv                                                      */
/* ------------------------------------------------------------------ */

/**
 * Build the environment variables passed to each Rust agent subprocess.
 */
export function buildAgentEnv(): Record<string, string> {
  return {
    LLM_PROVIDER: process.env.LLM_PROVIDER || "openai",
    OPENAI_API_KEY:
      process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY || "",
    OPENAI_API_BASE_URL:
      process.env.OPENAI_API_BASE_URL ||
      process.env.ANTHROPIC_BASE_URL ||
      "",
    OPENAI_MODEL:
      process.env.OPENAI_MODEL || serverConfig.wikiAgent.model,
    ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || "",
    PATH:
      process.env.PATH ||
      "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
    HOME: process.env.HOME || "/home/repopilot",
    VERIFY_MERMAID_SCRIPT: resolveVerifyMermaidScript(),
    VERIFY_MERMAID_WORK_DIR: resolveVerifyMermaidWorkDir(),
  };
}

/* ------------------------------------------------------------------ */
/*  runSubagent                                                        */
/* ------------------------------------------------------------------ */

/**
 * Spawn a single Rust agent subprocess, consuming its event stream.
 * Throws on failure.
 */
export async function runSubagent(
  prompt: string,
  cwd: string,
  agentType: string,
  maxIterations?: number,
  abortSignal?: AbortSignal,
): Promise<void> {
  let toolCalls = 0;
  let wroteFile = false;
  for await (const event of runRustAgent({
    prompt,
    cwd,
    systemPrompt:
      "You are a documentation generator. Read source code with read_file and search_files, " +
      "then write the completed documentation using write_file. " +
      "The task is only complete after write_file succeeds.",
    model: serverConfig.wikiAgent.model,
    tools: [...serverConfig.wikiAgent.toolset],
    maxIterations: maxIterations ?? serverConfig.wikiAgent.maxTurns,
    env: buildAgentEnv(),
    abortSignal,
  })) {
    if (event.type === "tool_call") {
      toolCalls++;
      if (event.tool_name === "write_file") wroteFile = true;
    }
    if (event.type === "completed") {
      console.log(
        `[wiki-gen] ${agentType} completed: ${event.iterations} iterations, ${toolCalls} tool calls, ${event.tokens_used} tokens, wrote_file=${wroteFile}`,
      );
    }
    if (event.type === "failed") {
      throw new Error(`${agentType} failed: ${event.error}`);
    }
  }
}

/* ------------------------------------------------------------------ */
/*  runParallelSubagents                                               */
/* ------------------------------------------------------------------ */

export interface ParallelTask<T> {
  /** A function that performs the work and returns a result. */
  run: () => Promise<T>;
}

/**
 * Generic concurrency-limited worker pool.
 *
 * Executes up to `concurrency` tasks in parallel, returning results in
 * the same order as the input array. If a task throws, its slot in the
 * result array will contain the rejected error (re-thrown to the caller
 * is NOT done — callers should inspect results).
 */
export async function runParallelSubagents<T>(
  tasks: ParallelTask<T>[],
  concurrency: number,
): Promise<T[]> {
  const results: T[] = new Array(tasks.length);
  let nextIndex = 0;

  async function worker(): Promise<void> {
    while (nextIndex < tasks.length) {
      const idx = nextIndex++;
      results[idx] = await tasks[idx].run();
    }
  }

  const workers: Promise<void>[] = [];
  for (let i = 0; i < Math.min(concurrency, tasks.length); i++) {
    workers.push(worker());
  }

  await Promise.all(workers);
  return results;
}
