"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createRemoteProjectResolutionCache = createRemoteProjectResolutionCache;
exports.clearRemoteProjectResolutionCacheForCwd = clearRemoteProjectResolutionCacheForCwd;
exports.getTrackedProjectKey = getTrackedProjectKey;
exports.resolveRepositoryHostProjectRef = resolveRepositoryHostProjectRef;
exports.resolveTicketTrackerProjectRef = resolveTicketTrackerProjectRef;
exports.resolveTrackedProjectRef = resolveTrackedProjectRef;
exports.resolveSupportedProviders = resolveSupportedProviders;
exports.resolveConfigRemoteProject = resolveConfigRemoteProject;
exports.getTrackedProjectStorageSegments = getTrackedProjectStorageSegments;
exports.getEventHookProjectStorageSegments = getEventHookProjectStorageSegments;
const catalog_1 = require("../i18n/catalog");
const tracked_project_1 = require("../core/tracked-project");
const native_git_repository_client_1 = require("../git/native-git-repository-client");
const remote_url_safety_1 = require("../git/remote-url-safety");
const registry_1 = require("./repository-host/registry");
const registry_2 = require("./ticket-tracker/registry");
const git_remote_project_ref_1 = require("./git-remote-project-ref");
const project_ref_1 = require("./gitea/project-ref");
const auth_1 = require("./gitea/auth");
const project_ref_2 = require("./gitlab/project-ref");
const project_ref_3 = require("./github/project-ref");
function createRemoteProjectResolutionCache() {
    return { entries: new Map() };
}
function clearRemoteProjectResolutionCacheForCwd(cache, cwd) {
    const cacheKeyPrefix = `${cwd}:`;
    for (const key of cache.entries.keys()) {
        if (key.startsWith(cacheKeyPrefix)) {
            cache.entries.delete(key);
        }
    }
}
function getTrackedProjectKey(ref) {
    return resolveTrackedProjectRef(ref).trackedProjectKey;
}
function resolveRepositoryHostProjectRef(repositoryHost, locale = catalog_1.DEFAULT_APP_LOCALE) {
    const provider = (0, registry_1.requireRepositoryHostProvider)(repositoryHost.kind, locale);
    const projectRef = provider.normalizeProjectRef(repositoryHost.projectRef);
    provider.validateProjectRef(projectRef, locale);
    return { provider, projectRef };
}
function resolveTicketTrackerProjectRef(ticketTracker, locale = catalog_1.DEFAULT_APP_LOCALE) {
    const provider = (0, registry_2.requireTicketTrackerProvider)(ticketTracker.kind, locale);
    const projectRef = provider.normalizeProjectRef(ticketTracker.projectRef);
    provider.validateProjectRef(projectRef, locale);
    return { provider, projectRef };
}
function resolveTrackedProjectRef(ref, locale = catalog_1.DEFAULT_APP_LOCALE) {
    const repositoryHost = resolveRepositoryHostProjectRef(ref.repositoryHost, locale);
    const ticketTracker = resolveTicketTrackerProjectRef(ref.ticketTracker, locale);
    const normalizedRef = (0, tracked_project_1.toTrackedProjectRef)({
        repositoryHost: {
            kind: repositoryHost.provider.kind,
            projectRef: repositoryHost.projectRef,
        },
        ticketTracker: {
            kind: ticketTracker.provider.kind,
            projectRef: ticketTracker.projectRef,
        },
    });
    return {
        ref: normalizedRef,
        repositoryHostProvider: repositoryHost.provider,
        ticketTrackerProvider: ticketTracker.provider,
        trackedProjectKey: (0, tracked_project_1.serializeTrackedProjectKey)(normalizedRef),
    };
}
function resolveSupportedProviders(config) {
    const resolvedTrackedProject = resolveTrackedProjectRef((0, tracked_project_1.toTrackedProjectRef)(config), config.i18n.defaultLanguage);
    return {
        ...resolvedTrackedProject,
        config: applyTrackedProjectRef(config, resolvedTrackedProject.ref),
    };
}
async function resolveConfigRemoteProject(cwd, config, input = {}) {
    const cache = input.cache;
    const remoteName = config.repositoryHost.remoteName;
    const remoteUrl = await (0, native_git_repository_client_1.discoverGitRemoteUrl)(cwd, remoteName);
    if (!remoteUrl) {
        throw new Error(`Git remote '${remoteName}' is not configured.`);
    }
    const cacheKey = `${cwd}:${remoteName}`;
    const cached = cache?.entries.get(cacheKey);
    if (cached?.remoteUrl === remoteUrl) {
        return applyResolvedRemoteProject(config, cached.ref);
    }
    const ref = await detectTrackedProjectRefFromRemoteUrl(remoteUrl);
    cache?.entries.set(cacheKey, {
        remoteName,
        remoteUrl,
        ref,
    });
    return applyResolvedRemoteProject(config, ref);
}
async function detectTrackedProjectRefFromRemoteUrl(remoteUrl) {
    const parsedRemote = (0, git_remote_project_ref_1.parseGitRemoteProjectRef)(remoteUrl);
    if (parsedRemote === null) {
        throw new Error(`Could not parse Git remote URL: ${(0, remote_url_safety_1.redactRemoteUrlCredentials)(remoteUrl)}.`);
    }
    const candidates = buildRemoteProjectCandidates(remoteUrl);
    if (candidates.length === 0) {
        throw new Error(`Could not detect a supported repository host from Git remote URL: ${(0, remote_url_safety_1.redactRemoteUrlCredentials)(remoteUrl)}.`);
    }
    const results = await Promise.all(candidates.map(async (candidate) => ({
        candidate,
        result: await probeRemoteProjectCandidate(candidate),
    })));
    const successful = results.filter((entry) => entry.result.ok);
    if (successful.length === 1) {
        const candidate = successful[0]?.candidate;
        if (!candidate) {
            throw new Error("Remote project detection failed unexpectedly.");
        }
        return (0, tracked_project_1.toTrackedProjectRef)({
            repositoryHost: candidate,
            ticketTracker: candidate,
        });
    }
    const redactedRemoteUrl = (0, remote_url_safety_1.redactRemoteUrlCredentials)(remoteUrl);
    if (successful.length > 1) {
        throw new Error(`Git remote URL matched multiple supported repository hosts (${successful
            .map((entry) => entry.candidate.kind)
            .join(", ")}): ${redactedRemoteUrl}.`);
    }
    const reasons = results
        .map((entry) => `${entry.candidate.kind}: ${entry.result.ok
        ? "not reachable"
        : (entry.result.reason ?? "not reachable")}`)
        .join("; ");
    throw new Error(`Could not access a supported repository host for Git remote URL ${redactedRemoteUrl}.${reasons ? ` ${reasons}` : ""}`);
}
function buildRemoteProjectCandidates(remoteUrl) {
    const parsedRemote = (0, git_remote_project_ref_1.parseGitRemoteProjectRef)(remoteUrl);
    if (parsedRemote === null) {
        return [];
    }
    const github = resolveCandidateProjectRef("github", remoteUrl);
    if (parsedRemote.host === "github.com") {
        return github ? [github] : [];
    }
    const gitlab = resolveCandidateProjectRef("gitlab", remoteUrl);
    if (parsedRemote.host === "gitlab.com") {
        return gitlab ? [gitlab] : [];
    }
    const gitea = resolveCandidateProjectRef("gitea", remoteUrl);
    return [gitlab, gitea].filter((candidate) => candidate !== null);
}
function resolveCandidateProjectRef(kind, remoteUrl) {
    const provider = (0, registry_1.requireRepositoryHostProvider)(kind);
    const projectRef = provider.detectProjectRefFromRemoteUrl?.(remoteUrl);
    return projectRef ? { kind, projectRef } : null;
}
async function probeRemoteProjectCandidate(candidate) {
    try {
        switch (candidate.kind) {
            case "github":
                await probeGitHubProject(candidate.projectRef);
                return { ok: true };
            case "gitlab":
                await probeGitLabProject(candidate.projectRef);
                return { ok: true };
            case "gitea":
                await probeGiteaProject(candidate.projectRef);
                return { ok: true };
        }
    }
    catch (error) {
        return {
            ok: false,
            reason: error instanceof Error ? error.message : String(error),
        };
    }
}
async function probeGitHubProject(projectRef) {
    const { owner, repo } = (0, project_ref_3.parseGitHubProjectRef)(projectRef);
    const provider = (0, registry_1.requireRepositoryHostProvider)("github");
    const token = await provider.auth.resolveCredential({ kind: "github", projectRef }, process.env);
    const response = await fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`, {
        headers: {
            accept: "application/vnd.github+json",
            ...(token ? { authorization: `Bearer ${token}` } : {}),
        },
    });
    await assertProbeResponseOk("GitHub", response);
}
async function probeGitLabProject(projectRef) {
    const provider = (0, registry_1.requireRepositoryHostProvider)("gitlab");
    const token = await provider.auth.resolveCredential({ kind: "gitlab", projectRef }, process.env);
    const response = await fetch((0, project_ref_2.buildGitLabProjectApiBaseUrl)(projectRef), {
        headers: {
            accept: "application/json",
            ...(token ? { "private-token": token } : {}),
        },
    });
    await assertProbeResponseOk("GitLab", response);
}
async function probeGiteaProject(projectRef) {
    const { owner, repo } = (0, project_ref_1.parseGiteaProjectRef)(projectRef);
    const provider = (0, registry_1.requireRepositoryHostProvider)("gitea");
    const credential = await provider.auth.resolveCredential({ kind: "gitea", projectRef }, process.env);
    const response = await fetch((0, project_ref_1.buildGiteaApiUrl)(projectRef, `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`), {
        headers: {
            accept: "application/json",
            ...(credential
                ? { authorization: (0, auth_1.toGiteaAuthorizationHeader)(credential) }
                : {}),
        },
    });
    await assertProbeResponseOk("Gitea", response);
}
async function assertProbeResponseOk(providerName, response) {
    if (response.ok) {
        return;
    }
    let detail = "";
    try {
        detail = (await response.text()).trim();
    }
    catch {
        detail = "";
    }
    throw new Error(`${providerName} repository probe failed with status ${response.status}${detail ? `: ${detail}` : ""}.`);
}
function applyResolvedRemoteProject(config, ref) {
    const resolvedTrackedProject = resolveTrackedProjectRef(ref, config.i18n.defaultLanguage);
    return {
        ...resolvedTrackedProject,
        config: applyTrackedProjectRef(config, resolvedTrackedProject.ref),
    };
}
function getTrackedProjectStorageSegments(ref) {
    const { ref: normalizedRef, repositoryHostProvider, ticketTrackerProvider, } = resolveTrackedProjectRef(ref);
    return [
        normalizedRef.repositoryHost.kind,
        ...repositoryHostProvider.toStorageSegments(normalizedRef.repositoryHost.projectRef),
        normalizedRef.ticketTracker.kind,
        ...ticketTrackerProvider.toStorageSegments(normalizedRef.ticketTracker.projectRef),
    ];
}
function getEventHookProjectStorageSegments(ref) {
    const { ref: normalizedRef, repositoryHostProvider, ticketTrackerProvider, } = resolveTrackedProjectRef(ref);
    const repositorySegments = [
        normalizedRef.repositoryHost.kind,
        ...repositoryHostProvider.toStorageSegments(normalizedRef.repositoryHost.projectRef),
    ];
    const trackerSegments = [
        normalizedRef.ticketTracker.kind,
        ...ticketTrackerProvider.toStorageSegments(normalizedRef.ticketTracker.projectRef),
    ];
    if (normalizedRef.repositoryHost.kind === normalizedRef.ticketTracker.kind &&
        normalizedRef.repositoryHost.projectRef ===
            normalizedRef.ticketTracker.projectRef) {
        return repositorySegments;
    }
    return ["repository", ...repositorySegments, "tracker", ...trackerSegments];
}
function applyTrackedProjectRef(config, ref) {
    return {
        ...config,
        repositoryHost: {
            ...config.repositoryHost,
            kind: ref.repositoryHost.kind,
            projectRef: ref.repositoryHost.projectRef,
        },
        ticketTracker: {
            ...config.ticketTracker,
            kind: ref.ticketTracker.kind,
            projectRef: ref.ticketTracker.projectRef,
        },
    };
}
