/**
 * Antigravity OAuth flow (Gemini 3, Claude, GPT-OSS via Google Cloud)
 * Uses different OAuth credentials than google-gemini-cli for access to additional models.
 *
 * NOTE: This module uses Node.js http.createServer for the OAuth callback.
 * It is only intended for CLI use, not browser environments.
 */
import { getGoogleUserEmail, parseRedirectUrl, refreshGoogleOAuthToken, startCallbackServer, } from "./google-oauth-utils.js";
import { generatePKCE } from "./pkce.js";
// Antigravity OAuth credentials (different from Gemini CLI)
const decode = (s) => atob(s);
const CLIENT_ID = decode("MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==");
const CLIENT_SECRET = decode("R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY=");
const REDIRECT_URI = "http://localhost:51121/oauth-callback";
// Antigravity requires additional scopes
const SCOPES = [
    "https://www.googleapis.com/auth/cloud-platform",
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/userinfo.profile",
    "https://www.googleapis.com/auth/cclog",
    "https://www.googleapis.com/auth/experimentsandconfigs",
];
const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
const TOKEN_URL = "https://oauth2.googleapis.com/token";
// Callback server configuration
const CALLBACK_PORT = 51121;
const CALLBACK_PATH = "/oauth-callback";
// Fallback project ID when discovery fails
const DEFAULT_PROJECT_ID = "rising-fact-p41fc";
/**
 * Discover or provision a project for the user
 */
async function discoverProject(accessToken, onProgress) {
    const headers = {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
        "User-Agent": "google-api-nodejs-client/9.15.1",
        "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
        "Client-Metadata": JSON.stringify({
            ideType: "IDE_UNSPECIFIED",
            platform: "PLATFORM_UNSPECIFIED",
            pluginType: "GEMINI",
        }),
    };
    // Try endpoints in order: prod first, then sandbox
    const endpoints = ["https://cloudcode-pa.googleapis.com", "https://daily-cloudcode-pa.sandbox.googleapis.com"];
    onProgress?.("Checking for existing project...");
    for (const endpoint of endpoints) {
        try {
            const loadResponse = await fetch(`${endpoint}/v1internal:loadCodeAssist`, {
                method: "POST",
                headers,
                body: JSON.stringify({
                    metadata: {
                        ideType: "IDE_UNSPECIFIED",
                        platform: "PLATFORM_UNSPECIFIED",
                        pluginType: "GEMINI",
                    },
                }),
                signal: AbortSignal.timeout(30_000),
            });
            if (loadResponse.ok) {
                const data = (await loadResponse.json());
                // Handle both string and object formats
                if (typeof data.cloudaicompanionProject === "string" && data.cloudaicompanionProject) {
                    return data.cloudaicompanionProject;
                }
                if (data.cloudaicompanionProject &&
                    typeof data.cloudaicompanionProject === "object" &&
                    data.cloudaicompanionProject.id) {
                    return data.cloudaicompanionProject.id;
                }
            }
        }
        catch {
            // Try next endpoint
        }
    }
    // Use fallback project ID
    onProgress?.("Using default project...");
    return DEFAULT_PROJECT_ID;
}
/**
 * Refresh Antigravity token
 */
export async function refreshAntigravityToken(refreshToken, projectId) {
    return refreshGoogleOAuthToken(refreshToken, CLIENT_ID, CLIENT_SECRET, "Antigravity", { projectId });
}
/**
 * Login with Antigravity OAuth
 *
 * @param onAuth - Callback with URL and optional instructions
 * @param onProgress - Optional progress callback
 * @param onManualCodeInput - Optional promise that resolves with user-pasted redirect URL.
 *                            Races with browser callback - whichever completes first wins.
 */
