import { Hono, describeRoute, validator, resolver, z, Session, MessageV2, SessionRunState, SessionID, MessageID, PartID, errors, jsonRequest, runRequest } from "./_shared"
import { Effect } from "effect"

export const messageRoutes = new Hono()
  .get(
    "/:sessionID/message",
    describeRoute({
      summary: "Get session messages",
      description: "Retrieve all messages in a session, including user prompts and AI responses.",
      operationId: "session.messages",
      responses: {
        200: {
          description: "List of messages",
          content: {
            "application/json": {
              schema: resolver(MessageV2.WithParts.zod.array()),
            },
          },
        },
        ...errors(400, 404),
      },
    }),
    validator(
      "param",
      z.object({
        sessionID: SessionID.zod,
      }),
    ),
    validator(
      "query",
      z
        .object({
          limit: z.coerce
            .number()
            .int()
            .min(0)
            .optional()
            .meta({ description: "Maximum number of messages to return" }),
          before: z
            .string()
            .optional()
            .meta({ description: "Opaque cursor for loading older messages" })
            .refine(
              (value) => {
                if (!value) return true
                try {
                  MessageV2.cursor.decode(value)
                  return true
                } catch {
                  return false
                }
              },
              { message: "Invalid cursor" },
            ),
        })
        .refine((value) => !value.before || value.limit !== undefined, {
          message: "before requires limit",
          path: ["before"],
        }),
    ),
    async (c) => {
      const query = c.req.valid("query")
      const sessionID = c.req.valid("param").sessionID
      if (query.limit === undefined || query.limit === 0) {
        const messages = await runRequest(
          "SessionRoutes.messages",
          c,
          Effect.gen(function* () {
            const session = yield* Session.Service
            yield* session.get(sessionID)
            return yield* session.messages({ sessionID })
          }),
        )
        return c.json(messages)
      }

      const page = await MessageV2.page({
        sessionID,
        limit: query.limit,
        before: query.before,
      })
      if (page.cursor) {
        const url = new URL(c.req.url)
        url.searchParams.set("limit", query.limit.toString())
        url.searchParams.set("before", page.cursor)
        c.header("Access-Control-Expose-Headers", "Link, X-Next-Cursor")
        c.header("Link", `<${url.toString()}>; rel="next"`)
        c.header("X-Next-Cursor", page.cursor)
      }
      return c.json(page.items)
    },
  )
  .get(
    "/:sessionID/message/:messageID",
    describeRoute({
      summary: "Get message",
      description: "Retrieve a specific message from a session by its message ID.",
      operationId: "session.message",
      responses: {
        200: {
          description: "Message",
          content: {
            "application/json": {
              schema: resolver(
                z.object({
                  info: MessageV2.Info.zod,
                  parts: MessageV2.Part.zod.array(),
                }),
              ),
            },
          },
        },
        ...errors(400, 404),
      },
    }),
    validator(
      "param",
      z.object({
        sessionID: SessionID.zod,
        messageID: MessageID.zod,
      }),
    ),
    async (c) => {
      const params = c.req.valid("param")
      const message = await MessageV2.get({
        sessionID: params.sessionID,
        messageID: params.messageID,
      })
      return c.json(message)
    },
  )
  .delete(
    "/:sessionID/message/:messageID",
    describeRoute({
      summary: "Delete message",
      description:
        "Permanently delete a specific message (and all of its parts) from a session. This does not revert any file changes that may have been made while processing the message.",
      operationId: "session.deleteMessage",
      responses: {
        200: {
          description: "Successfully deleted message",
          content: {
            "application/json": {
              schema: resolver(z.boolean()),
            },
          },
        },
        ...errors(400, 404),
      },
    }),
    validator(
      "param",
      z.object({
        sessionID: SessionID.zod,
        messageID: MessageID.zod,
      }),
    ),
    async (c) =>
      jsonRequest("SessionRoutes.deleteMessage", c, function* () {
        const params = c.req.valid("param")
        const state = yield* SessionRunState.Service
        const session = yield* Session.Service
        yield* state.assertNotBusy(params.sessionID)
        yield* session.removeMessage({
          sessionID: params.sessionID,
          messageID: params.messageID,
        })
        return true
      }),
  )
  .delete(
    "/:sessionID/message/:messageID/part/:partID",
    describeRoute({
      description: "Delete a part from a message",
      operationId: "part.delete",
      responses: {
        200: {
          description: "Successfully deleted part",
          content: {
            "application/json": {
              schema: resolver(z.boolean()),
            },
          },
        },
        ...errors(400, 404),
      },
    }),
    validator(
      "param",
      z.object({
        sessionID: SessionID.zod,
        messageID: MessageID.zod,
        partID: PartID.zod,
      }),
    ),
    async (c) =>
      jsonRequest("SessionRoutes.deletePart", c, function* () {
        const params = c.req.valid("param")
        const svc = yield* Session.Service
        yield* svc.removePart({
          sessionID: params.sessionID,
          messageID: params.messageID,
          partID: params.partID,
        })
        return true
      }),
  )
  .patch(
    "/:sessionID/message/:messageID/part/:partID",
    describeRoute({
      description: "Update a part in a message",
      operationId: "part.update",
      responses: {
        200: {
          description: "Successfully updated part",
          content: {
            "application/json": {
              schema: resolver(MessageV2.Part.zod),
            },
          },
        },
        ...errors(400, 404),
      },
    }),
    validator(
      "param",
      z.object({
        sessionID: SessionID.zod,
        messageID: MessageID.zod,
        partID: PartID.zod,
      }),
    ),
    validator("json", MessageV2.Part.zod),
    async (c) => {
      const params = c.req.valid("param")
      const body = c.req.valid("json")
      if (body.id !== params.partID || body.messageID !== params.messageID || body.sessionID !== params.sessionID) {
        throw new Error(
          `Part mismatch: body.id='${body.id}' vs partID='${params.partID}', body.messageID='${body.messageID}' vs messageID='${params.messageID}', body.sessionID='${body.sessionID}' vs sessionID='${params.sessionID}'`,
        )
      }
      return jsonRequest("SessionRoutes.updatePart", c, function* () {
        const svc = yield* Session.Service
        return yield* svc.updatePart(body)
      })
    },
  )
