"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.trackSignupAttempt = exports.AGENTIC_SIGNUP_ATTEMPTED_EVENT = void 0;
exports.performSignupOrAuth = performSignupOrAuth;
const direct_signup_js_1 = require("./direct-signup.js");
const ampli_settings_js_1 = require("./ampli-settings.js");
const api_js_1 = require("../lib/api.js");
const logger_js_1 = require("../lib/observability/logger.js");
const analytics_js_1 = require("./analytics.js");
const log = (0, logger_js_1.createLogger)('signup-or-auth');
// Retry delays for post-signup provisioning: worst-case total wait ~3.5s.
const PROVISIONING_RETRY_DELAYS_MS = [500, 1000, 2000];
function hasEnvWithApiKey(userInfo) {
    return userInfo.orgs.some((org) => org.projects.some((proj) => (proj.environments ?? []).some((e) => e.app?.apiKey)));
}
/**
 * After a successful direct signup, the backend may not have finished
 * provisioning the default org/workspace/environment. Retry the user
 * fetch a few times so downstream credential resolution finds an env
 * with a project API key, instead of mis-reporting "no_stored_credentials".
 *
 * Retries on both "returned but no env with apiKey" and "threw" — the
 * Data API throws "No user data returned" when orgs is empty, which is
 * the most-likely brand-new-signup race condition. Returns a discriminated
 * union so the caller can drive telemetry (retry count, env-with-apikey
 * flag) without duplicating try/catch. Never throws.
 */
async function fetchUserWithProvisioningRetry(idToken, zone) {
    let userInfo = null;
    let lastError = null;
    let retryCount = 0;
    try {
        userInfo = await (0, api_js_1.fetchAmplitudeUser)(idToken, zone);
    }
    catch (err) {
        lastError = err;
    }
    for (const delayMs of PROVISIONING_RETRY_DELAYS_MS) {
        if (userInfo && hasEnvWithApiKey(userInfo)) {
            return { ok: true, userInfo, retryCount, hasEnvWithApiKey: true };
        }
        log.debug('signup provisioning incomplete; retrying user fetch', {
            delayMs,
            threw: lastError !== null,
        });
        await new Promise((resolve) => setTimeout(resolve, delayMs));
        retryCount += 1;
        try {
            userInfo = await (0, api_js_1.fetchAmplitudeUser)(idToken, zone);
            lastError = null;
        }
        catch (err) {
            // Keep any prior successful userInfo — losing it here would make us
            // fall back to the pending sentinel when we already have real user
            // data from an earlier attempt that just didn't yet have an env.
            lastError = err;
        }
    }
    if (userInfo) {
        const hasEnv = hasEnvWithApiKey(userInfo);
        if (!hasEnv) {
            log.warn('signup user has no env with apiKey after retries', {
                zone,
                orgs: userInfo.orgs.length,
            });
        }
        return {
            ok: true,
            userInfo,
            retryCount,
            hasEnvWithApiKey: hasEnv,
        };
    }
    return { ok: false, retryCount, error: lastError };
}
exports.AGENTIC_SIGNUP_ATTEMPTED_EVENT = 'agentic signup attempted';
const trackSignupAttempt = (properties) => {
    analytics_js_1.analytics.wizardCapture(exports.AGENTIC_SIGNUP_ATTEMPTED_EVENT, properties);
};
exports.trackSignupAttempt = trackSignupAttempt;
/**
 * Attempt direct signup via the headless provisioning endpoint.
 *
 * Returns the new account's tokens (and userInfo, when the internal fetch
 * succeeded) on success; returns `null` when:
 * - email or fullName is missing
 * - the endpoint returns `requires_redirect` / `needs_information` / `error`
 * - the direct-signup network call itself errors
 *
 * On success, also fetches the real user profile and persists the
 * `StoredUser` + tokens to `~/.ampli.json` so downstream
 * `resolveCredentials()` can populate `session.credentials` via the
 * standard path. Falls back to the `id:'pending'` sentinel if the
 * user fetch fails. The user fetch retries briefly on the "no env with
 * API key yet" case to absorb post-signup provisioning lag.
 *
 * This function does NOT fall back to `performAmplitudeAuth()`. Callers
 * that want OAuth fallback (e.g. the TUI path) must call it explicitly
 * when this returns null. Agent/CI modes typically skip OAuth and let
 * `resolveNonInteractiveCredentials` handle cached-token resolution.
 */
