import { NextResponse } from "next/server";
import { rmSync } from "fs";
import {
  DefaultResourceLoader,
  getAgentDir,
  createAgentSession,
  SessionManager,
} from "@earendil-works/pi-coding-agent";
import { getRpcSession, startRpcSession } from "@/lib/rpc-manager";
import { cacheSessionPath, resolveSessionPath } from "@/lib/session-reader";
import type { SlashCommandItem } from "@/lib/types";

export const dynamic = "force-dynamic";

async function loadCommandsFromSession(
  sessionId: string,
  cwd: string
): Promise<SlashCommandItem[]> {
  let session = getRpcSession(sessionId);

  // If the session isn't currently in memory, try to wake it up from its file.
  if (!session?.isAlive()) {
    const filePath = await resolveSessionPath(sessionId);
    if (!filePath) {
      throw new Error("Session not found or not alive");
    }
    const sessionCwd = SessionManager.open(filePath).getHeader()?.cwd ?? cwd;
    const started = await startRpcSession(sessionId, filePath, sessionCwd);
    session = started.session;
  }

  return (await session.send({ type: "get_slash_commands" })) as SlashCommandItem[];
}

async function loadCommandsWithoutSession(cwd: string): Promise<SlashCommandItem[]> {
  const agentDir = getAgentDir();

  // Skills and prompt templates can be discovered without a full AgentSession.
  const loader = new DefaultResourceLoader({ cwd, agentDir });

  let skillCommands: SlashCommandItem[] = [];
  let promptCommands: SlashCommandItem[] = [];

  try {
    await loader.reload();
    const { skills } = loader.getSkills();
    skillCommands = skills
      .filter((s) => !s.disableModelInvocation)
      .map((s) => ({
        name: s.name,
        description: s.description,
        category: "skill" as const,
        sourceLabel: "skill",
        invocationPrefix: "skill:",
      }));

    const { prompts } = loader.getPrompts();
    promptCommands = prompts.map((p) => ({
      name: p.name,
      description: p.description,
      category: "prompt" as const,
      sourceLabel: "prompt",
      argumentHint: p.argumentHint,
    }));
  } catch (e) {
    // Resource loading may fail if a CJS-only extension is present.
    // Skills and prompts are still best-effort; we continue with empty lists.
    console.error("Resource loader reload failed (extensions may be incompatible):", e);
  }

  // Extension commands require a live AgentSession because they are registered
  // during extension runtime initialization. We spin up a temporary session,
  // capture the registered commands, then abort and delete the temporary file.
  let extCommands: SlashCommandItem[] = [];
  let tempSessionFile: string | undefined;
  let tempSessionId: string | undefined;

  try {
    const sessionManager = SessionManager.create(cwd);
    tempSessionFile = sessionManager.getSessionFile();
    const { session } = await createAgentSession({
      cwd,
      agentDir,
      sessionManager,
      tools: [],
    });

    tempSessionId = session.sessionId;
    if (tempSessionId) {
      // Avoid polluting the path cache with the temporary discovery session.
      if (tempSessionFile) {
        cacheSessionPath(tempSessionId, tempSessionFile);
      }
    }

    extCommands = session.extensionRunner.getRegisteredCommands().map((c) => ({
      name: c.invocationName,
      description: c.description,
      category: "extension" as const,
      sourceLabel: c.sourceInfo?.source ?? "extension",
    }));

    await session.abort();
  } catch (e) {
    // Extension discovery is best-effort. If the temporary session fails for
    // any reason, we still return builtin + skills + prompts.
    console.error("Failed to discover extension commands:", e);
  } finally {
    if (tempSessionFile) {
      try {
        rmSync(tempSessionFile, { force: true });
      } catch {
        // ignore cleanup errors
      }
    }
    if (tempSessionId) {
      try {
        getRpcSession(tempSessionId)?.destroy();
      } catch {
        // ignore
      }
      // Also purge from the path cache so it never leaks into the UI.
      const cache = (globalThis as Record<string, unknown>).__piSessionPathCache as
        | Map<string, string>
        | undefined;
      cache?.delete(tempSessionId);
    }
  }

  return [...extCommands, ...skillCommands, ...promptCommands];
}

// GET /api/slash-commands?cwd=<path>&sessionId=<id>
export async function GET(req: Request) {
  const { searchParams } = new URL(req.url);
  const cwd = searchParams.get("cwd");
  const sessionId = searchParams.get("sessionId");

  if (!cwd) {
    return NextResponse.json({ error: "cwd is required" }, { status: 400 });
  }

  try {
    const commands = sessionId
      ? await loadCommandsFromSession(sessionId, cwd)
      : await loadCommandsWithoutSession(cwd);

    return NextResponse.json({ commands });
  } catch (e) {
    const message = e instanceof Error ? e.message : String(e);
    const status = message.includes("Session not found") ? 404 : 500;
    return NextResponse.json({ error: message }, { status });
  }
}
