/**
 * POST /api/chat — main chat conversation, streams Agent SDK over SSE.
 *
 * Frontend (assistant-ui's adapter) consumes the SSE events and maps
 * Agent SDK message types (assistant/text, assistant/tool_use,
 * user/tool_result, result, system/init) into UI content blocks.
 *
 * Body:
 *   prompt    — the user's message
 *   sessionId — when present, SDK resumes the existing session
 *   cwd       — working dir for the SDK; falls back to settings.workspaceRoot
 */
import type { Hono } from "hono";
import { streamSSE } from "hono/streaming";
import { query } from "@anthropic-ai/claude-agent-sdk";
import { getWorkspaceRoot } from "./settings";
import { defaultSdkOptions } from "./agent";
import { linkThreadChat } from "./thread-chats";

type ChatRequest = {
  prompt: string;
  sessionId?: string;
  cwd?: string;
  /** File attachments (paste / picker). Base64-encoded, no data: URI prefix.
   *  `kind` selects the Claude content block — image (vision) or document (PDF). */
  attachments?: { kind: "image" | "document"; mimeType: string; data: string }[];
  /** When present + new session is created, link the new sessionId to this thread. */
  threadKey?: string;
  /** Optional label saved with the link (defaults to thread text prefix). */
  threadLabel?: string;
  /** Model alias (e.g. 'opus', 'sonnet', 'haiku') or full ID. Omit to inherit session default. */
  model?: string;
};

export function registerChatRoutes(app: Hono) {
  app.post("/api/chat", async (c) => {
    const body = await c.req.json<ChatRequest>();
    const { prompt, sessionId, cwd, attachments, threadKey, threadLabel, model } = body;

    // Propagate client disconnects (user clicks Stop, navigates away, etc.)
    // through to the Agent SDK so the in-flight `query()` actually stops.
    const sdkAbort = new AbortController();
    c.req.raw.signal.addEventListener("abort", () => sdkAbort.abort());

    return streamSSE(c, async (stream) => {
      try {
        const options = defaultSdkOptions({
          cwd: cwd || getWorkspaceRoot(),
          ...(model ? { model } : {}),
          includePartialMessages: true,
          abortController: sdkAbort,
          // AskUserQuestion is rendered by our chat UI (ToolCallChip's
          // AskUserQuestionCard); the SDK's CLI-style execution path
          // returns immediately, which makes Claude think the UI didn't
          // render and proceed with a fallback answer. Deny the SDK-side
          // execution with `interrupt: true` so the agent loop stops
          // after the tool_use is emitted — our UI surfaces it, the user
          // submits answers as a new user message, and the next turn
          // picks up from there.
          canUseTool: async (toolName: string) => {
            if (toolName === "AskUserQuestion") {
              return {
                behavior: "deny" as const,
                message: "User is responding through the chat UI. Stop and wait for their answers in their next message — do not proceed with a fallback answer.",
                interrupt: true,
              };
            }
            return { behavior: "allow" as const };
          },
          ...(sessionId ? { resume: sessionId } : {}),
        });
        // Multimodal: when attachments are present, build a structured user
        // message with image+text content blocks. Otherwise use the simple
        // string prompt path.
        const sdkPrompt: any = attachments && attachments.length
          ? (async function* () {
              yield {
                type: "user",
                message: {
                  role: "user",
                  content: [
                    ...attachments.map((a) => ({
                      type: a.kind === "document" ? "document" : "image",
                      source: { type: "base64", media_type: a.mimeType, data: a.data },
                    })),
                    { type: "text", text: prompt || "" },
                  ],
                },
                parent_tool_use_id: null,
                session_id: sessionId ?? "",
              };
            })()
          : prompt;
        let linkedSessionId: string | null = null;
        for await (const msg of query({ prompt: sdkPrompt, options: options as any })) {
          // Capture the new session_id from the init event so we can link
          // it to the thread (only when this is a NEW session, not a resume).
          if (!sessionId && !linkedSessionId) {
            const m = msg as any;
            if (m.type === "system" && m.subtype === "init" && typeof m.session_id === "string") {
              linkedSessionId = m.session_id;
              if (threadKey) {
                try {
                  linkThreadChat(threadKey, {
                    sessionId: linkedSessionId,
                    startedAt: new Date().toISOString(),
                    label: threadLabel,
                  });
                } catch (e) {
                  console.error("[chat] linkThreadChat failed:", e);
                }
              }
            }
          }
          await stream.writeSSE({ data: JSON.stringify(msg), event: "message" });
        }
        await stream.writeSSE({ data: "done", event: "done" });
      } catch (err: any) {
        await stream.writeSSE({
          data: JSON.stringify({ error: err?.message || String(err) }),
          event: "error",
        });
      }
    });
  });
}
