/**
 * Shortcut OAuth Provider for the agent.
 *
 * Implements the owned AI OAuthProviderInterface using Shortcut's Device Code Flow (RFC 8628).
 * Users run `/login`, get a code, open browser to authorize, and the agent polls for tokens.
 *
 * Token refresh uses Shortcut's `POST /auth/refresh` endpoint with body mode (token_delivery: "body").
 */
import { SHORTCUT_AUTH_URL as AUTH_SERVICE_URL } from '../../constants.js';
import { SHORTCUT_AUTH_ENDPOINTS } from '../../endpoints.js';
const PLATFORM = 'agent-cli';
// Refresh tokens 2 minutes before expiry to avoid mid-request failures.
// The web client uses 5 min, but the agent only refreshes reactively (on getApiKey),
// so we use a shorter buffer — just enough to cover an in-flight streaming request.
const REFRESH_BUFFER_MS = 2 * 60 * 1000;
// PKCE utilities (inlined to avoid cross-package import issues)
const BASE64URL_CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
function generateCodeVerifier() {
    const array = new Uint8Array(64);
    crypto.getRandomValues(array);
    return Array.from(array, (byte) => BASE64URL_CHARSET[byte % 64]).join('');
}
async function generateCodeChallenge(codeVerifier) {
    const data = new TextEncoder().encode(codeVerifier);
    const hashBuffer = await crypto.subtle.digest('SHA-256', data);
    const hashArray = new Uint8Array(hashBuffer);
    const base64 = btoa(String.fromCharCode(...hashArray));
    return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
/**
 * Shortcut OAuth provider config (without `id` — owned AI adds that from the provider name).
 */
export const shortcutOAuth = {
    name: 'Shortcut',
    usesCallbackServer: false,
    async login(callbacks) {
        // 1. Initiate device code flow
        const codeVerifier = generateCodeVerifier();
        const codeChallenge = await generateCodeChallenge(codeVerifier);
        const codeResponse = await fetch(`${AUTH_SERVICE_URL}${SHORTCUT_AUTH_ENDPOINTS.deviceCode}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                code_challenge: codeChallenge,
                platform: PLATFORM
            }),
            signal: callbacks.signal
        });
        if (!codeResponse.ok) {
            const data = await codeResponse.json().catch(() => ({}));
            throw new Error(data.error ?? `Failed to initiate device code: ${codeResponse.status}`);
        }
        const deviceCode = await codeResponse.json();
        // 2. Tell the user to open the browser
        callbacks.onAuth({
            url: deviceCode.verification_uri_complete
        });
        // 3. Poll for authorization
        let interval = deviceCode.interval ?? 5;
        const deadline = Date.now() + deviceCode.expires_in * 1000;
        while (Date.now() < deadline) {
            if (callbacks.signal?.aborted) {
                throw new Error('Login cancelled');
            }
            await new Promise((resolve, reject) => {
                const timeoutId = setTimeout(resolve, interval * 1000);
                if (callbacks.signal) {
                    callbacks.signal.addEventListener('abort', () => {
                        clearTimeout(timeoutId);
                        reject(new Error('Login cancelled'));
                    }, { once: true });
                }
            });
            let response;
            try {
                response = await fetch(`${AUTH_SERVICE_URL}${SHORTCUT_AUTH_ENDPOINTS.deviceToken}`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        device_code: deviceCode.device_code,
                        code_verifier: codeVerifier,
                        grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
                    }),
                    signal: callbacks.signal
                });
            }
            catch {
                // Network error — keep polling
                continue;
            }
            if (response.ok) {
                const tokens = await response.json();
                return {
                    access: tokens.accessToken,
                    refresh: tokens.refreshToken,
                    expires: tokens.accessExpiresAt - REFRESH_BUFFER_MS
                };
            }
            const errorData = await response.json().catch(() => ({ error: 'unknown' }));
            const errorType = errorData.error;
            if (errorType === 'authorization_pending') {
                continue;
            }
            else if (errorType === 'slow_down') {
                interval = errorData.interval ?? interval + 5;
                continue;
            }
            else if (errorType === 'expired_token') {
                throw new Error('Device code expired. Please try /login again.');
            }
            else if (errorType === 'access_denied') {
                throw new Error('Authorization denied.');
            }
            else {
                throw new Error(errorData.error_description ?? errorData.error ?? 'Login failed');
            }
        }
        throw new Error('Device code expired. Please try /login again.');
    },
    async refreshToken(credentials) {
        const response = await fetch(`${AUTH_SERVICE_URL}${SHORTCUT_AUTH_ENDPOINTS.refresh}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                refreshToken: credentials.refresh,
                token_delivery: 'body',
                platform: PLATFORM
            })
        });
        if (!response.ok) {
            const data = await response.json().catch(() => ({}));
            throw new Error(data.error ?? `Token refresh failed: ${response.status}`);
        }
        const tokens = await response.json();
        return {
            access: tokens.accessToken,
            refresh: tokens.refreshToken,
            expires: tokens.accessExpiresAt - REFRESH_BUFFER_MS
        };
    },
    getApiKey(credentials) {
        return credentials.access;
    }
};
//# sourceMappingURL=shortcut-oauth.js.map