import { runGh } from "./ui-github-repo-gh.js";
/** Child-process budget for one `gh` call. */
const GH_TIMEOUT_MS = 10_000;
/**
 * The required status-check contexts one branch rule names.
 * @param rule - One entry of the branch-rules response
 * @returns Its contexts, in the order the payload lists them
 */
function contextsOfRule(rule) {
    if (rule === null || typeof rule !== "object")
        return [];
    const parameters = Reflect.get(rule, "parameters");
    if (parameters === null || typeof parameters !== "object")
        return [];
    const checks = Reflect.get(parameters, "required_status_checks");
    if (!Array.isArray(checks))
        return [];
    return checks
        .map(check => check === null || typeof check !== "object"
        ? undefined
        : Reflect.get(check, "context"))
        .filter((context) => typeof context === "string");
}
/**
 * Pull every required status-check context out of a branch-rules payload.
 * @param payload - Parsed `rules/branches/{branch}` response
 * @returns Contexts, sorted and de-duplicated
 */
export function extractRequiredContexts(payload) {
    if (!Array.isArray(payload)) {
        throw new TypeError("Branch rules response was not an array");
    }
    return [...new Set(payload.flatMap(contextsOfRule))].sort((left, right) => left.localeCompare(right));
}
/**
 * The repository slug and default branch, from `gh repo view`.
 * @param projectRoot - Project root
 * @param signal - Cancellation signal
 * @returns Slug and branch
 */
async function readRepoTarget(projectRoot, signal) {
    const view = await runGh(["repo", "view", "--json", "nameWithOwner,defaultBranchRef"], projectRoot, GH_TIMEOUT_MS, signal);
    const parsed = JSON.parse(view);
    if (parsed === null || typeof parsed !== "object") {
        throw new TypeError("gh repo view did not return an object");
    }
    const slug = Reflect.get(parsed, "nameWithOwner");
    const branchRef = Reflect.get(parsed, "defaultBranchRef");
    const branch = branchRef !== null && typeof branchRef === "object"
        ? Reflect.get(branchRef, "name")
        : undefined;
    if (typeof slug !== "string" || typeof branch !== "string") {
        throw new TypeError("gh repo view omitted the repository or its branch");
    }
    return { slug, branch };
}
/**
 * Read the live ruleset through the `gh` CLI.
 * @param projectRoot - Project root, so `gh` resolves the right remote
 * @returns The required contexts guarding the default branch
 */
export const defaultRequiredContextsReader = async (projectRoot) => {
    const controller = new AbortController();
    const { slug, branch } = await readRepoTarget(projectRoot, controller.signal);
    const rules = await runGh(["api", `repos/${slug}/rules/branches/${branch}`], projectRoot, GH_TIMEOUT_MS, controller.signal);
    return extractRequiredContexts(JSON.parse(rules));
};
/**
 * Turn a `gh` failure into the narrowest honest reason.
 * @param error - Whatever the call threw
 * @returns A machine reason and a human message
 */
export function classifyGhFailure(error) {
    const text = error instanceof Error ? error.message : String(error);
    if (text.includes("ENOENT")) {
        return {
            reason: "gh-not-installed",
            message: "The GitHub CLI is not installed, so branch protection could not be read. Nothing here claims a gate does or does not block a merge.",
        };
    }
    if (/auth|credential|HTTP 401|HTTP 403/i.test(text)) {
        return {
            reason: "not-authenticated",
            message: "The GitHub CLI is not authenticated for this repository's host, so branch protection could not be read. Nothing here claims a gate does or does not block a merge.",
        };
    }
    return {
        reason: "call-failed",
        message: "Reading branch protection failed, so what a merge actually requires is unknown for this run.",
    };
}
/**
 * Read the live required contexts, degrading to `unknown` and never to a pass.
 * @param options - Probe inputs
 * @param options.projectRoot - Project root
 * @param options.offline - Whether the run may make a network call
 * @param options.read - Injectable reader
 * @returns The contexts, or an honest unknown
 */
export async function readRequiredContexts(options) {
    if (options.offline) {
        return {
            state: "unknown",
            reason: "offline",
            message: "This run was asked not to use the network, so branch protection was not read. The data exists; this run did not fetch it.",
        };
    }
    try {
        const value = await options.read(options.projectRoot);
        return { state: "verified", value };
    }
    catch (error) {
        return { state: "unknown", ...classifyGhFailure(error) };
    }
}
/**
 * Compare the contexts a declaration implies with the ones a ruleset requires.
 * @param declared - Contexts derived from the gates block
 * @param required - Contexts the ruleset actually requires
 * @returns The three-way comparison
 */
export function compareContexts(declared, required) {
    const requiredSet = new Set(required);
    const declaredSet = new Set(declared);
    const sort = (values) => [...values].sort((left, right) => left.localeCompare(right));
    return {
        matched: sort(declared.filter(context => requiredSet.has(context))),
        declaredNotRequired: sort(declared.filter(context => !requiredSet.has(context))),
        requiredNotDeclared: sort(required.filter(context => !declaredSet.has(context))),
    };
}
//# sourceMappingURL=gate-report-ruleset.js.map