/**
 * Bridge HTTP routes for repository linking + locate/clone (local-first).
 *
 * Routes:
 *   GET    /workspaces/{ws}/repositories              → sanitized linked list
 *   POST   /workspaces/{ws}/repositories              → link a remote repo
 *   DELETE /workspaces/{ws}/repositories/{id}         → unlink (leaves disk)
 *   POST   /workspaces/{ws}/repositories/{id}/locate  → bind existing working copy
 *   POST   /workspaces/{ws}/repositories/{id}/clone   → clone into an allowed root
 *
 * Every write returns the sanitized repository projection (the same shape as the
 * read), which carries owner/repo/state but NEVER tokens or local paths. All
 * responses pass the public leak guard. Failures collapse to a finite reason
 * code; the offending local path / remote URL never appears in a response.
 */
import { authorizeWorkspace } from "./authGuards.js";
import { writeJson } from "./publicResponse.js";
import { readJsonBody } from "./requestParsing.js";
import { firstCapture, matchPath, secondCapture } from "./routeMatching.js";
import { isRecord } from "../utils/json.js";
import { RepositoryProjectionService } from "../capabilities/repositoryProjectionService.js";
import { RepositoryLinkError } from "../repos/RepositoryLinkService.js";
const REPOSITORY_RESPONSE_SCAN_OPTIONS = {
    allowedFieldNames: [
        "id",
        "workspaceId",
        "source",
        "displayLabel",
        "providerLabel",
        "status",
        "tone",
        "lastSyncLabel",
        "lastErrorCode",
        "owner",
        "repo",
        "defaultBranch",
        "linkState",
        "cloneState",
        "hasLocalWorkingCopy",
        "contractVersion",
        "generatedAt",
        "items",
        "reasonCode",
    ],
};
const MAX_BODY_FIELD = 512;
export async function handleRepositoryRoutes(context) {
    const { auth, request, response, url } = context;
    const collectionMatch = matchPath(url.pathname, /^\/workspaces\/([^/]+)\/repositories$/);
    if (collectionMatch) {
        const workspaceId = firstCapture(collectionMatch);
        if (!authorizeWorkspace(response, auth, workspaceId))
            return true;
        if (request.method === "GET") {
            writeProjection(context, workspaceId, 200);
            return true;
        }
        if (request.method === "POST") {
            await handleLink(context, workspaceId);
            return true;
        }
        return false;
    }
    const itemMatch = matchPath(url.pathname, /^\/workspaces\/([^/]+)\/repositories\/([^/]+)$/);
    if (itemMatch && request.method === "DELETE") {
        const workspaceId = firstCapture(itemMatch);
        const bindingId = secondCapture(itemMatch);
        if (!authorizeWorkspace(response, auth, workspaceId))
            return true;
        await handleUnlink(context, workspaceId, bindingId);
        return true;
    }
    const locateMatch = matchPath(url.pathname, /^\/workspaces\/([^/]+)\/repositories\/([^/]+)\/locate$/);
    if (locateMatch && request.method === "POST") {
        const workspaceId = firstCapture(locateMatch);
        const bindingId = secondCapture(locateMatch);
        if (!authorizeWorkspace(response, auth, workspaceId))
            return true;
        await handleLocate(context, workspaceId, bindingId);
        return true;
    }
    const cloneMatch = matchPath(url.pathname, /^\/workspaces\/([^/]+)\/repositories\/([^/]+)\/clone$/);
    if (cloneMatch && request.method === "POST") {
        const workspaceId = firstCapture(cloneMatch);
        const bindingId = secondCapture(cloneMatch);
        if (!authorizeWorkspace(response, auth, workspaceId))
            return true;
        await handleClone(context, workspaceId, bindingId);
        return true;
    }
    return false;
}
async function handleLink(context, workspaceId) {
    const { app, response } = context;
    const body = await readJsonBody(context.request);
    const remoteUrl = readBodyString(body, "remoteUrl");
    if (!remoteUrl) {
        writeJson(response, 400, {
            error: "invalid_request",
            message: 'A "remoteUrl" string is required.',
        });
        return;
    }
    const displayLabel = readBodyString(body, "displayLabel");
    try {
        await app.repositoryLink.link({
            workspaceId,
            remoteUrl,
            ...(displayLabel ? { displayLabel } : {}),
        });
        writeProjection(context, workspaceId, 201);
    }
    catch (error) {
        writeRepositoryError(response, error);
    }
}
async function handleUnlink(context, workspaceId, bindingId) {
    const { app, response } = context;
    try {
        const removed = await app.repositoryLink.unlink({ workspaceId, bindingId });
        if (!removed) {
            writeJson(response, 404, {
                error: "repo-binding-not-found",
                message: "No such repository binding for this workspace.",
            });
            return;
        }
        writeProjection(context, workspaceId, 200);
    }
    catch (error) {
        writeRepositoryError(response, error);
    }
}
async function handleLocate(context, workspaceId, bindingId) {
    const { app, response } = context;
    const body = await readJsonBody(context.request);
    const localPath = readBodyString(body, "localPath");
    if (!localPath) {
        writeJson(response, 400, {
            error: "invalid_request",
            message: 'A "localPath" string is required.',
        });
        return;
    }
    try {
        await app.repositoryLink.locate({ workspaceId, bindingId, localPath });
        writeProjection(context, workspaceId, 200);
    }
    catch (error) {
        writeRepositoryError(response, error);
    }
}
async function handleClone(context, workspaceId, bindingId) {
    const { app, response } = context;
    const body = await readJsonBody(context.request);
    const targetDir = readBodyString(body, "targetDir");
    if (!targetDir) {
        writeJson(response, 400, {
            error: "invalid_request",
            message: 'A "targetDir" string is required.',
        });
        return;
    }
    try {
        await app.repositoryLink.clone({ workspaceId, bindingId, targetDir });
        writeProjection(context, workspaceId, 200);
    }
    catch (error) {
        writeRepositoryError(response, error);
    }
}
function writeProjection(context, workspaceId, statusCode) {
    const projection = new RepositoryProjectionService(context.app.localStore).listForWorkspace(workspaceId);
    writeJson(context.response, statusCode, projection, {
        surface: "workspace.repositories",
        scanOptions: REPOSITORY_RESPONSE_SCAN_OPTIONS,
    });
}
function readBodyString(body, key) {
    if (!isRecord(body))
        return undefined;
    const value = body[key];
    if (typeof value !== "string")
        return undefined;
    const trimmed = value.trim();
    if (!trimmed || trimmed.length > MAX_BODY_FIELD)
        return undefined;
    return trimmed;
}
const REPO_ERROR_STATUS = {
    "repo-binding-not-found": 404,
    "repo-path-not-found": 404,
    "clone-not-found": 404,
    "locate-not-a-git-repo": 404,
    "repo-remote-url-invalid": 422,
    "repo-path-not-absolute": 422,
    "repo-path-outside-allowed-roots": 422,
    "locate-remote-mismatch": 422,
    "repo-roots-not-configured": 409,
    "repo-clone-in-progress": 409,
    "clone-target-occupied": 409,
    "clone-auth-failed": 502,
    "clone-not-found-remote": 502,
    "clone-timeout": 504,
    "clone-failed": 502,
    "locate-failed": 500,
};
function writeRepositoryError(response, error) {
    if (error instanceof RepositoryLinkError) {
        const statusCode = REPO_ERROR_STATUS[error.reasonCode] ?? 500;
        writeJson(response, statusCode, {
            error: error.reasonCode,
            // The message is the reason code only — never a path or URL.
            message: error.reasonCode,
        });
        return;
    }
    writeJson(response, 500, {
        error: "repo-unknown-error",
        message: "An unknown repository error occurred.",
    });
}
