/**
 * Pure REST handlers for `/api/projects/*`.
 *
 * Extracted from the deleted Hono `routes.ts` so the relay envelope dispatcher
 * (Batch 3) can call them with a parsed payload instead of a Hono context.
 *
 * Contract:
 *  - Inputs are already-parsed JSON objects (or `unknown` shapes the handler
 *    validates). The dispatcher upstream parses the relay envelope body.
 *  - Outputs are the wire shapes the landing client already expects, so the
 *    Batch 3 dispatcher can JSON-stringify them straight into the response
 *    envelope without remapping.
 *  - Errors are thrown as typed `HandlerError` subclasses; the dispatcher
 *    maps `BAD_REQUEST` → 400-equivalent and `NOT_FOUND` → 404-equivalent.
 *  - Stream teardown on project delete is the caller's responsibility — the
 *    handler returns the cascaded session ids so the dispatcher can hand
 *    them to `SessionStreamManager.disposeProjectStreams` BEFORE the SQL
 *    cascade has already torn down (in our model the cascade has already
 *    happened by the time we return; that's intentional and matches the
 *    old route's ordering — the manager is best-effort cleanup).
 */
import { validateProjectPath } from "../paths.js";
import { BadRequestError, NotFoundError } from "./errors.js";
import { writeStudioBindingAt } from "../../studio-binding.js";
export function handleListProjects(store) {
    return store.listProjects();
}
export function handleCreateProject(store, body) {
    if (typeof body.name !== "string" || !body.name.trim()) {
        throw new BadRequestError("name (non-empty string) is required");
    }
    if (typeof body.path !== "string" || !body.path.trim()) {
        throw new BadRequestError("path (non-empty string) is required");
    }
    const validation = validateProjectPath(body.path);
    if (!validation.ok) {
        throw new BadRequestError(validation.error ?? "Invalid path");
    }
    return store.createProject({ name: body.name, path: validation.path });
}
export function handleGetProject(store, id) {
    const project = store.getProject(id);
    if (!project)
        throw new NotFoundError("Project not found");
    return project;
}
export function handleUpdateProject(store, id, body) {
    const update = {};
    if (body.name !== undefined) {
        if (typeof body.name !== "string") {
            throw new BadRequestError("name must be a string");
        }
        update.name = body.name;
    }
    if (body.path !== undefined) {
        if (typeof body.path !== "string") {
            throw new BadRequestError("path must be a string");
        }
        const validation = validateProjectPath(body.path);
        if (!validation.ok) {
            throw new BadRequestError(validation.error ?? "Invalid path");
        }
        update.path = validation.path;
    }
    if (update.name === undefined && update.path === undefined) {
        throw new BadRequestError("At least one of name, path must be provided");
    }
    const updated = store.updateProject(id, update);
    if (!updated)
        throw new NotFoundError("Project not found");
    return updated;
}
export function handleDeleteProject(store, id) {
    const project = store.getProject(id);
    if (!project)
        throw new NotFoundError("Project not found");
    const result = store.deleteProject(id);
    if (!result.deleted)
        throw new NotFoundError("Project not found");
    return { sessionIds: result.sessionIds };
}
/**
 * Bind a local project directory to an Aexol Studio project by writing
 * `.aexol/aexol.jsonc` into the project's filesystem path.
 */
export async function handleBindStudioProject(store, id, body) {
    const project = store.getProject(id);
    if (!project)
        throw new NotFoundError("Project not found");
    if (typeof body.studioProjectId !== "string" || !body.studioProjectId.trim()) {
        throw new BadRequestError("studioProjectId (non-empty string) is required");
    }
    const studioProjectId = body.studioProjectId.trim();
    const studioTeamId = typeof body.studioTeamId === "string" && body.studioTeamId.trim()
        ? body.studioTeamId.trim()
        : undefined;
    const studioProjectName = typeof body.studioProjectName === "string" && body.studioProjectName.trim()
        ? body.studioProjectName.trim()
        : undefined;
    const binding = {
        $schema: "https://aexol.ai/schemas/studio-binding.json",
        projectId: studioProjectId,
        ...(studioTeamId ? { teamId: studioTeamId } : {}),
        ...(studioProjectName ? { name: studioProjectName } : {}),
    };
    await writeStudioBindingAt(project.path, binding);
    // Re-read to pick up the freshly written studio fields
    const updated = store.getProject(id);
    return {
        project: updated,
        studioProjectId,
        studioProjectName: studioProjectName || studioProjectId,
        ...(studioTeamId ? { studioTeamId } : {}),
    };
}
export function handleListSessionsByProject(store, id) {
    const project = store.getProject(id);
    if (!project)
        throw new NotFoundError("Project not found");
    return store.listSessionsByProject(id);
}
