// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
// Licensed under the Apache License, Version 2.0

import type { IAiLogger, IToolArguments, IToolDefinition } from "@gadget/ai";
import { formatError } from "@gadget/ai";
import { DroneTool } from "../tool.ts";
import SubProcessService from "../../services/subprocess.ts";

const VALID_CMDS = [
  "create",
  "list",
  "kill",
  "killAll",
  "halt",
  "log",
] as const;

type SubprocessCmd = (typeof VALID_CMDS)[number];

export class SubprocessTool extends DroneTool {
  get name(): string {
    return "subprocess";
  }

  get category(): string {
    return "system";
  }

  get definition(): IToolDefinition {
    return {
      type: "function",
      function: {
        name: this.name,
        description:
          "Manage child processes spawned by this agent. Use 'create' to spawn a new process, 'list' to see running processes, 'kill' to stop a process and remove its log files, 'halt' to stop a process while retaining its log files for later inspection, 'killAll' to stop all running processes, and 'log' to read stdout or stderr output from a running or halted process.",
        parameters: {
          type: "object",
          properties: {
            cmd: {
              type: "string",
              enum: [...VALID_CMDS],
              description:
                "The subprocess command to execute. create: spawn a new process. list: show all managed processes. kill: terminate a process and remove its logs. halt: terminate a process but keep its logs. killAll: terminate all managed processes. log: read stdout or stderr output from a process.",
            },
            command: {
              type: "string",
              description:
                "The shell command to execute (required for cmd=create). Example: 'node', 'npm', 'python', './my-server'.",
            },
            args: {
              type: "array",
              items: { type: "string" },
              description:
                "Command-line arguments for the process (optional, used with cmd=create). Example: ['run', 'dev'].",
            },
            pid: {
              type: "number",
              description:
                "Process ID of the managed subprocess (required for cmd=kill, cmd=halt, cmd=log).",
            },
            which: {
              type: "string",
              enum: ["stdout", "stderr"],
              description:
                "Which log stream to read (required for cmd=log). 'stdout' for standard output, 'stderr' for standard error.",
            },
          },
          required: ["cmd"],
        },
      },
    };
  }

  async execute(args: IToolArguments, logger: IAiLogger): Promise<string> {
    const cmd = args.cmd as string | undefined;

    if (!cmd) {
      return formatError({
        code: "MISSING_PARAMETER",
        message: "The 'cmd' parameter is required.",
        parameter: "cmd",
        expected: `One of: ${VALID_CMDS.join(", ")}`,
        recoveryHint: `Specify a valid command: ${VALID_CMDS.join(", ")}.`,
      });
    }

    if (!VALID_CMDS.includes(cmd as SubprocessCmd)) {
      return formatError({
        code: "INVALID_PARAMETER",
        message: `Invalid cmd: '${cmd}'. Must be one of: ${VALID_CMDS.join(", ")}`,
        parameter: "cmd",
        expected: `One of: ${VALID_CMDS.join(", ")}`,
        recoveryHint: `Use one of: ${VALID_CMDS.join(", ")}.`,
      });
    }

    switch (cmd) {
      case "create":
        return this.cmdCreate(args, logger);
      case "list":
        return this.cmdList(args, logger);
      case "kill":
        return this.cmdKill(args, logger);
      case "killAll":
        return this.cmdKillAll(args, logger);
      case "halt":
        return this.cmdHalt(args, logger);
      case "log":
        return this.cmdLog(args, logger);
      default:
        return formatError({
          code: "INVALID_PARAMETER",
          message: `Unknown subprocess command: '${cmd}'`,
          parameter: "cmd",
          expected: `One of: ${VALID_CMDS.join(", ")}`,
        });
    }
  }

  private async cmdCreate(
    args: IToolArguments,
    logger: IAiLogger,
  ): Promise<string> {
    const command = args.command as string | undefined;
    if (!command || typeof command !== "string" || command.trim().length === 0) {
      return formatError({
        code: "MISSING_PARAMETER",
        message: "The 'command' parameter is required for cmd=create.",
        parameter: "command",
        recoveryHint:
          "Provide the command to execute, e.g. 'node', 'npm', 'python'.",
      });
    }

    const rawArgs = args.args;
    const cmdArgs: string[] | undefined =
      Array.isArray(rawArgs)
        ? rawArgs.map((a) => String(a))
        : undefined;

    const project = this.toolbox.env.workspace?.projectDir
      ? { _id: "workspace", slug: this.toolbox.env.workspace.projectDir.split("/").pop() || "project" }
      : { _id: "workspace", slug: "project" };

    const projectObj = {
      _id: project._id,
      slug: project.slug,
      name: project.slug,
      createdAt: new Date(),
      updatedAt: new Date(),
      createdBy: "system",
    };

    try {
      const sp = await SubProcessService.create(projectObj as any, command, cmdArgs);
      return [
        "SUBPROCESS CREATED",
        `pid: ${sp.pid}`,
        `stdout: ${sp.stdoutFileName}`,
        `stderr: ${sp.stderrFileName}`,
      ].join("\n");
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      logger.error("failed to create subprocess", {
        command,
        args: cmdArgs,
        error: message,
      });
      return formatError({
        code: "OPERATION_FAILED",
        message: `Failed to spawn subprocess: ${message}`,
      });
    }
  }

