/**
 * `cph` subprocess tools - the Hub<->Courseware coupling point.
 *
 * The Hub agent does not parse engineering-file models in-process; it calls the
 * stable `cph` CLI (ADR-0016 version contract). `cph check` validates a
 * project; `cph build` renders a target artifact. Version compatibility is the
 * CLI's responsibility - it refuses incompatible `.cph-version` files with a
 * `cphVersionMismatch` error diagnostic (ADR-0016); the Hub merely runs the
 * subprocess and returns its stdout/stderr/exit to the model.
 *
 * The `cph` binary path is configurable (env `CPH_BIN`, default `cph` on
 * PATH). All subprocesses run with `cwd = ctx.workspaceDir` so paths in
 * diagnostics are relative to the project root.
 */
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { tool } from "ai";
import { z } from "zod";
import type { ToolContext, ToolDefinition } from "./tools.js";

const execFileAsync = promisify(execFile);

const CPH_BIN = process.env["CPH_BIN"] ?? "cph";
const DEFAULT_TIMEOUT_MS = 120_000;

interface ExecResult {
  readonly exitCode: number;
  readonly stdout: string;
  readonly stderr: string;
}

async function runCph(args: readonly string[], workspaceDir: string): Promise<ExecResult> {
  try {
    const { stdout, stderr } = await execFileAsync(CPH_BIN, args, {
      cwd: workspaceDir,
      timeout: DEFAULT_TIMEOUT_MS,
      maxBuffer: 10 * 1024 * 1024,
    });
    return { exitCode: 0, stdout, stderr };
  } catch (e) {
    // execFile rejects on non-zero exit; the error carries stdout/stderr/code.
    const err = e as NodeJS.ErrnoException & { stdout?: string; stderr?: string; code?: number | string };
    if (err.code === "ENOENT") {
      return {
        exitCode: 127,
        stdout: "",
        stderr: `cph binary not found at "${CPH_BIN}" (set CPH_BIN env or install cph on PATH)`,
      };
    }
    return {
      exitCode: typeof err.code === "number" ? err.code : 1,
      stdout: err.stdout ?? "",
      stderr: err.stderr ?? "",
    };
  }
}

/// `cph check <dir>` - validate a curriculum engineering file. Returns the
/// checker's diagnostics (stdout) for the model to act on. Non-zero exit means
/// the file has error diagnostics (ADR-0010); the output is still returned,
/// not thrown - the model needs to see the diagnostics to fix them.
export function cphCheckTool(ctx: ToolContext): ToolDefinition {
  return tool({
    description:
      "Run `cph check` on the project's curriculum engineering file. Returns diagnostics (7 classes: structure/schema/compile/version...). Non-zero exit means error diagnostics present - read the output to fix them.",
    inputSchema: z.object({
      path: z.string().describe("Path to the engineering file dir; defaults to the workspace root.").optional(),
    }),
    execute: async (args): Promise<string> => {
      const target = args.path ?? ".";
      const res = await runCph(["check", target], ctx.workspaceDir);
      return JSON.stringify({
        exitCode: res.exitCode,
        stdout: res.stdout,
        stderr: res.stderr,
      });
    },
  });
}

/// `cph build <dir> --target <T> -o <out>` - render a target artifact. The
/// target (student/teacher/...) determines the build (ADR-0009). Output path
/// is relative to the workspace unless absolute.
export function cphBuildTool(ctx: ToolContext): ToolDefinition {
  return tool({
    description:
      "Run `cph build` to render a curriculum artifact (e.g. student/teacher PDF). Target selects the build; output names the result file.",
    inputSchema: z.object({
      target: z.string().describe("Build target, e.g. 'student', 'teacher'."),
      path: z.string().describe("Path to the engineering file dir; defaults to the workspace root.").optional(),
      output: z.string().describe("Output file path (relative to workspace or absolute).").optional(),
    }),
    execute: async (args): Promise<string> => {
      const target = args.path ?? ".";
      const out = args.output ?? `build/${args.target}.pdf`;
      const res = await runCph(["build", target, "--target", args.target, "-o", out], ctx.workspaceDir);
      return JSON.stringify({
        exitCode: res.exitCode,
        stdout: res.stdout,
        stderr: res.stderr,
        output: out,
      });
    },
  });
}
