import { Effect, Types } from "effect"
import { MessageID, PartID } from "../core/schema"
import { MessageV2 } from "../message/message-v2"
import { NamedError } from "@saeeol/core/util/error"
import * as Log from "@saeeol/core/util/log"
import type { PromptInput } from "./prompt-schemas"
import type { PromptDeps } from "./prompt-types"
import { getModel, lastModel } from "./prompt-model"

const log = Log.create({ service: "session.prompt" })

export const createUserMessage = (deps: PromptDeps) =>
  Effect.fn("SessionPrompt.createUserMessage")(function* (input: PromptInput) {
    const agentName = input.agent || (yield* deps.agents.defaultAgent())
    const ag = yield* deps.agents.get(agentName)
    if (!ag) {
      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(deps.sessions.Event?.Error ?? { type: "session.error" }, { sessionID: input.sessionID, error: error.toObject() })
      throw error
    }
    const model = input.model ?? ag.model ?? (yield* lastModel(deps)(input.sessionID))
    const same = ag.model && model.providerID === ag.model.providerID && model.modelID === ag.model.modelID
    const full = !input.variant && ag.variant && same
      ? yield* deps.provider.getModel(model.providerID, model.modelID).pipe(Effect.catchDefect(() => Effect.void))
      : undefined
    const variant = input.variant ?? (ag.variant && full?.variants?.[ag.variant] ? ag.variant : undefined)
    const info: MessageV2.User = {
      id: input.messageID ?? MessageID.ascending(),
      role: "user",
      sessionID: input.sessionID,
      time: { created: Date.now() },
      tools: input.tools,
      agent: ag.name,
      model: { providerID: model.providerID, modelID: model.modelID, variant },
      system: input.system,
      format: input.format,
      editorContext: input.editorContext,
    }
    yield* Effect.addFinalizer(() => deps.instruction.clear(info.id))
    type Draft<T> = T extends MessageV2.Part ? Omit<T, "id"> & { id?: string } : never
    const assign = (part: Draft<MessageV2.Part>): MessageV2.Part => ({
      ...part,
      id: part.id ? PartID.make(part.id) : PartID.ascending(),
    })
    const resolvePart = (part: PromptInput["parts"][number]) =>
      deps.userPartSvc.resolve(part, {
        sessionID: input.sessionID,
        messageID: info.id,
        agent: input.agent!,
        agentPermission: ag.permission,
        model: info.model,
      })
    const parts = yield* Effect.forEach(input.parts, resolvePart, { concurrency: "unbounded" }).pipe(
      Effect.map((x: any) => x.flat().map(assign)),
    )
    yield* deps.plugin.trigger("chat.message", {
      sessionID: input.sessionID, agent: input.agent, model: input.model,
      messageID: input.messageID, variant: input.variant,
    }, { message: info, parts })
    const parsed = (MessageV2 as any).Info?.zod?.safeParse?.(info) ?? { success: true }
    if (!parsed.success) {
      log.error("invalid user message before save", {
        sessionID: input.sessionID, messageID: info.id, agent: info.agent,
        model: info.model, issues: parsed.error?.issues,
      })
    }
    parts.forEach((part: any, index: number) => {
      const p = (MessageV2 as any).Part?.zod?.safeParse?.(part) ?? { success: true }
      if (p.success) return
      log.error("invalid user part before save", {
        sessionID: input.sessionID, messageID: info.id, partID: part.id,
        partType: part.type, index, issues: p.error?.issues, part,
      })
    })
    yield* deps.sessions.updateMessage(info)
    for (const part of parts) yield* deps.sessions.updatePart(part)
    return { info, parts }
  }, Effect.scoped)