  private async cmdList(
    _args: IToolArguments,
    _logger: IAiLogger,
  ): Promise<string> {
    const processes = SubProcessService.ps();
    if (processes.length === 0) {
      return "SUBPROCESS LIST\n(no running subprocesses)";
    }

    const lines = ["SUBPROCESS LIST"];
    for (const sp of processes) {
      lines.push(
        `pid: ${sp.pid} | project: ${sp.project.slug} | created: ${sp.createdAt.toISOString()} | updated: ${sp.updatedAt.toISOString()}`,
      );
    }
    return lines.join("\n");
  }

  private async cmdKill(
    args: IToolArguments,
    logger: IAiLogger,
  ): Promise<string> {
    const pid = args.pid as number | undefined;
    if (typeof pid !== "number" || !Number.isFinite(pid)) {
      return formatError({
        code: "MISSING_PARAMETER",
        message: "The 'pid' parameter is required for cmd=kill.",
        parameter: "pid",
        recoveryHint: "Provide the numeric process ID to kill.",
      });
    }

    try {
      await SubProcessService.killPid(pid);
      return `SUBPROCESS KILLED\npid: ${pid}`;
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      logger.error("failed to kill subprocess", { pid, error: message });
      return formatError({
        code: "OPERATION_FAILED",
        message: `Failed to kill subprocess: ${message}`,
      });
    }
  }

  private async cmdKillAll(
    _args: IToolArguments,
    logger: IAiLogger,
  ): Promise<string> {
    try {
      await SubProcessService.killAll();
      return "SUBPROCESS KILLALL\nAll managed subprocesses have been terminated and their logs removed.";
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      logger.error("failed to kill all subprocesses", { error: message });
      return formatError({
        code: "OPERATION_FAILED",
        message: `Failed to kill all subprocesses: ${message}`,
      });
    }
  }

  private async cmdHalt(
    args: IToolArguments,
    logger: IAiLogger,
  ): Promise<string> {
    const pid = args.pid as number | undefined;
    if (typeof pid !== "number" || !Number.isFinite(pid)) {
      return formatError({
        code: "MISSING_PARAMETER",
        message: "The 'pid' parameter is required for cmd=halt.",
        parameter: "pid",
        recoveryHint: "Provide the numeric process ID to halt.",
      });
    }

    try {
      await SubProcessService.haltPid(pid);
      return [
        "SUBPROCESS HALTED",
        `pid: ${pid}`,
        "The process has been stopped. Log files are retained for inspection.",
      ].join("\n");
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      logger.error("failed to halt subprocess", { pid, error: message });
      return formatError({
        code: "OPERATION_FAILED",
        message: `Failed to halt subprocess: ${message}`,
      });
    }
  }

  private async cmdLog(
    args: IToolArguments,
    logger: IAiLogger,
  ): Promise<string> {
    const pid = args.pid as number | undefined;
    if (typeof pid !== "number" || !Number.isFinite(pid)) {
      return formatError({
        code: "MISSING_PARAMETER",
        message: "The 'pid' parameter is required for cmd=log.",
        parameter: "pid",
        recoveryHint: "Provide the numeric process ID to read logs from.",
      });
    }

    const which = args.which as string | undefined;
    if (!which || (which !== "stdout" && which !== "stderr")) {
      return formatError({
        code: "MISSING_PARAMETER",
        message: "The 'which' parameter is required for cmd=log.",
        parameter: "which",
        expected: "'stdout' or 'stderr'",
        recoveryHint: "Specify 'stdout' to read standard output or 'stderr' to read standard error.",
      });
    }

    try {
      const content = await SubProcessService.getLog(pid, which);
      return [
        `SUBPROCESS LOG (${which})`,
        `pid: ${pid}`,
        "---",
        content || "(log is empty)",
      ].join("\n");
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      logger.error("failed to read subprocess log", {
        pid,
        which,
        error: message,
      });
      return formatError({
        code: "OPERATION_FAILED",
        message: `Failed to read subprocess log: ${message}`,
      });
    }
  }
}
