import { RequestError, type CancelNotification, type SetSessionModelRequest, type SetSessionModeRequest, type SetSessionModeResponse, type SetSessionConfigOptionRequest, type SetSessionConfigOptionResponse, type PromptRequest } from "@agentclientprotocol/sdk"
import { ProviderID, ModelID } from "../provider/schema"
import { AppRuntime } from "@/effect/app-runtime"
import { Agent as AgentModule } from "../agent/agent"
import type { AgentDeps } from "./agent-types"
import { log, defaultModel, sortProvidersByName, modelVariantsFromProviders, buildAvailableModels, formatModelIdWithVariant, buildVariantMeta, buildConfigOptions, sendUsageUpdate, buildUsage, parseUri, parseModelSelection } from "./agent-utils"

export async function prompt(deps: AgentDeps, params: PromptRequest) {
  const sessionID = params.sessionId
  const session = deps.sessionManager.get(sessionID)
  const directory = session.cwd
  const current = session.model
  const model = current ?? (await defaultModel(deps.config, directory))
  if (!current) deps.sessionManager.setModel(session.id, model)
  const agent = session.modeId ?? (await AppRuntime.runPromise(AgentModule.Service.use((svc: any) => svc.defaultAgent())))

  const parts: Array<{ type: "text"; text: string; synthetic?: boolean; ignored?: boolean } | { type: "file"; url: string; filename: string; mime: string }> = []
  for (const part of params.prompt) {
    switch (part.type) {
      case "text": {
        const audience = part.annotations?.audience
        const forAssistant = audience?.length === 1 && audience[0] === "assistant"
        const forUser = audience?.length === 1 && audience[0] === "user"
        parts.push({ type: "text" as const, text: part.text, ...(forAssistant && { synthetic: true }), ...(forUser && { ignored: true }) })
        break
      }
      case "image": {
        const parsed = parseUri(part.uri ?? "")
        const filename = parsed.type === "file" ? parsed.filename : "image"
        if (part.data) parts.push({ type: "file", url: `data:${part.mimeType};base64,${part.data}`, filename, mime: part.mimeType })
        else if (part.uri && part.uri.startsWith("http:")) parts.push({ type: "file", url: part.uri, filename, mime: part.mimeType })
        break
      }
      case "resource_link": {
        const parsed = parseUri(part.uri)
        if (part.name && parsed.type === "file") parsed.filename = part.name
        parts.push(parsed)
        break
      }
      case "resource": {
        const resource = part.resource
        if ("text" in resource && resource.text) parts.push({ type: "text", text: resource.text })
        else if ("blob" in resource && resource.blob && resource.mimeType) {
          const parsed = parseUri(resource.uri ?? "")
          const filename = parsed.type === "file" ? parsed.filename : "file"
          parts.push({ type: "file", url: `data:${resource.mimeType};base64,${resource.blob}`, filename, mime: resource.mimeType })
        }
        break
      }
    }
  }
  log.info("parts", { parts })

  const cmd = (() => {
    const text = parts.filter((p): p is { type: "text"; text: string } => p.type === "text").map((p) => p.text).join("").trim()
    if (!text.startsWith("/")) return
    const [name, ...rest] = text.slice(1).split(/\s+/)
    return { name, args: rest.join(" ").trim() }
  })()

  if (!cmd) {
    const response = await deps.sdk.session.prompt({ sessionID, model: { providerID: model.providerID, modelID: model.modelID }, variant: deps.sessionManager.getVariant(sessionID), parts, agent, directory })
    const msg = response.data?.info
    await sendUsageUpdate(deps.connection, deps.sdk, sessionID, directory)
    return { stopReason: "end_turn" as const, usage: msg ? buildUsage(msg) : undefined, _meta: {} }
  }

  const command = await deps.config.sdk.command.list({ directory }, { throwOnError: true }).then((x: any) => x.data!.find((c: any) => c.name === cmd.name))
  if (command) {
    const response = await deps.sdk.session.command({ sessionID, command: command.name, arguments: cmd.args, model: model.providerID + "/" + model.modelID, agent, directory })
    const msg = response.data?.info
    await sendUsageUpdate(deps.connection, deps.sdk, sessionID, directory)
    return { stopReason: "end_turn" as const, usage: msg ? buildUsage(msg) : undefined, _meta: {} }
  }

  switch (cmd.name) {
    case "compact":
      await deps.config.sdk.session.summarize({ sessionID, directory, providerID: model.providerID, modelID: model.modelID }, { throwOnError: true })
      break
  }
  await sendUsageUpdate(deps.connection, deps.sdk, sessionID, directory)
  return { stopReason: "end_turn" as const, _meta: {} }
}

