var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { BaseGitProviderClient } from "./BaseGitProviderClient";
import { WorkspaceBackendError } from "./errors";
import { parseGitRemote } from "./gitRemote";
function inferApiBase(config) {
    const configured = config.apiBaseUrl;
    if (configured === null || configured === void 0 ? void 0 : configured.trim())
        return configured.trim().replace(/\/+$/g, "");
    return "https://api.bitbucket.org/2.0";
}
function parseWorkspaceRepo(remoteUrl) {
    const { host, repoPath } = parseGitRemote(remoteUrl);
    const parts = repoPath.split("/").filter(Boolean);
    if (parts.length < 2)
        throw new Error("Invalid Bitbucket remote URL");
    const workspace = parts[0];
    const repo = parts[1];
    return { host, workspace, repo };
}
export class BitbucketProviderClient extends BaseGitProviderClient {
    static canHandle(config) {
        const provider = config.provider;
        if (provider && provider !== "auto")
            return provider === "bitbucket";
        try {
            const { host } = parseGitRemote(config.remoteUrl);
            return host === "bitbucket.org";
        }
        catch (_a) {
            return false;
        }
    }
    requestJson(apiBase, config, path, init) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            const headers = {
                Accept: "application/json",
            };
            const username = (_a = config.auth.username) === null || _a === void 0 ? void 0 : _a.trim();
            const token = (_b = config.auth.token) === null || _b === void 0 ? void 0 : _b.trim();
            if (username && token)
                headers.Authorization = `Basic ${this.base64Encode(`${username}:${token}`)}`;
            const url = path.startsWith("http") ? path : `${apiBase}${path}`;
            const res = yield fetch(url, Object.assign(Object.assign({}, init), { cache: "no-store", headers: Object.assign(Object.assign({}, headers), init === null || init === void 0 ? void 0 : init.headers) }));
            const status = res.status;
            const contentType = res.headers.get("content-type") || "";
            if (contentType.includes("application/json")) {
                const text = yield res.text();
                if (text.trim()) {
                    try {
                        const json = JSON.parse(text);
                        return { status, json };
                    }
                    catch (_c) {
                        return { status };
                    }
                }
            }
            return { status };
        });
    }
    requestText(apiBase, config, url, init) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            const headers = {
                Accept: "text/plain",
            };
            const username = (_a = config.auth.username) === null || _a === void 0 ? void 0 : _a.trim();
            const token = (_b = config.auth.token) === null || _b === void 0 ? void 0 : _b.trim();
            if (username && token)
                headers.Authorization = `Basic ${this.base64Encode(`${username}:${token}`)}`;
            const res = yield fetch(url.startsWith("http") ? url : `${apiBase}${url}`, Object.assign(Object.assign({}, init), { cache: "no-store", headers: Object.assign(Object.assign({}, headers), init === null || init === void 0 ? void 0 : init.headers) }));
            return { status: res.status, text: yield res.text() };
        });
    }
    validateAccess(config) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            const { host, workspace, repo } = parseWorkspaceRepo(config.remoteUrl);
            if (host !== "bitbucket.org") {
                throw new WorkspaceBackendError("UNKNOWN", "This Bitbucket provider currently supports Bitbucket Cloud (bitbucket.org) only.");
            }
            const apiBase = inferApiBase(config);
            const username = (_a = config.auth.username) === null || _a === void 0 ? void 0 : _a.trim();
            if (!username) {
                throw new WorkspaceBackendError("UNKNOWN", "Bitbucket Cloud requires a username (workspace/user) in addition to an app password token.");
            }
            const { status } = yield this.requestJson(apiBase, config, `/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repo)}`);
            this.ensureOk(status, "Could not access Bitbucket repository with the provided credentials.");
        });
    }
    resolveBranch(apiBase, config, workspace, repo) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b, _c;
            const configured = (_a = config.branch) === null || _a === void 0 ? void 0 : _a.trim();
            if (configured)
                return configured;
            const { status, json } = yield this.requestJson(apiBase, config, `/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repo)}`);
            this.ensureOk(status, "Failed to resolve repository default branch.");
            const inferred = (_c = (_b = json === null || json === void 0 ? void 0 : json.mainbranch) === null || _b === void 0 ? void 0 : _b.name) === null || _c === void 0 ? void 0 : _c.trim();
            if (inferred)
                return inferred;
            return "main";
        });
    }
    listFolder(config, folderId) {
        return __awaiter(this, void 0, void 0, function* () {
            const { host, workspace, repo } = parseWorkspaceRepo(config.remoteUrl);
            if (host !== "bitbucket.org")
                return [];
            const apiBase = inferApiBase(config);
            const relPath = (folderId === null || folderId === void 0 ? void 0 : folderId.trim()) || "";
            const fullPath = this.joinPath(config.rootPath, relPath);
            const ref = yield this.resolveBranch(apiBase, config, workspace, repo);
            const basePath = `/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repo)}/src/${encodeURIComponent(ref)}` +
                (fullPath ? `/${this.encodePath(fullPath)}` : "");
            const entries = [];
            let nextUrl = `${apiBase}${basePath}?format=meta&pagelen=100`;
            for (let guard = 0; guard < 50 && nextUrl; guard++) {
                const response = yield this.requestJson(apiBase, config, nextUrl);
                const status = response.status;
                const json = response.json;
                if (status === 404)
                    return [];
                this.ensureOk(status, "Failed to list folder contents.");
                const values = (json === null || json === void 0 ? void 0 : json.values) || [];
                for (const v of values) {
                    const name = v.path.split("/").filter(Boolean).pop() || v.path;
                    if (v.type === "commit_directory") {
                        const id = relPath ? this.joinPath(relPath, name) : name;
                        entries.push({ kind: "folder", id, label: name, parentId: relPath || undefined });
                    }
                    if (v.type === "commit_file") {
                        if (!/\.sparql$/i.test(name))
                            continue;
                        const id = relPath ? this.joinPath(relPath, name) : name;
                        const label = name.replace(/\.sparql$/i, "");
                        entries.push({ kind: "query", id, label, parentId: relPath || undefined });
                    }
                }
                nextUrl = json === null || json === void 0 ? void 0 : json.next;
            }
            entries.sort((a, b) => {
                if (a.kind !== b.kind)
                    return a.kind === "folder" ? -1 : 1;
                return a.label.toLowerCase().localeCompare(b.label.toLowerCase());
            });
            return entries;
        });
    }
    getLatestCommitHash(apiBase, config, workspace, repo, filePath) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            const ref = yield this.resolveBranch(apiBase, config, workspace, repo);
            const qs = new URLSearchParams();
            qs.set("path", filePath);
            qs.set("pagelen", "1");
            const { status, json } = yield this.requestJson(apiBase, config, `/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repo)}/commits/${encodeURIComponent(ref)}?${qs.toString()}`);
            if (status === 404)
                return undefined;
            this.ensureOk(status, "Failed to read query metadata.");
            const commits = (json === null || json === void 0 ? void 0 : json.values) || [];
            return (_a = commits[0]) === null || _a === void 0 ? void 0 : _a.hash;
        });
    }
    readQuery(config, queryId) {
        return __awaiter(this, void 0, void 0, function* () {
            const { host, workspace, repo } = parseWorkspaceRepo(config.remoteUrl);
            if (host !== "bitbucket.org")
                throw new WorkspaceBackendError("NOT_FOUND", "Unsupported Bitbucket host");
            const apiBase = inferApiBase(config);
            const filePath = this.joinPath(config.rootPath, queryId);
            const ref = yield this.resolveBranch(apiBase, config, workspace, repo);
            const { status, text } = yield this.requestText(apiBase, config, `/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repo)}/src/${encodeURIComponent(ref)}/${this.encodePath(filePath)}`);
            this.ensureOk(status, "Failed to read query.");
            const versionTag = yield this.getLatestCommitHash(apiBase, config, workspace, repo, filePath);
            return { queryText: text, versionTag };
        });
    }
    writeQuery(config, queryId, queryText, options) {
        return __awaiter(this, void 0, void 0, function* () {
            const { host, workspace, repo } = parseWorkspaceRepo(config.remoteUrl);
            if (host !== "bitbucket.org")
                throw new WorkspaceBackendError("UNKNOWN", "Unsupported Bitbucket host");
            const apiBase = inferApiBase(config);
            const filePath = this.joinPath(config.rootPath, queryId);
            const current = yield this.getLatestCommitHash(apiBase, config, workspace, repo, filePath);
            if ((options === null || options === void 0 ? void 0 : options.expectedVersionTag) && current && options.expectedVersionTag !== current) {
                throw new WorkspaceBackendError("CONFLICT", "The file changed remotely since it was last opened. Please reload the managed query and try saving again.");
            }
            const message = this.getCommitMessage(queryId, options, !current);
            const form = new FormData();
            form.append("message", message);
            const ref = yield this.resolveBranch(apiBase, config, workspace, repo);
            if (ref)
                form.append("branch", ref);
            form.append(filePath, new Blob([queryText], { type: "text/plain" }));
            const { status } = yield this.requestJson(apiBase, config, `/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repo)}/src`, {
                method: "POST",
                body: form,
            });
            this.ensureOk(status, "Failed to save query to Bitbucket workspace.");
        });
    }
    listVersions(config, queryId) {
        return __awaiter(this, void 0, void 0, function* () {
            const { host, workspace, repo } = parseWorkspaceRepo(config.remoteUrl);
            if (host !== "bitbucket.org")
                return [];
            const apiBase = inferApiBase(config);
            const filePath = this.joinPath(config.rootPath, queryId);
            const ref = yield this.resolveBranch(apiBase, config, workspace, repo);
            const qs = new URLSearchParams();
            qs.set("path", filePath);
            qs.set("pagelen", "30");
            const { status, json } = yield this.requestJson(apiBase, config, `/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repo)}/commits/${encodeURIComponent(ref)}?${qs.toString()}`);
            this.ensureOk(status, "Failed to list query versions.");
            const commits = (json === null || json === void 0 ? void 0 : json.values) || [];
            return commits
                .map((c) => {
                var _a, _b, _c;
                const createdAt = c.date || new Date().toISOString();
                const author = ((_b = (_a = c.author) === null || _a === void 0 ? void 0 : _a.user) === null || _b === void 0 ? void 0 : _b.display_name) || ((_c = c.author) === null || _c === void 0 ? void 0 : _c.raw);
                return { id: c.hash, createdAt, author, message: c.message };
            })
                .filter((v) => !!v.id);
        });
    }
    readVersion(config, queryId, versionId) {
        return __awaiter(this, void 0, void 0, function* () {
            const { host, workspace, repo } = parseWorkspaceRepo(config.remoteUrl);
            if (host !== "bitbucket.org")
                throw new WorkspaceBackendError("NOT_FOUND", "Unsupported Bitbucket host");
            const apiBase = inferApiBase(config);
            const filePath = this.joinPath(config.rootPath, queryId);
            const { status, text } = yield this.requestText(apiBase, config, `/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repo)}/src/${encodeURIComponent(versionId)}/${this.encodePath(filePath)}`);
            this.ensureOk(status, "Failed to read query version.");
            return { queryText: text, versionTag: versionId };
        });
    }
    deleteQuery(config, queryId) {
        return __awaiter(this, void 0, void 0, function* () {
            const { host, workspace, repo } = parseWorkspaceRepo(config.remoteUrl);
            if (host !== "bitbucket.org")
                throw new WorkspaceBackendError("UNKNOWN", "Unsupported Bitbucket host");
            const apiBase = inferApiBase(config);
            const filePath = this.joinPath(config.rootPath, queryId);
            const ref = yield this.resolveBranch(apiBase, config, workspace, repo);
            const form = new FormData();
            form.append("message", this.getDeleteMessage(queryId));
            if (ref)
                form.append("branch", ref);
            form.append("files", filePath);
            const { status } = yield this.requestJson(apiBase, config, `/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repo)}/src`, { method: "POST", body: form });
            if (status === 404)
                return;
            this.ensureOk(status, "Failed to delete query from Bitbucket workspace.");
        });
    }
}
//# sourceMappingURL=BitbucketProviderClient.js.map