async function performSignupOrAuth(input) {
    if (input.email === null || input.fullName === null) {
        log.debug('missing email or fullName; skipping direct signup');
        return null;
    }
    log.debug('attempting direct signup');
    // performDirectSignup is contracted to catch its own network/parse errors
    // and return { kind: 'error' }. The try/catch here is belt-and-suspenders
    // enforcement of the wrapper's documented null-on-any-error behavior —
    // callers rely on the null return to decide fallback strategy.
    let result;
    try {
        result = await (0, direct_signup_js_1.performDirectSignup)({
            email: input.email,
            fullName: input.fullName,
            zone: input.zone,
        });
    }
    catch (err) {
        log.warn('direct signup threw unexpectedly', {
            message: err instanceof Error ? err.message : String(err),
        });
        (0, exports.trackSignupAttempt)({ status: 'signup_error', zone: input.zone });
        return null;
    }
    if (result.kind === 'requires_redirect') {
        log.debug('direct signup did not succeed', { kind: result.kind });
        (0, exports.trackSignupAttempt)({ status: 'requires_redirect', zone: input.zone });
        return null;
    }
    if (result.kind === 'error') {
        log.debug('direct signup did not succeed', { kind: result.kind });
        (0, exports.trackSignupAttempt)({ status: 'signup_error', zone: input.zone });
        return null;
    }
    const tokens = {
        accessToken: result.tokens.accessToken,
        idToken: result.tokens.idToken,
        refreshToken: result.tokens.refreshToken,
        expiresAt: result.tokens.expiresAt,
    };
    // Fetch the real user profile so resolveCredentials() downstream can find
    // a non-pending stored user with a valid zone. Fall back to the pending
    // sentinel on fetch failure — the next wizard run will patch the entry.
    // The fetch retries briefly on the "no env with API key yet" case to
    // absorb post-signup provisioning lag.
    let userInfo = null;
    let user;
    const fetchResult = await fetchUserWithProvisioningRetry(tokens.idToken, input.zone);
    if (fetchResult.ok) {
        userInfo = fetchResult.userInfo;
        user = {
            id: userInfo.id,
            firstName: userInfo.firstName,
            lastName: userInfo.lastName,
            email: userInfo.email,
            zone: input.zone,
        };
    }
    else {
        log.warn('fetchAmplitudeUser failed after direct signup; falling back to pending sentinel', {
            zone: input.zone,
        });
        const parts = input.fullName.trim().split(/\s+/);
        user = {
            id: 'pending',
            firstName: parts[0] ?? '',
            lastName: parts.slice(1).join(' '),
            email: input.email,
            zone: input.zone,
        };
    }
    // Mark ToS as accepted for signup flow (the user went through the ToS
    // screen or --signup was used, which implies acceptance)
    const userWithTos = {
        ...user,
        tosAccepted: true,
        tosAcceptedAt: new Date().toISOString(),
    };
    // Persist BEFORE telemetry: a disk/permission failure must propagate to
    // the outer catch so `wrapper_exception` is the sole event — emitting
    // success or user_fetch_failed first would double-count the attempt.
    (0, ampli_settings_js_1.replaceStoredUser)(userWithTos, tokens);
    if (fetchResult.ok) {
        (0, exports.trackSignupAttempt)({
            status: 'success',
            zone: input.zone,
            'has env with api key': fetchResult.hasEnvWithApiKey,
            'user fetch retry count': fetchResult.retryCount,
        });
    }
    else {
        (0, exports.trackSignupAttempt)({
            status: 'user_fetch_failed',
            zone: input.zone,
            'user fetch retry count': fetchResult.retryCount,
        });
    }
    return {
        idToken: tokens.idToken,
        accessToken: tokens.accessToken,
        refreshToken: tokens.refreshToken,
        zone: result.tokens.zone,
        userInfo,
        dashboardUrl: result.dashboardUrl ?? null,
    };
}
