/**
 * Agent orchestration — runs the Rust Agent SDK binary and yields AG-UI events.
 *
 * Spawns the rust agent as a subprocess with NDJSON streaming,
 * mapping events to the AG-UI protocol for the frontend.
 */

import { resolve } from "node:path";
import { runRustAgent } from "./rust-agent-adapter";
import { RustEventMapper } from "./rust-event-mapper";
import { buildSystemPrompt } from "./prompts";
import { llmStatus, checkLlmReachable } from "./llm-status";
import { serverConfig } from "./config";
import { buildWikiChatContext } from "../wiki/store";
import type { AguiEvent, RunAgentInput, AguiMessage } from "../types/agui-types";
import { getRepositorySourcePath } from "../types/repository";

let envInitialized = false;
async function ensureEnvDetected() {
  if (envInitialized) return;
  await checkLlmReachable();
  envInitialized = true;
}

function buildConversationPrompt(messages: AguiMessage[]): string {
  const truncate = (value: string, maxChars: number) =>
    value.length > maxChars ? `${value.slice(0, maxChars)}\n...[truncated]` : value;

  const summaryMessages = messages
    .filter(
      (msg) =>
        msg.role === "system" &&
        typeof msg.content === "string" &&
        msg.content.trim().length > 0,
    )
    .slice(-1)
    .map((msg) => ({
      ...msg,
      content: truncate(
        msg.content.trim(),
        serverConfig.agent.conversationSummaryMaxChars,
      ),
    }));

  const conversationalMessages = messages
    .filter(
      (msg) =>
        (msg.role === "user" || msg.role === "assistant") &&
        typeof msg.content === "string" &&
        msg.content.trim().length > 0,
    )
    .slice(-serverConfig.agent.conversationHistoryMessages)
    .map((msg) => ({
      ...msg,
      content: truncate(
        msg.content.trim(),
        msg.role === "assistant"
          ? serverConfig.agent.conversationAssistantMaxChars
          : serverConfig.agent.conversationUserMaxChars,
      ),
    }));

  const latestUserMessage = [...conversationalMessages]
    .reverse()
    .find((msg) => msg.role === "user");

  if (!latestUserMessage) {
    return "";
  }

  const transcriptParts = [
    ...summaryMessages.map((msg) => `Summary:\n${msg.content.trim()}`),
    ...conversationalMessages.map(
      (msg) =>
        `${msg.role === "user" ? "User" : "Assistant"}:\n${msg.content.trim()}`,
    ),
  ];

  return [
    "Continue the conversation below.",
    "Use the previous turns and any compacted summary as memory.",
    "Answer the latest user message in context.",
    "",
    transcriptParts.join("\n\n"),
  ].join("\n");
}

export async function* runAgent(
  inputData: RunAgentInput,
): AsyncGenerator<AguiEvent> {
  const threadId = inputData.threadId;
  const runId = inputData.runId;

  yield { type: "RUN_STARTED", threadId, runId };

  const prompt = buildConversationPrompt(inputData.messages || []);
  if (!prompt) {
    yield { type: "RUN_ERROR", message: "No user message found" };
    return;
  }

  await ensureEnvDetected();

  let sourcePath = serverConfig.agent.sourcePath;
  if (inputData.repositoryId) {
    const resolved = await getRepositorySourcePath(inputData.repositoryId);
    if (resolved) {
      sourcePath = resolved;
    }
  }

  // Resolve relative paths (e.g. "repos/<uuid>") to absolute
  sourcePath = resolve(sourcePath);

  const wikiContext = buildWikiChatContext(sourcePath);

  const systemPrompt = buildSystemPrompt(llmStatus, serverConfig.agent.maxTurns) +
    (wikiContext ? `\n\n${wikiContext}` : "") +
    `\n\nIMPORTANT: Your working directory is set to the repository root. ` +
    `All file paths are relative to this root. Do NOT reveal absolute host paths in your responses.`;

  const abortController = new AbortController();
  const timeoutMs = serverConfig.agent.timeoutMs;
  const timeout = setTimeout(() => abortController.abort(), timeoutMs);

  const mapper = new RustEventMapper();

  try {
    for await (const event of runRustAgent({
      prompt,
      cwd: sourcePath,
      systemPrompt,
      model: serverConfig.llmModel,
      tools: [...serverConfig.agent.toolset],
      maxIterations: serverConfig.agent.maxTurns,
      abortSignal: abortController.signal,
      env: {
        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.llmModel,
        ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || "",
        ANTHROPIC_MODEL: process.env.ANTHROPIC_MODEL || "",
        PATH: process.env.PATH || "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
        HOME: process.env.HOME || "/home/repopilot",
      },
    })) {
      yield* mapper.mapEvent(event);
    }
  } catch (err) {
    clearTimeout(timeout);
    const message = err instanceof Error ? err.message : String(err);

    if (abortController.signal.aborted) {
      if (!mapper.hasEmittedText) {
        const fallbackId = `msg_fallback_${Date.now()}`;
        yield { type: "TEXT_MESSAGE_START", messageId: fallbackId, role: "assistant" };
        yield {
          type: "TEXT_MESSAGE_CONTENT",
          messageId: fallbackId,
          delta: "Agent timed out while gathering context. The run exceeded the server time limit.",
        };
        yield { type: "TEXT_MESSAGE_END", messageId: fallbackId };
      }
    } else if (!mapper.hasEmittedText) {
      const fallbackId = `msg_fallback_${Date.now()}`;
      yield { type: "TEXT_MESSAGE_START", messageId: fallbackId, role: "assistant" };
      yield {
        type: "TEXT_MESSAGE_CONTENT",
        messageId: fallbackId,
        delta: `Agent encountered an error: ${message}`,
      };
      yield { type: "TEXT_MESSAGE_END", messageId: fallbackId };
    }

    yield { type: "RUN_ERROR", message };
    yield { type: "RUN_FINISHED", threadId, runId };
    return;
  }
  clearTimeout(timeout);

  if (!mapper.hasEmittedText) {
    const fallbackId = `msg_fallback_${Date.now()}`;
    yield { type: "TEXT_MESSAGE_START", messageId: fallbackId, role: "assistant" };
    yield {
      type: "TEXT_MESSAGE_CONTENT",
      messageId: fallbackId,
      delta: "Task completed. The agent finished processing using tool calls only — check the tool results above for details.",
    };
    yield { type: "TEXT_MESSAGE_END", messageId: fallbackId };
  }

  yield { type: "RUN_FINISHED", threadId, runId };
}
