import { EOL } from "os"
import type { Part } from "@saeeol/sdk/v2"
import type { SaeeolClient } from "@saeeol/sdk/v2"
import { expDelay } from "@/boxes/schedule"
import { SaeeolRunAuto } from "@/saeeol/cli/run-auto"
import { UI } from "../ui"
import { displayTool, displayTask, inline } from "./run-display"

type EventLoopDeps = {
  sdk: SaeeolClient
  sessionID: string
  format: string
  thinking: boolean
  auto: ReturnType<typeof SaeeolRunAuto.create>
}

export function emitJSON(type: string, data: Record<string, unknown>, sessionID: string) {
  process.stdout.write(JSON.stringify({ type, timestamp: Date.now(), sessionID, ...data }) + EOL)
}

export async function runEventLoop(deps: EventLoopDeps) {
  const { sdk, sessionID, format, thinking, auto } = deps
  const events = await sdk.event.subscribe()
  const toggles = new Map<string, boolean>()
  const MAX_RETRIES = 3
  let retries = 0
  let error: string | undefined
  const isJSON = format === "json"

  for await (const event of events.stream) {
    if (
      event.type === "message.updated" &&
      event.properties.info.role === "assistant" &&
      !isJSON &&
      !toggles.get("start")
    ) {
      UI.empty()
      UI.println(`> ${event.properties.info.agent} · ${event.properties.info.modelID}`)
      UI.empty()
      toggles.set("start", true)
    }

    if (event.type === "message.part.updated") {
      const part = event.properties.part as Part
      SaeeolRunAuto.track(auto, part)
      if (part.sessionID !== sessionID) continue

      if (part.type === "tool" && (part.state.status === "completed" || part.state.status === "error")) {
        if (isJSON) { emitJSON("tool_use", { part }, sessionID); continue }
        if (part.state.status === "completed") { displayTool(part); continue }
        inline({ icon: "✗", title: `${part.tool} failed` })
        UI.error(part.state.error)
        continue
      }

      if (part.type === "tool" && part.tool === "task" && part.state.status === "running" && !isJSON) {
        if (toggles.get(part.id)) continue
        displayTask(part)
        toggles.set(part.id, true)
        continue
      }

      if (part.type === "step-start") {
        if (isJSON) { emitJSON("step_start", { part }, sessionID); continue }
      }

      if (part.type === "step-finish") {
        if (isJSON) { emitJSON("step_finish", { part }, sessionID); continue }
      }

      if (part.type === "text" && part.time?.end) {
        if (isJSON) { emitJSON("text", { part }, sessionID); continue }
        const text = part.text.trim()
        if (!text) continue
        if (!process.stdout.isTTY) { process.stdout.write(text + EOL); continue }
        UI.empty()
        UI.println(text)
        UI.empty()
        continue
      }

      if (part.type === "reasoning" && part.time?.end && thinking) {
        if (isJSON) { emitJSON("reasoning", { part }, sessionID); continue }
        const text = part.text.trim()
        if (!text) continue
        const line = `Thinking: ${text}`
        if (process.stdout.isTTY) {
          UI.empty()
          UI.println(`${UI.Style.TEXT_DIM}\u001b[3m${line}\u001b[0m${UI.Style.TEXT_NORMAL}`)
          UI.empty()
          continue
        }
        process.stdout.write(line + EOL)
      }
    }

    if (event.type === "session.error") {
      const props = event.properties
      if (props.sessionID !== sessionID || !props.error) continue
      let err = String(props.error.name)
      if ("data" in props.error && props.error.data && "message" in props.error.data) {
        err = String(props.error.data.message)
      }
      error = error ? error + EOL + err : err
      if (isJSON) { emitJSON("error", { error: props.error }, sessionID); continue }
      UI.error(err)
    }

    if (event.type === "session.status" && event.properties.sessionID === sessionID) {
      if (event.properties.status.type === "busy") retries = 0
      if (event.properties.status.type === "idle") break
    }

    if (event.type === "permission.asked") {
      const permission = event.properties
      if (auto) {
        if (!SaeeolRunAuto.allowed(auto, permission.sessionID)) continue
        await sdk.permission.reply({ requestID: permission.id, reply: "once" })
        continue
      }
      if (permission.sessionID !== sessionID) continue
      UI.println(
        UI.Style.TEXT_WARNING_BOLD + "!",
        UI.Style.TEXT_NORMAL +
          `permission requested: ${permission.permission} (${permission.patterns.join(", ")}); auto-rejecting`,
      )
      await sdk.permission.reply({ requestID: permission.id, reply: "reject" })
    }

    if (event.type === "session.network.asked") {
      const request = event.properties
      if (request.sessionID !== sessionID) continue
      retries++
      if (retries > MAX_RETRIES) {
        UI.println(
          UI.Style.TEXT_WARNING_BOLD + "!",
          UI.Style.TEXT_NORMAL + `network retry limit reached (${MAX_RETRIES}); rejecting`,
        )
        await sdk.network.reject({ requestID: request.id })
        continue
      }
      const delay = Math.min(expDelay(5000, retries - 1), 60_000)
      await new Promise((r) => setTimeout(r, delay))
      await sdk.network.reply({ requestID: request.id })
    }
  }

  return error
}
