import { ApiError } from "./errors.js";
import { mergeRoomMemberships, deleteRoomsData } from "./hooks.js";
import { sharesRoomWith } from "./membership.js";
import { joinAttempts, joinAttemptsOverall, roomCreations } from "./ratelimit.js";
import { archiveRoom, createRoom, getRoom, joinRoom, kickMember, leaveRoom, listRooms, patchRoom, rotateJoinCode, setMemberRole, } from "./handlers/rooms.js";
const PREFIX = "/rooms/v1";
export async function handleRoomsRequest(req, deps) {
    try {
        return json(200, await dispatch(req, deps));
    }
    catch (err) {
        if (err instanceof ApiError)
            return json(err.status, err.toBody());
        console.error("[rooms] unhandled error", err);
        return json(500, { error: { code: "internal", message: "Internal error" } });
    }
}
async function dispatch(req, deps) {
    const raw = req.method.toUpperCase();
    const override = raw === "POST" && req.header("x-http-method-override")?.toUpperCase() === "PATCH";
    const method = override ? "PATCH" : raw;
    if (!req.path.startsWith(`${PREFIX}/`)) {
        throw new ApiError(404, "not_found", "Unknown endpoint");
    }
    const claims = await authenticate(req, deps);
    const rest = req.path.slice(PREFIX.length + 1).split("/").filter(Boolean);
    if (rest.length === 1 && rest[0] === "join" && method === "POST") {
        if (!joinAttempts.allow(claims.userId) || !joinAttemptsOverall.allow("all")) {
            throw new ApiError(429, "rate_limited", "Too many join attempts; try again shortly");
        }
        return joinRoom(claims, req.body, deps.notify);
    }
    if (rest[0] === "rooms") {
        if (rest.length === 1) {
            if (method === "POST") {
                if (!roomCreations.allow(claims.userId)) {
                    throw new ApiError(429, "rate_limited", "Too many rooms created; try again shortly");
                }
                return createRoom(claims, req.body, deps.knownApps);
            }
            if (method === "GET")
                return listRooms(claims);
        }
        const roomId = rest[1];
        if (roomId !== undefined) {
            if (rest.length === 2) {
                if (method === "GET")
                    return getRoom(claims, roomId);
                if (method === "PATCH")
                    return patchRoom(claims, roomId, req.body);
            }
            if (rest.length === 3 && method === "POST") {
                if (rest[2] === "leave")
                    return leaveRoom(claims, roomId, deps.notify);
                if (rest[2] === "code")
                    return rotateJoinCode(claims, roomId);
                if (rest[2] === "archive")
                    return archiveRoom(claims, roomId);
            }
            if (rest[2] === "members" && rest[3] !== undefined) {
                const targetUserId = rest[3];
                if (rest.length === 4 && method === "DELETE") {
                    return kickMember(claims, roomId, targetUserId);
                }
                if (rest.length === 5 && rest[4] === "role" && method === "POST") {
                    return setMemberRole(claims, roomId, targetUserId, req.body);
                }
            }
        }
    }
    if (rest.length === 1 && rest[0] === "me" && method === "DELETE") {
        await deleteRoomsData(claims.userId, claims.appId);
        return { ok: true };
    }
    if (rest.length === 1 && rest[0] === "shares-with" && method === "POST") {
        const candidates = req.body?.candidates;
        if (!Array.isArray(candidates) || candidates.some((id) => typeof id !== "string")) {
            throw new ApiError(400, "invalid_body", "candidates must be an array of user ids");
        }
        if (candidates.length > 500) {
            throw new ApiError(400, "invalid_body", "candidates is capped at 500");
        }
        return { shares: await sharesRoomWith(claims.userId, candidates, claims.appId) };
    }
    if (rest.length === 1 && rest[0] === "supersede" && method === "POST") {
        return supersede(claims, req.body, deps);
    }
    throw new ApiError(404, "not_found", "Unknown endpoint");
}
async function authenticate(req, deps) {
    const header = req.header("authorization");
    if (!header?.startsWith("Bearer ")) {
        throw new ApiError(401, "invalid_token", "Authorization header is required");
    }
    let claims;
    try {
        claims = await deps.verifyToken(header.slice("Bearer ".length));
    }
    catch {
        throw new ApiError(401, "invalid_token", "Access token is invalid or expired");
    }
    if (!(await deps.knownApps(claims.appId))) {
        throw new ApiError(403, "unknown_app", "This app is not registered");
    }
    return claims;
}
async function supersede(claims, body, deps) {
    if (!deps.supersede)
        throw new ApiError(404, "not_found", "Unknown endpoint");
    const token = body?.supersession_token;
    if (typeof token !== "string" || !token) {
        throw new ApiError(400, "invalid_body", "supersession_token is required");
    }
    let moved;
    try {
        moved = await deps.supersede(token, claims);
    }
    catch {
        throw new ApiError(403, "invalid_supersession", "That statement was not issued for this caller");
    }
    await mergeRoomMemberships(moved.previousUserId, claims.userId);
    return { ok: true };
}
function json(status, body) {
    return { status, contentType: "application/json", body: JSON.stringify(body) };
}
