const BRIDGE_TOKEN_HEADER = "x-patchtrail-bridge-token";
export class LoopbackRemoteCommandDispatcher {
    #baseUrl;
    #bridgeToken;
    #mintSession;
    #fetch;
    constructor(options) {
        this.#baseUrl = options.baseUrl.replace(/\/+$/u, "");
        this.#bridgeToken = options.bridgeToken;
        this.#mintSession = options.mintSession;
        this.#fetch = options.fetchImpl ?? defaultFetch;
    }
    async dispatch(input) {
        switch (input.category) {
            case "read":
                // Reading is harmless and has no write route — acknowledge as a no-op.
                return { ok: true };
            case "review-decisions":
                return this.#reviewDecision(input);
            case "open-prs":
                return this.#openPr(input);
            case "start-runs":
                return this.#startRun(input);
            default:
                return { ok: false, reasonCode: "dispatch_unsupported_category" };
        }
    }
    async #reviewDecision(input) {
        const reviewId = readString(input.params, "reviewId");
        const decision = readString(input.params, "decision");
        if (!reviewId || !decision) {
            return invalidParams();
        }
        const body = {
            decision,
            idempotencyKey: input.commandId,
        };
        const reasonCode = readString(input.params, "reasonCode");
        if (reasonCode)
            body.reasonCode = reasonCode;
        const noteLabel = readString(input.params, "noteLabel");
        if (noteLabel)
            body.noteLabel = noteLabel;
        return this.#post(`/workspaces/${encodeURIComponent(input.workspaceId)}/review-queue/${encodeURIComponent(reviewId)}/decision`, { [BRIDGE_TOKEN_HEADER]: this.#bridgeToken }, body);
    }
    async #openPr(input) {
        const repository = readString(input.params, "repository");
        const headBranch = readString(input.params, "headBranch");
        const baseBranch = readString(input.params, "baseBranch");
        const title = readString(input.params, "title");
        if (!repository || !headBranch || !baseBranch || !title) {
            return invalidParams();
        }
        const body = {
            repository,
            headBranch,
            baseBranch,
            title,
        };
        const prBody = readString(input.params, "body");
        if (prBody)
            body.body = prBody;
        return this.#post(`/workspaces/${encodeURIComponent(input.workspaceId)}/forge/draft-pr`, { [BRIDGE_TOKEN_HEADER]: this.#bridgeToken }, body);
    }
    async #startRun(input) {
        const rootPath = readString(input.params, "rootPath");
        const receipt = input.params.runIntentBridgeReceipt;
        if (!rootPath || typeof receipt !== "object" || receipt === null) {
            return invalidParams();
        }
        // Run routes reject the bridge token — mint a scoped local-UI session.
        let session;
        try {
            session = await this.#mintSession({
                workspaceId: input.workspaceId,
                deviceId: input.deviceId,
                scopes: ["run-intents:create"],
            });
        }
        catch {
            return { ok: false, reasonCode: "dispatch_session_mint_failed" };
        }
        return this.#post(`/workspaces/${encodeURIComponent(input.workspaceId)}/run-intent-receipts/runs`, {
            authorization: `Bearer ${session.sessionToken}`,
            origin: session.origin,
        }, { rootPath, runIntentBridgeReceipt: receipt });
    }
    async #post(path, headers, body) {
        let response;
        try {
            response = await this.#fetch(`${this.#baseUrl}${path}`, {
                method: "POST",
                headers: { "content-type": "application/json", ...headers },
                body: JSON.stringify(body),
            });
        }
        catch {
            // Never surface raw transport errors — bounded reason only.
            return { ok: false, reasonCode: "dispatch_network_error" };
        }
        return response.ok
            ? { ok: true }
            : { ok: false, reasonCode: "dispatch_route_rejected" };
    }
}
function readString(params, key) {
    const value = params[key];
    return typeof value === "string" && value.length > 0 ? value : undefined;
}
function invalidParams() {
    return { ok: false, reasonCode: "dispatch_invalid_params" };
}
const defaultFetch = async (url, init) => {
    const response = await fetch(url, {
        method: init.method,
        headers: init.headers,
        body: init.body,
    });
    return { ok: response.ok, status: response.status };
};
