// SPEC-597-EXEMPT: canonical mapping (npm-package↔framework, keyword catalogue)
// — must enumerate framework names by design; no detection logic to migrate.
// src/engine/oauth/config-guard.ts — OAuth integration config + flow builder (SPEC-440)
import { createOAuthState } from './state-manager.js';
import { generateCodeChallenge } from './pkce.js';
import { findFreePort, startCallbackServer } from './local-server.js';
const REDIRECT_URI = (port) => `http://localhost:${port}/callback`;
function getIntegrationDef(integration) {
    switch (integration) {
        case 'figma':
            return {
                flowType: 'oauth2-pkce',
                clientId: process.env.PLANU_FIGMA_CLIENT_ID ?? 'planu-figma-app',
                authUrlTemplate: 'https://www.figma.com/oauth',
                tokenUrl: 'https://api.figma.com/v1/oauth/token',
                scope: 'file_read',
            };
        case 'github':
            return {
                flowType: 'oauth2-pkce',
                clientId: process.env.PLANU_GITHUB_CLIENT_ID ?? 'planu-github-app',
                authUrlTemplate: 'https://github.com/login/oauth/authorize',
                tokenUrl: 'https://github.com/login/oauth/access_token',
                scope: 'repo read:user',
            };
        case 'sentry':
            return {
                flowType: 'guided-token',
                guidedMessage: "Ve a https://sentry.io/settings/account/api/auth-tokens/new/ → crea un token con scope 'project:read event:read' → pega el token en configure_sentry",
            };
        case 'supabase':
            return {
                flowType: 'guided-token',
                guidedMessage: "Ve a https://supabase.com/dashboard/project/_/settings/api → copia la 'service_role' key → pega el valor en configure_supabase",
            };
    }
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Build the OAuth flow result for the given integration.
 * For oauth2-pkce integrations, starts a local callback server and returns authUrl.
 * For guided-token integrations, returns the guidance message.
 */
export async function buildOAuthFlow(integration, projectPath) {
    const def = getIntegrationDef(integration);
    if (def.flowType === 'guided-token') {
        return {
            integration,
            flowType: 'guided-token',
            guidedMessage: def.guidedMessage,
        };
    }
    // oauth2-pkce flow
    const port = await findFreePort();
    const oauthState = createOAuthState(integration, projectPath, port);
    const challenge = generateCodeChallenge(oauthState.codeVerifier);
    const url = new URL(def.authUrlTemplate ?? '');
    url.searchParams.set('client_id', def.clientId ?? '');
    url.searchParams.set('redirect_uri', REDIRECT_URI(port));
    url.searchParams.set('scope', def.scope ?? '');
    url.searchParams.set('state', oauthState.state);
    url.searchParams.set('response_type', 'code');
    url.searchParams.set('code_challenge', challenge);
    url.searchParams.set('code_challenge_method', 'S256');
    // Start the local server (non-blocking — caller decides when to wait)
    await startCallbackServer(port);
    return {
        integration,
        flowType: 'oauth2-pkce',
        authUrl: url.toString(),
        port,
        state: oauthState.state,
    };
}
/**
 * Return user-friendly text describing the next step for an OAuth flow.
 */
export function buildOAuthPromptText(result) {
    const integrationName = result.integration;
    const link = result.authUrl ?? result.guidedMessage ?? '';
    if (result.flowType === 'guided-token') {
        return `Para continuar, ${integrationName} necesita autorización.\n\n${link}\n\nEl link expira en 5 minutos.`;
    }
    return `Para continuar, ${integrationName} necesita autorización.\n\n👉 Abre este link (~10 segundos):\n   ${link}\n\nEl link expira en 5 minutos.`;
}
/**
 * Exchange an OAuth authorization code for a token.
 * Validates the CSRF state before sending the exchange request.
 */
export async function exchangeOAuthCode(stateToken, code) {
    // Import here to avoid circular dependency issues at module load time
    const { validateAndConsumeState } = await import('./state-manager.js');
    const oauthState = validateAndConsumeState(stateToken);
    if (!oauthState) {
        throw new Error('OAuth exchange failed: invalid or expired state token (CSRF check)');
    }
    const def = getIntegrationDef(oauthState.integration);
    if (def.flowType === 'guided-token' || !def.tokenUrl) {
        throw new Error(`OAuth exchange not supported for guided-token integration: ${oauthState.integration}`);
    }
    const clientId = def.clientId ?? '';
    const clientSecret = process.env[`PLANU_${oauthState.integration.toUpperCase()}_CLIENT_SECRET`] ?? '';
    const redirectUri = REDIRECT_URI(oauthState.port);
    const body = new URLSearchParams({
        client_id: clientId,
        client_secret: clientSecret,
        code,
        redirect_uri: redirectUri,
        code_verifier: oauthState.codeVerifier,
    });
    const response = await fetch(def.tokenUrl, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            Accept: 'application/json',
        },
        body: body.toString(),
    });
    if (!response.ok) {
        const text = await response.text().catch(() => response.statusText);
        throw new Error(`OAuth token exchange failed for ${oauthState.integration}: HTTP ${response.status} — ${text}`);
    }
    const data = (await response.json());
    const accessToken = typeof data.access_token === 'string' ? data.access_token : '';
    if (!accessToken) {
        throw new Error(`OAuth token exchange failed for ${oauthState.integration}: no access_token in response`);
    }
    return {
        integration: oauthState.integration,
        accessToken,
        refreshToken: typeof data.refresh_token === 'string' ? data.refresh_token : undefined,
        expiresAt: typeof data.expires_in === 'number'
            ? new Date(Date.now() + data.expires_in * 1000).toISOString()
            : undefined,
        scope: typeof data.scope === 'string' ? data.scope : undefined,
        storedAt: new Date().toISOString(),
    };
}
//# sourceMappingURL=config-guard.js.map