/**
 * GitHub Copilot subscription auth: device OAuth flow and token refresh.
 * Uses the same approach as opencode-copilot-auth for compatibility.
 * @see https://github.com/anomalyco/opencode-copilot-auth
 *
 * CLIENT_ID: VS Code Copilot OAuth client (Iv1.b507a08c87ecfe98). External
 * dependency; changing it may break device flow compatibility.
 */
const GITHUB_COM = 'github.com';
const CLIENT_ID = 'Iv1.b507a08c87ecfe98';
/** Headers required by Copilot API; export for reuse in provider-factory. */
export const COPILOT_HEADERS = {
    'User-Agent': 'GitHubCopilotChat/0.35.0',
    'Editor-Version': 'vscode/1.107.0',
    'Editor-Plugin-Version': 'copilot-chat/0.35.0',
    'Copilot-Integration-Id': 'vscode-chat',
};
function normalizeDomain(url) {
    return url.replace(/^https?:\/\//, '').replace(/\/$/, '');
}
export function getCopilotUrls(domain) {
    const base = `https://${normalizeDomain(domain)}`;
    return {
        deviceCodeUrl: `${base}/login/device/code`,
        accessTokenUrl: `${base}/login/oauth/access_token`,
        copilotTokenUrl: domain === GITHUB_COM
            ? 'https://api.github.com/copilot_internal/v2/token'
            : `https://api.${normalizeDomain(domain)}/copilot_internal/v2/token`,
    };
}
/**
 * Start GitHub device OAuth flow. User must visit verificationUri and enter userCode.
 * @internal Used by runCopilotLoginFlow.
 */
async function startDeviceFlow(domain = GITHUB_COM, fetchFn = fetch) {
    const urls = getCopilotUrls(domain);
    const res = await fetchFn(urls.deviceCodeUrl, {
        method: 'POST',
        headers: {
            Accept: 'application/json',
            'Content-Type': 'application/json',
            'User-Agent': 'GitHubCopilotChat/0.35.0',
        },
        body: JSON.stringify({
            client_id: CLIENT_ID,
            scope: 'read:user',
        }),
        signal: AbortSignal.timeout(30_000),
    });
    if (!res.ok) {
        const body = await res.text();
        throw new Error(`Device code request failed (POST ${urls.deviceCodeUrl}): HTTP ${res.status} ${res.statusText}\nResponse: ${body}`);
    }
    const data = (await res.json());
    return {
        deviceCode: data.device_code,
        userCode: data.user_code,
        verificationUri: data.verification_uri,
        interval: typeof data.interval === 'number' ? data.interval : 5,
    };
}
/**
 * Poll until user completes device flow; returns the GitHub OAuth access token
 * (the `access_token` from the response). This token is stored as the credential
 * and used to obtain short-lived Copilot API tokens via getCopilotAccessToken.
 * @internal Used by runCopilotLoginFlow.
 */
const POLL_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes
async function pollForOAuthToken(deviceCode, intervalSeconds, domain = GITHUB_COM, fetchFn = fetch) {
    const urls = getCopilotUrls(domain);
    const intervalMs = Math.max(intervalSeconds * 1000, 1000);
    const deadline = Date.now() + POLL_TIMEOUT_MS;
    // eslint-disable-next-line no-constant-condition
    while (true) {
        if (Date.now() > deadline) {
            throw new Error('Device code flow timed out. Please run login again and complete the flow within 15 minutes.');
        }
        const res = await fetchFn(urls.accessTokenUrl, {
            method: 'POST',
            headers: {
                Accept: 'application/json',
                'Content-Type': 'application/json',
                'User-Agent': 'GitHubCopilotChat/0.35.0',
            },
            body: JSON.stringify({
                client_id: CLIENT_ID,
                device_code: deviceCode,
                grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
            }),
        });
        if (!res.ok) {
            const text = await res.text();
            throw new Error(`Token exchange failed: ${res.status} ${text}`);
        }
        const data = (await res.json());
        if (data.access_token) {
            return data.access_token;
        }
        if (data.error === 'authorization_pending') {
            await new Promise(r => setTimeout(r, intervalMs));
            continue;
        }
        if (data.error === 'expired_token') {
            throw new Error('Device code expired. Please run login again.');
        }
        if (data.error) {
            throw new Error(`Authorization failed: ${data.error}`);
        }
        await new Promise(r => setTimeout(r, intervalMs));
    }
}
let cachedToken = null;
/**
 * Get a short-lived access token for the Copilot API using the stored GitHub OAuth
 * token (from device flow). Caches the result until close to expiry (5 min buffer).
 * Cache key uses full token so different tokens never share an entry; key is only
 * used in-process for lookup, not stored or logged.
 */
function tokenCacheKey(githubOAuthToken, domain) {
    return `${domain}:${githubOAuthToken}`;
}
/**
 * Clear the in-memory Copilot token cache so the next request fetches a new token.
 * Use after credential change or logout so credentials can be invalidated without waiting for expiry.
 */
export function clearCopilotTokenCache() {
    cachedToken = null;
}
export async function getCopilotAccessToken(githubOAuthToken, domain = GITHUB_COM, fetchFn = fetch) {
    const cacheKey = tokenCacheKey(githubOAuthToken, domain);
    const now = Date.now();
    if (cachedToken?.key === cacheKey && cachedToken.result.expiresAt > now) {
        return cachedToken.result;
    }
    const urls = getCopilotUrls(domain);
    const res = await fetchFn(urls.copilotTokenUrl, {
        headers: {
            Accept: 'application/json',
            Authorization: `Bearer ${githubOAuthToken}`,
            ...COPILOT_HEADERS,
        },
    });
    if (!res.ok) {
        const text = await res.text();
        throw new Error(`Copilot token refresh failed: ${res.status} ${text}`);
    }
    const data = (await res.json());
    if (!data.token || typeof data.token !== 'string') {
        throw new Error('Copilot token endpoint returned no token. Your Copilot subscription may not include chat access.');
    }
    const expiresAt = (data.expires_at ?? 0) * 1000 - 5 * 60 * 1000; // 5 min buffer
    const result = { token: data.token, expiresAt };
    cachedToken = { key: cacheKey, result };
    return result;
}
export function getCopilotBaseUrl(domain) {
    if (domain === GITHUB_COM) {
        return 'https://api.githubcopilot.com';
    }
    return `https://copilot-api.${normalizeDomain(domain)}`;
}
/**
 * Run the full Copilot device-flow login: start flow, show code via callback, poll for token, save credential.
 * Shared by CLI (cli.tsx) and in-chat UI (copilot-login.tsx) so the flow logic lives in one place.
 */
export async function runCopilotLoginFlow(providerName, options) {
    const { onShowCode, onPollingStart, delayBeforePollMs = 0, domain = GITHUB_COM, fetchFn = fetch, } = options;
    const { saveCopilotCredential } = await import('../config/copilot-credentials.js');
    const flow = await startDeviceFlow(domain, fetchFn);
    onShowCode(flow.verificationUri, flow.userCode);
    if (delayBeforePollMs > 0) {
        await new Promise(r => setTimeout(r, delayBeforePollMs));
    }
    onPollingStart?.();
    const oauthToken = await pollForOAuthToken(flow.deviceCode, flow.interval, domain, fetchFn);
    saveCopilotCredential(providerName, oauthToken);
}
//# sourceMappingURL=github-copilot.js.map