export async function loginAntigravity(onAuth, onProgress, onManualCodeInput) {
    const { verifier, challenge } = await generatePKCE();
    // Start local server for callback
    onProgress?.("Starting local server for OAuth callback...");
    const server = await startCallbackServer(CALLBACK_PORT, CALLBACK_PATH, "Antigravity");
    let code;
    try {
        // Build authorization URL
        const authParams = new URLSearchParams({
            client_id: CLIENT_ID,
            response_type: "code",
            redirect_uri: REDIRECT_URI,
            scope: SCOPES.join(" "),
            code_challenge: challenge,
            code_challenge_method: "S256",
            state: verifier,
            access_type: "offline",
            prompt: "consent",
        });
        const authUrl = `${AUTH_URL}?${authParams.toString()}`;
        // Notify caller with URL to open
        onAuth({
            url: authUrl,
            instructions: "Complete the sign-in in your browser.",
        });
        // Wait for the callback, racing with manual input if provided
        onProgress?.("Waiting for OAuth callback...");
        if (onManualCodeInput) {
            // Race between browser callback and manual input
            let manualInput;
            let manualError;
            const manualPromise = onManualCodeInput()
                .then((input) => {
                manualInput = input;
                server.cancelWait();
            })
                .catch((err) => {
                manualError = err instanceof Error ? err : new Error(String(err));
                server.cancelWait();
            });
            const result = await server.waitForCode();
            // If manual input was cancelled, throw that error
            if (manualError) {
                throw manualError;
            }
            if (result?.code) {
                // Browser callback won - verify state
                if (result.state !== verifier) {
                    throw new Error("OAuth state mismatch - possible CSRF attack");
                }
                code = result.code;
            }
            else if (manualInput) {
                // Manual input won
                const parsed = parseRedirectUrl(manualInput);
                if (parsed.state && parsed.state !== verifier) {
                    throw new Error("OAuth state mismatch - possible CSRF attack");
                }
                code = parsed.code;
            }
            // If still no code, wait for manual promise and try that
            if (!code) {
                await manualPromise;
                if (manualError) {
                    throw manualError;
                }
                if (manualInput) {
                    const parsed = parseRedirectUrl(manualInput);
                    if (parsed.state && parsed.state !== verifier) {
                        throw new Error("OAuth state mismatch - possible CSRF attack");
                    }
                    code = parsed.code;
                }
            }
        }
        else {
            // Original flow: just wait for callback
            const result = await server.waitForCode();
            if (result?.code) {
                if (result.state !== verifier) {
                    throw new Error("OAuth state mismatch - possible CSRF attack");
                }
                code = result.code;
            }
        }
        if (!code) {
            throw new Error("No authorization code received");
        }
        // Exchange code for tokens
        onProgress?.("Exchanging authorization code for tokens...");
        const tokenResponse = await fetch(TOKEN_URL, {
            method: "POST",
            headers: {
                "Content-Type": "application/x-www-form-urlencoded",
            },
            body: new URLSearchParams({
                client_id: CLIENT_ID,
                client_secret: CLIENT_SECRET,
                code,
                grant_type: "authorization_code",
                redirect_uri: REDIRECT_URI,
                code_verifier: verifier,
            }),
            signal: AbortSignal.timeout(30_000),
        });
        if (!tokenResponse.ok) {
            const error = await tokenResponse.text();
            throw new Error(`Token exchange failed: ${error}`);
        }
        const tokenData = (await tokenResponse.json());
        if (!tokenData.refresh_token) {
            throw new Error("No refresh token received. Please try again.");
        }
        // Get user email
        onProgress?.("Getting user info...");
        const email = await getGoogleUserEmail(tokenData.access_token);
        // Discover project
        const projectId = await discoverProject(tokenData.access_token, onProgress);
        // Calculate expiry time (current time + expires_in seconds - 5 min buffer)
        const expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;
        const credentials = {
            refresh: tokenData.refresh_token,
            access: tokenData.access_token,
            expires: expiresAt,
            projectId,
            email,
        };
        return credentials;
    }
    finally {
        server.server.close();
    }
}
export const antigravityOAuthProvider = {
    id: "google-antigravity",
    name: "Antigravity (Gemini 3, Claude, GPT-OSS)",
    usesCallbackServer: true,
    async login(callbacks) {
        return loginAntigravity(callbacks.onAuth, callbacks.onProgress, callbacks.onManualCodeInput);
    },
    async refreshToken(credentials) {
        const creds = credentials;
        if (!creds.projectId) {
            throw new Error("Antigravity credentials missing projectId");
        }
        return refreshAntigravityToken(creds.refresh, creds.projectId);
    },
    getApiKey(credentials) {
        const creds = credentials;
        return JSON.stringify({ token: creds.access, projectId: creds.projectId });
    },
};
//# sourceMappingURL=google-antigravity.js.map