// npm Trusted Publishing — OIDC token exchange for `gjsify publish`.
//
// Two-step flow, mirroring `refs/npm-cli/lib/utils/oidc.js`:
//
//   1. Request a GitHub Actions OIDC ID token (JWT) from the runner.
//      Requires `permissions: id-token: write` in the calling workflow.
//      GitHub provides `ACTIONS_ID_TOKEN_REQUEST_URL` and
//      `ACTIONS_ID_TOKEN_REQUEST_TOKEN` env vars; we GET the URL with
//      the runner-provided audience (`npm:registry.npmjs.org`).
//
//   2. Exchange that JWT at npm's `/-/npm/v1/oidc/token/exchange/package/<name>`
//      endpoint for a short-lived (~5 min) npm publish token. npm verifies
//      the JWT against the package's configured Trusted Publisher
//      (repository + workflow filename + optional environment) and either
//      issues a token or rejects with an explanatory error.
//
// The token returned by step 2 is used for the publish PUT in the same
// way the long-lived NPM_TOKEN would have been — drop-in replacement.
//
// Reference: refs/npm-cli/lib/utils/oidc.js
// Original: Copyright (c) npm contributors. Artistic-2.0.
export class OidcUnavailableError extends Error {
    reason;
    constructor(message, reason) {
        super(message);
        this.reason = reason;
        this.name = 'OidcUnavailableError';
    }
}
export class OidcExchangeError extends Error {
    status;
    body;
    packageName;
    constructor(message, status, body, packageName) {
        super(message);
        this.status = status;
        this.body = body;
        this.packageName = packageName;
        this.name = 'OidcExchangeError';
    }
}
/**
 * Probe whether OIDC publishing is available in the current process —
 * cheap env-var check, no network access. Used by `gjsify publish` to
 * decide between OIDC and token-based auth in auto-detect mode.
 */
export function hasGithubOidcEnv() {
    return Boolean(process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN);
}
/**
 * Request a GitHub Actions OIDC ID token for the given audience.
 * Throws `OidcUnavailableError` when the required env vars are missing
 * (caller can fall back to token auth) or when GitHub rejects the request.
 */
export async function fetchGithubOidcToken(audience, log) {
    const url = process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
    const bearer = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
    if (!url || !bearer) {
        throw new OidcUnavailableError('GitHub Actions OIDC env vars (ACTIONS_ID_TOKEN_REQUEST_{URL,TOKEN}) not set. ' +
            'The calling workflow needs `permissions: id-token: write`.', 'no-env');
    }
    const requestUrl = new URL(url);
    requestUrl.searchParams.set('audience', audience);
    log?.(`gjsify oidc: GET ${requestUrl.href.replace(bearer, '<bearer>')}`);
    const res = await fetch(requestUrl.href, {
        method: 'GET',
        headers: {
            Accept: 'application/json',
            Authorization: `Bearer ${bearer}`,
        },
    });
    if (!res.ok) {
        const text = await res.text().catch(() => '<no body>');
        throw new OidcUnavailableError(`Failed to fetch GitHub OIDC id_token: ${res.status} ${res.statusText} — ${text.slice(0, 200)}`, 'fetch-id-token');
    }
    const json = (await res.json().catch(() => ({})));
    if (!json.value) {
        throw new OidcUnavailableError('GitHub OIDC response missing `value` field', 'no-id-token');
    }
    return json.value;
}
/**
 * Exchange a GitHub OIDC JWT for a short-lived npm publish token at
 * `/-/npm/v1/oidc/token/exchange/package/<escaped-name>`. The npm
 * registry validates the JWT against the package's Trusted Publisher
 * config — if no Trusted Publisher is configured, or the JWT comes from
 * a repo/workflow that doesn't match, the exchange returns a 4xx with
 * a descriptive body which we propagate as `OidcExchangeError`.
 */
export async function exchangeOidcForNpmToken(args) {
    const { packageName, registry, idToken, log } = args;
    const registryClean = registry.endsWith('/') ? registry.slice(0, -1) : registry;
    // npm-package-arg's escapedName convention — same as gjsify publish.ts.
    const escapedName = packageName.startsWith('@')
        ? (() => {
            const slash = packageName.indexOf('/');
            const scope = packageName.slice(1, slash);
            const base = packageName.slice(slash + 1);
            return `@${encodeURIComponent(scope)}%2f${encodeURIComponent(base)}`;
        })()
        : encodeURIComponent(packageName);
    const exchangeUrl = `${registryClean}/-/npm/v1/oidc/token/exchange/package/${escapedName}`;
    log?.(`gjsify oidc: POST ${exchangeUrl}`);
    const res = await fetch(exchangeUrl, {
        method: 'POST',
        headers: {
            Authorization: `Bearer ${idToken}`,
            'Content-Type': 'application/json',
            Accept: 'application/json',
        },
        // npm's exchange endpoint accepts an empty JSON body — the JWT is
        // the proof, no additional claims needed from us.
        body: '{}',
    });
    const text = await res.text().catch(() => '');
    if (!res.ok) {
        throw new OidcExchangeError(`npm OIDC token exchange failed for ${packageName}: ${res.status} ${res.statusText} — ${text.slice(0, 300)}`, res.status, text, packageName);
    }
    let json;
    try {
        json = JSON.parse(text);
    }
    catch {
        throw new OidcExchangeError(`npm OIDC token exchange returned non-JSON body for ${packageName}: ${text.slice(0, 200)}`, res.status, text, packageName);
    }
    if (!json.token) {
        throw new OidcExchangeError(`npm OIDC token exchange returned no \`token\` field for ${packageName}`, res.status, text, packageName);
    }
    return json.token;
}
/**
 * End-to-end: probe env → fetch id-token → exchange for npm token.
 * One-shot convenience for `gjsify publish` and the verification command.
 */
export async function getNpmTrustedToken(opts) {
    const audience = `npm:${new URL(opts.registry).hostname}`;
    const idToken = await fetchGithubOidcToken(audience, opts.log);
    const token = await exchangeOidcForNpmToken({ ...opts, idToken });
    return { token, audience };
}
