import { stream } from "hono/streaming"
import { Hono, describeRoute, validator, resolver, z, Session, SessionID, SessionPrompt, MessageV2, errors, jsonRequest, runRequest, zodObject, log, Bus, NamedError } from "./_shared"

export const promptRoutes = new Hono()
  .post(
    "/:sessionID/message",
    describeRoute({
      summary: "Send message",
      description: "Create and send a new message to a session, streaming the AI response.",
      operationId: "session.prompt",
      responses: {
        200: {
          description: "Created message",
          content: {
            "application/json": {
              schema: resolver(
                z.object({
                  info: MessageV2.Assistant.zod,
                  parts: MessageV2.Part.zod.array(),
                }),
              ),
            },
          },
        },
        ...errors(400, 404),
      },
    }),
    validator(
      "param",
      z.object({
        sessionID: SessionID.zod,
      }),
    ),
    validator("json", zodObject(SessionPrompt.PromptInput).omit({ sessionID: true })),
    async (c) => {
      c.status(200)
      c.header("Content-Type", "application/json")
      return stream(c, async (stream) => {
        const sessionID = c.req.valid("param").sessionID
        const body = c.req.valid("json")
        const msg = await runRequest(
          "SessionRoutes.prompt",
          c,
          SessionPrompt.Service.use((svc) =>
            svc.prompt({ ...body, sessionID } as unknown as SessionPrompt.PromptInput),
          ),
        )
        void stream.write(JSON.stringify(msg))
      })
    },
  )
  .post(
    "/:sessionID/prompt_async",
    describeRoute({
      summary: "Send async message",
      description:
        "Create and send a new message to a session asynchronously, starting the session if needed and returning immediately.",
      operationId: "session.prompt_async",
      responses: {
        204: {
          description: "Prompt accepted",
        },
        ...errors(400, 404),
      },
    }),
    validator(
      "param",
      z.object({
        sessionID: SessionID.zod,
      }),
    ),
    validator("json", zodObject(SessionPrompt.PromptInput).omit({ sessionID: true })),
    async (c) => {
      const sessionID = c.req.valid("param").sessionID
      const body = c.req.valid("json")
      void runRequest(
        "SessionRoutes.prompt_async",
        c,
        SessionPrompt.Service.use((svc) =>
          svc.prompt({ ...body, sessionID } as unknown as SessionPrompt.PromptInput),
        ),
      ).catch((err) => {
        log.error("prompt_async failed", { sessionID, error: err })
        void Bus.publish(Session.Event.Error, {
          sessionID,
          error: new NamedError.Unknown({ message: err instanceof Error ? err.message : String(err) }).toObject(),
        })
      })

      return c.body(null, 204)
    },
  )
  .post(
    "/:sessionID/command",
    describeRoute({
      summary: "Send command",
      description: "Send a new command to a session for execution by the AI assistant.",
      operationId: "session.command",
      responses: {
        200: {
          description: "Created message",
          content: {
            "application/json": {
              schema: resolver(
                z.object({
                  info: MessageV2.Assistant.zod,
                  parts: MessageV2.Part.zod.array(),
                }),
              ),
            },
          },
        },
        ...errors(400, 404),
      },
    }),
    validator(
      "param",
      z.object({
        sessionID: SessionID.zod,
      }),
    ),
    validator("json", zodObject(SessionPrompt.CommandInput).omit({ sessionID: true })),
    async (c) =>
      jsonRequest("SessionRoutes.command", c, function* () {
        const sessionID = c.req.valid("param").sessionID
        const body = c.req.valid("json") as Omit<SessionPrompt.CommandInput, "sessionID">
        const svc = yield* SessionPrompt.Service
        return yield* svc.command({ ...body, sessionID })
      }),
  )
  .post(
    "/:sessionID/shell",
    describeRoute({
      summary: "Run shell command",
      description: "Execute a shell command within the session context and return the AI's response.",
      operationId: "session.shell",
      responses: {
        200: {
          description: "Created message",
          content: {
            "application/json": {
              schema: resolver(MessageV2.WithParts.zod),
            },
          },
        },
        ...errors(400, 404),
      },
    }),
    validator(
      "param",
      z.object({
        sessionID: SessionID.zod,
      }),
    ),
    validator("json", zodObject(SessionPrompt.ShellInput).omit({ sessionID: true })),
    async (c) =>
      jsonRequest("SessionRoutes.shell", c, function* () {
        const sessionID = c.req.valid("param").sessionID
        const body = c.req.valid("json") as Omit<SessionPrompt.ShellInput, "sessionID">
        const svc = yield* SessionPrompt.Service
        return yield* svc.shell({ ...body, sessionID })
      }),
  )
