import path from "node:path";

import type {
  AgentMeta,
  AgentParseResult,
  AgentSpec,
  AgentToolResult,
} from "./types.js";

type PiAssistantMessage = {
  role?: string;
  content?: Array<{ type?: string; text?: string }>;
  usage?: { input?: number; output?: number };
  model?: string;
  provider?: string;
  stopReason?: string;
  name?: string;
  toolName?: string;
  tool_call_id?: string;
  toolCallId?: string;
  details?: Record<string, unknown>;
  arguments?: Record<string, unknown>;
};

function inferToolName(msg: PiAssistantMessage): string | undefined {
  const candidates = [msg.toolName, msg.name, msg.toolCallId, msg.tool_call_id]
    .map((c) => (typeof c === "string" ? c.trim() : ""))
    .filter(Boolean);
  if (candidates.length) return candidates[0];

  if (msg.role?.includes(":")) {
    const suffix = msg.role.split(":").slice(1).join(":").trim();
    if (suffix) return suffix;
  }

  return undefined;
}

function deriveToolMeta(msg: PiAssistantMessage): string | undefined {
  const details = msg.details ?? msg.arguments;
  const pathVal =
    details && typeof details.path === "string" ? details.path : undefined;
  const offset =
    details && typeof details.offset === "number" ? details.offset : undefined;
  const limit =
    details && typeof details.limit === "number" ? details.limit : undefined;
  const command =
    details && typeof details.command === "string"
      ? details.command
      : undefined;

  if (pathVal) {
    if (offset !== undefined && limit !== undefined) {
      return `${pathVal}:${offset}-${offset + limit}`;
    }
    return pathVal;
  }
  if (command) return command;
  return undefined;
}

function parsePiJson(raw: string): AgentParseResult {
  const lines = raw.split(/\n+/).filter((l) => l.trim().startsWith("{"));

  // Collect only completed assistant messages (skip streaming updates/toolcalls).
  const texts: string[] = [];
  const toolResults: AgentToolResult[] = [];
  let lastAssistant: PiAssistantMessage | undefined;
  let lastPushed: string | undefined;

  for (const line of lines) {
    try {
      const ev = JSON.parse(line) as {
        type?: string;
        message?: PiAssistantMessage;
      };

      const isToolResult =
        (ev.type === "message" || ev.type === "message_end") &&
        ev.message?.role &&
        typeof ev.message.role === "string" &&
        ev.message.role.toLowerCase().includes("tool");
      const isAssistantMessage =
        (ev.type === "message" || ev.type === "message_end") &&
        ev.message?.role === "assistant" &&
        Array.isArray(ev.message.content);

      if (!isAssistantMessage && !isToolResult) continue;

      const msg = ev.message as PiAssistantMessage;
      const msgText = msg.content
        ?.filter((c) => c?.type === "text" && typeof c.text === "string")
        .map((c) => c.text)
        .join("\n")
        .trim();

      if (isAssistantMessage) {
        if (msgText && msgText !== lastPushed) {
          texts.push(msgText);
          lastPushed = msgText;
          lastAssistant = msg;
        }
      } else if (isToolResult && msg.content) {
        const toolText = msg.content
          ?.filter((c) => c?.type === "text" && typeof c.text === "string")
          .map((c) => c.text)
          .join("\n")
          .trim();
        if (toolText) {
          toolResults.push({
            text: toolText,
            toolName: inferToolName(msg),
            meta: deriveToolMeta(msg),
          });
        }
      }
    } catch {
      // ignore malformed lines
    }
  }

  const meta: AgentMeta | undefined =
    lastAssistant && texts.length
      ? {
          model: lastAssistant.model,
          provider: lastAssistant.provider,
          stopReason: lastAssistant.stopReason,
          usage: lastAssistant.usage,
        }
      : undefined;

  return {
    texts,
    toolResults: toolResults.length ? toolResults : undefined,
    meta,
  };
}

export const piSpec: AgentSpec = {
  kind: "pi",
  isInvocation: (argv) => {
    if (argv.length === 0) return false;
    const base = path.basename(argv[0]).replace(/\.(m?js)$/i, "");
    if (base === "pi" || base === "tau") return true;

    // Also handle node entrypoints like `node /.../pi-mono/packages/coding-agent/dist/cli.js`
    if (base === "node" && argv.length > 1) {
      const second = argv[1]?.toString().toLowerCase();
      return (
        second.includes("pi-mono") &&
        second.includes("packages") &&
        second.includes("coding-agent") &&
        (second.endsWith("cli.js") || second.includes("/dist/cli"))
      );
    }

    return false;
  },
  buildArgs: (ctx) => {
    const argv = [...ctx.argv];
    let bodyPos = ctx.bodyIndex;
    // Non-interactive print + JSON
    if (!argv.includes("-p") && !argv.includes("--print")) {
      argv.splice(bodyPos, 0, "-p");
      bodyPos += 1;
    }
    if (
      ctx.format === "json" &&
      !argv.includes("--mode") &&
      !argv.some((a) => a === "--mode")
    ) {
      argv.splice(bodyPos, 0, "--mode", "json");
      bodyPos += 2;
    }
    // Session defaults
    // Identity prefix optional; Pi usually doesn't need it, but allow injection
    if (!(ctx.sendSystemOnce && ctx.systemSent) && argv[bodyPos]) {
      const existingBody = argv[bodyPos];
      argv[bodyPos] = [ctx.identityPrefix, existingBody]
        .filter(Boolean)
        .join("\n\n");
    }
    return argv;
  },
  parseOutput: parsePiJson,
};
