import {
  buildInstructions,
  buildReactionInstructions,
  highPersonality,
  injectPersonality,
  lowPersonality,
} from "./instructions"
import { tools } from "./tools"
import { Agent, InputGuardrailTripwireTriggered, run, user, system } from "@openai/agents"
import type { AgentInputItem, InputGuardrail } from "@openai/agents-core"
import { currentLocalTime } from "@workshop/shared/utils"
import { type Message, type OmitPartialGroupDMChannel } from "discord.js"
import z, { ZodObject } from "zod"

export type UserContext = {
  name: string
  currentTime: string
  msg: OmitPartialGroupDMChannel<Message<boolean>>
  personality?: string
}

const historyForChannel: Record<string, AgentInputItem[]> = {}
let abortController = new AbortController()

export const respondToUserMessage = async (msg: OmitPartialGroupDMChannel<Message<boolean>>) => {
  if (msg.partial || msg.author.bot || msg.content.trim() === "") {
    return
  }

  const context: UserContext = { name: msg.author.username, currentTime: currentLocalTime(), msg }
  agent.on("agent_start", (input) => {
    msg.channel.sendTyping()
  })

  return await respond(msg.content, agent, msg.channel.id, context)
}

export const respondToSystemMessage = async (msg: string, channelId: string) => {
  return await respond(msg, systemAgent, channelId)
}

const respond = async (
  content: string,
  agent: Agent<UserContext>,
  channelId: string,
  context?: UserContext
) => {
  const history = (historyForChannel[channelId] ||= [])

  try {
    // Stop the previous response
    abortController.abort()
    abortController = new AbortController()

    history.push(context ? user(content) : system(content))
    const result = await run(agent, history, { context, signal: abortController.signal })
    historyForChannel[channelId] = result.history

    return result.finalOutput
  } catch (error) {
    if (error instanceof InputGuardrailTripwireTriggered) {
      // This is totally fine, the guardrail just said we shouldn't reply
    } else if ((error as Error).name === "AbortError") {
      console.warn("Response was aborted, likely due to a new message.")
    } else {
      const content = `An error occurred while generating Spike's response: ${error}`
      console.error(`💥 ${content}`, error)
      history.push(system(content))

      return content
    }
  }
}

const guardrailAgent = new Agent<UserContext, ZodObject<any>>({
  name: "'Inject personality' check",
  model: "gpt-4.1-nano",
  instructions: injectPersonality(),
  outputType: z.object({
    includePersonality: z.boolean().describe("Whether to include humor or a personal anecdote"),
    reasoning: z.string().describe("One sentence explaining why or why not"),
  }),
})

const personalityGuardrail: InputGuardrail = {
  name: "'Inject personality' guardrail",
  execute: async ({ input, context }) => {
    const result = await run(guardrailAgent, input.slice(-4), { context })

    const userContext = context.context as UserContext
    if (result.finalOutput?.includePersonality) {
      userContext.personality = highPersonality(userContext)
    } else {
      userContext.personality = lowPersonality()
    }

    return {
      outputInfo: result.finalOutput,
      tripwireTriggered: false,
    }
  },
}

const reactionAgent = new Agent({
  name: "Reaction Handler",
  model: "gpt-4.1-nano",
  instructions: ({}) => buildReactionInstructions(),
})

const reactionGuardrail: InputGuardrail = {
  name: "Reaction Guardrail",
  execute: async ({ input, context }) => {
    const result = await run(reactionAgent, input.slice(-1), { context })
    const emoji = result.finalOutput?.trim()
    if (emoji && emoji !== "0") {
      console.log(`😐 Spike reacted with ${emoji}`)
      const msg = (context.context as UserContext).msg
      const history = (historyForChannel[msg.channel.id] ||= [])
      history.push(system(`Spike reacted with ${emoji}`))
      msg.react(emoji).catch((error) => {
        console.error(`💥 Failed to react with ${emoji}:`, error)
      })
    }

    // Always return false, we aren't really using this for a guardrail
    return { outputInfo: emoji, tripwireTriggered: false }
  },
}

const agent = new Agent<UserContext>({
  name: "Spike",
  model: "gpt-4.1",
  instructions: ({ context }) => {
    return buildInstructions(context)
  },
  inputGuardrails: [personalityGuardrail, reactionGuardrail],
  tools,
})

// There are no guardrails when responding to system messages
const systemAgent = agent.clone({ inputGuardrails: [] })
