import { parse as parseDataUrl } from "@/boxes/dataurl"
import { pathToFileURL } from "url"
import type { Role, ToolCallContent } from "@agentclientprotocol/sdk"
import type { SessionMessageResponse, ToolPart } from "@saeeol/sdk/v2"
import { Result } from "effect"
import type { AgentDeps } from "./agent-types"
import { log, decodeTodos, toToolKind, toLocations } from "./agent-utils"
import { bashOutput, toolStart } from "./agent-events"

export async function processMessage(deps: AgentDeps, message: SessionMessageResponse) {
  log.debug("process message", message)
  if (message.info.role !== "assistant" && message.info.role !== "user") return
  const sessionId = message.info.sessionID
  for (const part of message.parts) {
    if (part.type === "tool") {
      await processToolPart(deps, sessionId, part)
    } else if (part.type === "text") {
      await processTextPart(deps, sessionId, message, part)
    } else if (part.type === "file") {
      await processFilePart(deps, sessionId, message, part)
    } else if (part.type === "reasoning") {
      await processReasoningPart(deps, sessionId, message, part)
    }
  }
}

async function processToolPart(deps: AgentDeps, sessionId: string, part: ToolPart) {
  await toolStart(deps, sessionId, part)
  switch (part.state.status) {
    case "pending": deps.bashSnapshots.delete(part.callID); break
    case "running": {
      const output = bashOutput(deps, part)
      const runningContent: ToolCallContent[] = []
      if (output) runningContent.push({ type: "content", content: { type: "text", text: output } })
      await deps.connection.sessionUpdate({ sessionId, update: { sessionUpdate: "tool_call_update", toolCallId: part.callID, status: "in_progress", kind: toToolKind(part.tool), title: part.tool, locations: toLocations(part.tool, part.state.input), rawInput: part.state.input, ...(runningContent.length > 0 && { content: runningContent }) } }).catch((err: any) => { log.error("failed to send tool in_progress to ACP", { error: err }) })
      break
    }
    case "completed": {
      deps.toolStarts.delete(part.callID)
      deps.bashSnapshots.delete(part.callID)
      const kind = toToolKind(part.tool)
      const content: ToolCallContent[] = [{ type: "content", content: { type: "text", text: part.state.output } }]
      if (kind === "edit") {
        const input = part.state.input
        content.push({ type: "diff", path: typeof input["filePath"] === "string" ? input["filePath"] : "", oldText: typeof input["oldString"] === "string" ? input["oldString"] : "", newText: typeof input["newString"] === "string" ? input["newString"] : typeof input["content"] === "string" ? input["content"] : "" })
      }
      if (part.tool === "todowrite") {
        const parsedTodos = decodeTodos(part.state.output)
        if (Result.isSuccess(parsedTodos)) {
          await deps.connection.sessionUpdate({ sessionId, update: { sessionUpdate: "plan", entries: parsedTodos.success.map((todo) => ({ priority: "medium" as const, status: (todo.status === "cancelled" ? "completed" : todo.status) as any, content: todo.content })) } }).catch((err: any) => { log.error("failed to send session update for todo", { error: err }) })
        } else { log.error("failed to parse todo output", { error: parsedTodos.failure }) }
      }
      await deps.connection.sessionUpdate({ sessionId, update: { sessionUpdate: "tool_call_update", toolCallId: part.callID, status: "completed", kind, content, title: part.state.title, rawInput: part.state.input, rawOutput: { output: part.state.output, metadata: part.state.metadata } } }).catch((err: any) => { log.error("failed to send tool completed to ACP", { error: err }) })
      break
    }
    case "error": {
      deps.toolStarts.delete(part.callID)
      deps.bashSnapshots.delete(part.callID)
      await deps.connection.sessionUpdate({ sessionId, update: { sessionUpdate: "tool_call_update", toolCallId: part.callID, status: "failed", kind: toToolKind(part.tool), title: part.tool, rawInput: part.state.input, content: [{ type: "content", content: { type: "text", text: part.state.error } }], rawOutput: { error: part.state.error, metadata: part.state.metadata } } }).catch((err: any) => { log.error("failed to send tool error to ACP", { error: err }) })
      break
    }
  }
}

async function processTextPart(deps: AgentDeps, sessionId: string, message: SessionMessageResponse, part: any) {
  if (!part.text) return
  const audience: Role[] | undefined = part.synthetic ? ["assistant"] : part.ignored ? ["user"] : undefined
  await deps.connection.sessionUpdate({ sessionId, update: { sessionUpdate: message.info.role === "user" ? "user_message_chunk" : "agent_message_chunk", messageId: message.info.id, content: { type: "text", text: part.text, ...(audience && { annotations: { audience } }) } } }).catch((err: any) => { log.error("failed to send text to ACP", { error: err }) })
}

async function processFilePart(deps: AgentDeps, sessionId: string, message: SessionMessageResponse, part: any) {
  const url = part.url
  const filename = part.filename ?? "file"
  const mime = part.mime || "application/octet-stream"
  const messageChunk = message.info.role === "user" ? "user_message_chunk" : "agent_message_chunk"
  if (url.startsWith("file://")) {
    await deps.connection.sessionUpdate({ sessionId, update: { sessionUpdate: messageChunk, messageId: message.info.id, content: { type: "resource_link", uri: url, name: filename, mimeType: mime } } }).catch((err: any) => { log.error("failed to send resource_link to ACP", { error: err }) })
  } else if (url.startsWith("data:")) {
    const parsed = parseDataUrl(url)
    const dataMime = parsed?.mime
    const base64Data = parsed?.base64 ?? ""
    const effectiveMime = dataMime || mime
    if (effectiveMime.startsWith("image/")) {
      await deps.connection.sessionUpdate({ sessionId, update: { sessionUpdate: messageChunk, messageId: message.info.id, content: { type: "image", mimeType: effectiveMime, data: base64Data, uri: pathToFileURL(filename).href } } }).catch((err: any) => { log.error("failed to send image to ACP", { error: err }) })
    } else {
      const isText = effectiveMime.startsWith("text/") || effectiveMime === "application/json"
      const fileUri = pathToFileURL(filename).href
      const resource = isText ? { uri: fileUri, mimeType: effectiveMime, text: Buffer.from(base64Data, "base64").toString("utf-8") } : { uri: fileUri, mimeType: effectiveMime, blob: base64Data }
      await deps.connection.sessionUpdate({ sessionId, update: { sessionUpdate: messageChunk, messageId: message.info.id, content: { type: "resource", resource } } }).catch((err: any) => { log.error("failed to send resource to ACP", { error: err }) })
    }
  }
}

async function processReasoningPart(deps: AgentDeps, sessionId: string, message: SessionMessageResponse, part: any) {
  if (!part.text) return
  await deps.connection.sessionUpdate({ sessionId, update: { sessionUpdate: "agent_thought_chunk", messageId: message.info.id, content: { type: "text", text: part.text } } }).catch((err: any) => { log.error("failed to send reasoning to ACP", { error: err }) })
}
