import { z } from "zod";
import { formatZodError } from "../utils/zod.js";
const DEFAULT_MAX_JSON_BODY_BYTES = 1_000_000;
export class AsyncHttpRequestParseError extends Error {
    statusCode;
    constructor(message, statusCode = 400) {
        super(message);
        this.name = "AsyncHttpRequestParseError";
        this.statusCode = statusCode;
    }
}
export class AsyncHttpBodyTooLargeError extends Error {
    maxBytes;
    constructor(maxBytes) {
        super(`request body exceeds ${maxBytes} bytes`);
        this.name = "AsyncHttpBodyTooLargeError";
        this.maxBytes = maxBytes;
    }
}
export class AsyncHttpBodyParseError extends Error {
    constructor() {
        super("request body is not valid json");
        this.name = "AsyncHttpBodyParseError";
    }
}
export async function readJsonBody(request, options) {
    const maxBytes = options?.maxBytes ?? DEFAULT_MAX_JSON_BODY_BYTES;
    let body = "";
    let bytes = 0;
    request.setEncoding("utf8");
    for await (const chunk of request) {
        const textChunk = typeof chunk === "string" ? chunk : String(chunk);
        bytes += Buffer.byteLength(textChunk);
        if (bytes > maxBytes) {
            throw new AsyncHttpBodyTooLargeError(maxBytes);
        }
        body += textChunk;
    }
    if (!body.trim()) {
        return {};
    }
    try {
        return JSON.parse(body);
    }
    catch {
        throw new AsyncHttpBodyParseError();
    }
}
export function sendJson(response, statusCode, payload) {
    response.statusCode = statusCode;
    response.setHeader("content-type", "application/json; charset=utf-8");
    response.end(`${JSON.stringify(payload)}\n`);
}
export function sendError(response, statusCode, error) {
    sendJson(response, statusCode, { error });
}
export function sendOk(response, statusCode, data) {
    sendJson(response, statusCode, { ok: true, data });
}
function decodePathSegment(segment, errorMessage) {
    try {
        const decoded = decodeURIComponent(segment);
        if (!decoded) {
            throw new Error("empty");
        }
        return decoded;
    }
    catch {
        throw new AsyncHttpRequestParseError(errorMessage);
    }
}
function decodeSessionRoute(route) {
    return (segment) => ({
        route,
        sessionId: decodePathSegment(segment, "invalid session id"),
    });
}
const dynamicRoutes = [
    [/^\/v1\/sessions\/([^/]+)$/, "GET", decodeSessionRoute("get-session")],
    [/^\/v1\/sessions\/([^/]+)\/logs$/, "GET", decodeSessionRoute("get-session-logs")],
    [/^\/v1\/sessions\/([^/]+)\/messages$/, "POST", decodeSessionRoute("send-message")],
    [/^\/v1\/sessions\/([^/]+)\/interrupt$/, "POST", decodeSessionRoute("interrupt-session")],
    [
        /^\/v1\/cron\/jobs\/([^/]+)\/run$/,
        "POST",
        (segment) => ({
            route: "trigger-cron-job",
            jobId: decodePathSegment(segment, "invalid cron job id"),
        }),
    ],
];
export function decodeAsyncHttpRoute(method, pathname) {
    if (pathname === "/healthz") {
        return { route: "healthz" };
    }
    if (pathname === "/v1/sessions") {
        return method === "POST"
            ? { route: "create-session" }
            : method === "GET"
                ? { route: "list-sessions" }
                : "method-not-allowed";
    }
    if (pathname === "/v1/cron/jobs") {
        return method === "GET" ? { route: "list-cron-jobs" } : "method-not-allowed";
    }
    if (pathname === "/v1/cron/runs") {
        return method === "GET" ? { route: "list-cron-runs" } : "method-not-allowed";
    }
    for (const [pattern, expectedMethod, decode] of dynamicRoutes) {
        const segment = pattern.exec(pathname)?.[1];
        if (segment !== undefined) {
            return method === expectedMethod ? decode(segment) : "method-not-allowed";
        }
    }
    return "not-found";
}
const trimmedNonEmptyStringSchema = z
    .string()
    .transform((value) => value.trim())
    .pipe(z.string().min(1));
const createSessionBodySchema = z
    .object({
    projectId: trimmedNonEmptyStringSchema,
    prompt: trimmedNonEmptyStringSchema.optional(),
})
    .strict();
const sendMessageBodySchema = z.object({ text: trimmedNonEmptyStringSchema }).strict();
const cronRunsQueryInputSchema = z
    .object({
    jobId: z.array(z.string()).max(1).optional(),
    limit: z.array(z.string()).max(1).optional(),
})
    .strict();
const positiveIntegerSchema = z
    .string()
    .regex(/^\d+$/)
    .transform((value) => Number.parseInt(value, 10))
    .pipe(z.number().int().safe().positive());
function parseSchemaOrThrow(schema, raw, context) {
    const parsed = schema.safeParse(raw);
    if (!parsed.success) {
        throw new AsyncHttpRequestParseError(`${context}: ${formatZodError(parsed.error)}`);
    }
    return parsed.data;
}
export function parseCreateSessionBody(raw) {
    return parseSchemaOrThrow(createSessionBodySchema, raw, "invalid create-session body");
}
export function parseSendMessageBody(raw) {
    return parseSchemaOrThrow(sendMessageBodySchema, raw, "invalid send-message body");
}
export function parseCronRunsQuery(searchParams) {
    const query = {};
    for (const [key, value] of searchParams) {
        const values = query[key];
        if (values) {
            values.push(value);
        }
        else {
            query[key] = [value];
        }
    }
    const parsed = parseSchemaOrThrow(cronRunsQueryInputSchema, query, "invalid cron-runs query");
    const parseOptional = (value, schema, key) => value
        ? parseSchemaOrThrow(schema, value[0], `invalid cron-runs query parameter ${key}`)
        : undefined;
    const jobId = parseOptional(parsed.jobId, trimmedNonEmptyStringSchema, "jobId");
    const limit = parseOptional(parsed.limit, positiveIntegerSchema, "limit");
    return { ...(jobId === undefined ? {} : { jobId }), ...(limit === undefined ? {} : { limit }) };
}
//# sourceMappingURL=http_protocol.js.map