import { Effect } from "effect"
import { NamedError } from "@saeeol/core/util/error"
import * as EffectLogger from "@saeeol/core/effect/logger"
import { ConfigMarkdown } from "@/config/markdown"
import { Shell } from "@/shell/shell"
import { Provider } from "@/provider/provider"
import { Process } from "@/util/process"
import { SessionID } from "../core/schema"
import * as Session from "../core/session"
import { resolvePromptParts } from "./prompt-resolve"
import { getModel, lastModel } from "./prompt-model"
import type { CommandInput } from "./prompt-schemas"
import type { PromptDeps } from "./prompt-types"

const elog = EffectLogger.create({ service: "session.prompt" })

export const commandHandler = (deps: PromptDeps, markReviewTelemetry?: (parts: any, cmd: string) => void) => {
  const resolveParts = resolvePromptParts(deps)

  return Effect.fn("SessionPrompt.command")(function* (input: CommandInput) {
    yield* elog.info("command", { sessionID: input.sessionID, command: input.command, agent: input.agent })
    const cmd = yield* deps.commands.get(input.command)
    if (!cmd) {
      const available = (yield* deps.commands.list()).map((c: any) => c.name)
      const hint = available.length ? ` Available commands: ${available.join(", ")}` : ""
      const error = new NamedError.Unknown({ message: `Command not found: "${input.command}".${hint}` })
      yield* deps.bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
      throw error
    }
    const argsRegex = /\S+|"[^"]*"|'[^']*'/g
    const quoteTrimRegex = /^["']|["']$/g
    const placeholderRegex = /\$(\d+)/g
    const agentName = cmd.agent ?? input.agent ?? (yield* deps.agents.defaultAgent())
    const raw = input.arguments.match(argsRegex) ?? []
    const args = raw.map((arg: string) => arg.replace(quoteTrimRegex, ""))
    const templateCommand = yield* Effect.promise(async () => cmd.template)
    const placeholders = templateCommand.match(placeholderRegex) ?? []
    let last = 0
    for (const item of placeholders) { const value = Number(item.slice(1)); if (value > last) last = value }
    const withArgs = templateCommand.replaceAll(placeholderRegex, (_: string, index: string) => {
      const position = Number(index)
      const argIndex = position - 1
      if (argIndex >= args.length) return ""
      if (position === last) return args.slice(argIndex).join(" ")
      return args[argIndex]
    })
    const usesArgumentsPlaceholder = templateCommand.includes("$ARGUMENTS")
    let template = withArgs.replaceAll("$ARGUMENTS", input.arguments)
    if (placeholders.length === 0 && !usesArgumentsPlaceholder && input.arguments.trim()) {
      template = template + "\n\n" + input.arguments
    }
    const bashRegex = /```bash\n([\s\S]*?)```/g
    const shellMatches = ConfigMarkdown.shell(template)
    if (shellMatches.length > 0) {
      const cfg = yield* deps.config.get()
      const sh = Shell.preferred(cfg.shell)
      const results = yield* Effect.promise(() =>
        Promise.all(shellMatches.map(async (match: RegExpExecArray) => (await Process.text([match[1]], { shell: sh, nothrow: true })).text)),
      )
      let index = 0
      template = template.replace(bashRegex, () => results[index++])
    }
    template = template.trim()
    const taskModel = yield* Effect.gen(function* () {
      if (cmd.model) return Provider.parseModel(cmd.model)
      if (cmd.agent) { const cmdAgent = yield* deps.agents.get(cmd.agent); if (cmdAgent?.model) return cmdAgent.model }
      if (input.model) return Provider.parseModel(input.model)
      return yield* lastModel(deps)(input.sessionID)
    })
    yield* getModel(deps)(taskModel.providerID, taskModel.modelID, input.sessionID)
    const agent = yield* deps.agents.get(agentName)
    if (!agent) {
      const available = (yield* deps.agents.list()).filter((a: any) => !a.hidden).map((a: any) => a.name)
      const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
      const error = new NamedError.Unknown({ message: `Agent not found: "${agentName}".${hint}` })
      yield* deps.bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
      throw error
    }
    const templateParts = yield* resolveParts(template)
    markReviewTelemetry?.(templateParts, input.command)
    const isSubtask = (agent.mode === "subagent" && cmd.subtask !== false) || cmd.subtask === true
    const parts = isSubtask
      ? [{ type: "subtask" as const, agent: agent.name, description: cmd.description ?? "",
          command: input.command, model: { providerID: taskModel.providerID, modelID: taskModel.modelID },
          prompt: (templateParts.find((y: any) => y.type === "text") as any)?.text ?? "" }]
      : [...templateParts, ...(input.parts ?? [])]
    const userAgent = isSubtask ? (input.agent ?? (yield* deps.agents.defaultAgent())) : agentName
    const userModel = isSubtask ? input.model ? Provider.parseModel(input.model) : yield* lastModel(deps)(input.sessionID) : taskModel
    yield* deps.plugin.trigger("command.execute.before",
      { command: input.command, sessionID: input.sessionID, arguments: input.arguments }, { parts })
    return { sessionID: input.sessionID, messageID: input.messageID, model: userModel, agent: userAgent, parts, variant: input.variant, command: input.command, arguments: input.arguments }
  })
}