export async function cancel(deps: AgentDeps, params: CancelNotification) {
  const session = deps.sessionManager.get(params.sessionId)
  await deps.config.sdk.session.abort({ sessionID: params.sessionId, directory: session.cwd }, { throwOnError: true })
}

export async function setSessionModel(deps: AgentDeps, params: SetSessionModelRequest) {
  const session = deps.sessionManager.get(params.sessionId)
  const providers = await deps.sdk.config.providers({ directory: session.cwd }, { throwOnError: true }).then((x: any) => x.data!.providers)
  const selection = parseModelSelection(params.modelId, providers)
  deps.sessionManager.setModel(session.id, selection.model)
  deps.sessionManager.setVariant(session.id, selection.variant)
  const entries = sortProvidersByName(providers)
  const availableVariants = modelVariantsFromProviders(entries as any, selection.model)
  return { _meta: buildVariantMeta({ model: selection.model, variant: selection.variant, availableVariants }) }
}

export async function setSessionMode(deps: AgentDeps, params: SetSessionModeRequest): Promise<SetSessionModeResponse | void> {
  const session = deps.sessionManager.get(params.sessionId)
  const availableModes = await loadAvailableModes(deps, session.cwd)
  if (!availableModes.some((mode: { id: string }) => mode.id === params.modeId)) throw new Error(`Agent not found: ${params.modeId}`)
  deps.sessionManager.setMode(params.sessionId, params.modeId)
}

export async function setSessionConfigOption(deps: AgentDeps, params: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse> {
  const session = deps.sessionManager.get(params.sessionId)
  const providers = await deps.sdk.config.providers({ directory: session.cwd }, { throwOnError: true }).then((x: any) => x.data!.providers)
  const entries = sortProvidersByName(providers)
  if (params.configId === "model") {
    if (typeof params.value !== "string") throw RequestError.invalidParams("model value must be a string")
    const selection = parseModelSelection(params.value, providers)
    deps.sessionManager.setModel(session.id, selection.model)
    deps.sessionManager.setVariant(session.id, selection.variant)
  } else if (params.configId === "mode") {
    if (typeof params.value !== "string") throw RequestError.invalidParams("mode value must be a string")
    const availableModes = await loadAvailableModes(deps, session.cwd)
    if (!availableModes.some((mode: { id: string }) => mode.id === params.value)) throw RequestError.invalidParams(JSON.stringify({ error: `Mode not found: ${params.value}` }))
    deps.sessionManager.setMode(session.id, params.value)
  } else {
    throw RequestError.invalidParams(JSON.stringify({ error: `Unknown config option: ${params.configId}` }))
  }
  const updatedSession = deps.sessionManager.get(session.id)
  const model = updatedSession.model ?? (await defaultModel(deps.config, session.cwd))
  const availableVariants = modelVariantsFromProviders(entries as any, model)
  const currentModelId = formatModelIdWithVariant(model, updatedSession.variant, availableVariants, true)
  const availableModels = buildAvailableModels(entries as any, { includeVariants: true })
  const modeState = await resolveModeState(deps, session.cwd, session.id)
  const modes = modeState.currentModeId ? { availableModes: modeState.availableModes, currentModeId: modeState.currentModeId } : undefined
  return { configOptions: buildConfigOptions({ currentModelId, availableModels, modes }) }
}

async function loadAvailableModes(deps: AgentDeps, directory: string) {
  return deps.config.sdk.app.agents({ directory }, { throwOnError: true }).then((resp: any) => resp.data!.filter((agent: any) => agent.mode !== "subagent" && !agent.hidden).map((agent: any) => ({ id: agent.name, name: agent.name, description: agent.description })))
}

async function resolveModeState(deps: AgentDeps, directory: string, sessionId: string) {
  const availableModes = await loadAvailableModes(deps, directory)
  const currentModeId = deps.sessionManager.get(sessionId).modeId || await (async () => {
    if (!availableModes.length) return undefined
    const defaultAgentName = await AppRuntime.runPromise(AgentModule.Service.use((svc: any) => svc.defaultAgent()))
    const resolvedModeId = availableModes.find((mode: any) => mode.name === defaultAgentName)?.id ?? availableModes[0].id
    deps.sessionManager.setMode(sessionId, resolvedModeId)
    return resolvedModeId
  })()
  return { availableModes, currentModeId }
}
