"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runWizard = runWizard;
exports.detectAllFrameworks = detectAllFrameworks;
const wizard_session_1 = require("./lib/wizard-session");
const constants_1 = require("./lib/constants");
const environment_1 = require("./utils/environment");
const install_dir_1 = require("./utils/install-dir");
const ui_1 = require("./ui");
const promises_1 = __importDefault(require("node:fs/promises"));
const registry_1 = require("./lib/registry");
const analytics_1 = require("./utils/analytics");
const agent_runner_1 = require("./lib/agent-runner");
const events_1 = require("events");
const chalk_1 = __importDefault(require("chalk"));
const debug_1 = require("./utils/debug");
const wizard_abort_1 = require("./utils/wizard-abort");
const zone_probe_1 = require("./utils/zone-probe");
const version_check_1 = require("./lib/version-check");
const feature_flags_1 = require("./lib/feature-flags");
const feature_discovery_1 = require("./lib/feature-discovery");
events_1.EventEmitter.defaultMaxListeners = 50;
async function runWizard(argv, session, getAdditionalFeatureQueue, featureProgress) {
    const finalArgs = {
        ...argv,
        ...(0, environment_1.readEnvironment)(),
    };
    // installDir precedence (highest wins):
    //   1. TUI directory picker — already mutated session.installDir via
    //      store.changeInstallDir() before runWizard is called.
    //   2. --install-dir / AMPLITUDE_WIZARD_INSTALL_DIR — flowed into the
    //      session at buildSession() time.
    //   3. process.cwd() — buildSession's default.
    //
    // Trust the session as the single source of truth. Only the fresh-
    // session path needs to resolve from CLI args — and buildSession does
    // that via its zod schema (resolveInstallDir handles `~` expansion).
    // The previous code unconditionally re-assigned session.installDir
    // from finalArgs.installDir, which silently reverted any TUI
    // directory change back to the original CLI value.
    if (!session) {
        session = (0, wizard_session_1.buildSession)({
            debug: finalArgs.debug,
            forceInstall: finalArgs.forceInstall,
            installDir: (0, install_dir_1.resolveInstallDir)(finalArgs.installDir),
            ci: finalArgs.ci,
            authOnboarding: finalArgs.authOnboarding,
            signup: finalArgs.signup,
            signupEmail: finalArgs.signupEmail,
            signupFullName: finalArgs.signupFullName,
            localMcp: finalArgs.localMcp,
            apiKey: finalArgs.apiKey,
            menu: finalArgs.menu,
            integration: finalArgs.integration,
            benchmark: finalArgs.benchmark,
            appId: finalArgs.appId,
            region: finalArgs.region,
        });
    }
    (0, ui_1.getUI)().intro(`Welcome to the Amplitude setup wizard`);
    if (session.ci) {
        (0, ui_1.getUI)().log.info(chalk_1.default.dim('Running in CI mode'));
    }
    const integration = session.integration ?? (await detectAndResolveIntegration(session));
    session.integration = integration;
    analytics_1.analytics.setSessionProperty('integration', integration);
    analytics_1.analytics.wizardCapture('session started', {
        integration,
        ci: session.ci ?? false,
        'account creation flow': (0, wizard_session_1.isCreateAccountOnboarding)(session),
    });
    // Non-interactive modes (CI / agent) auto-enable every discovered
    // opt-in feature here so the agent run gets the same SR + G&S + LLM
    // coverage as an interactive TUI run (bin.ts handles the TUI side via
    // store.autoEnableInlineAddons). Both paths converge on the same set
    // of inline addons — there is no picker in either flow.
    if ((session.ci || session.agent) && !session.optInFeaturesComplete) {
        await (0, feature_flags_1.initFeatureFlags)().catch(() => {
            // Flag init failure is non-fatal — LLM gate just stays off
        });
        (0, feature_discovery_1.autoEnableOptInFeatures)(session, session.agent ? 'auto-agent' : 'auto-ci');
    }
    const config = registry_1.FRAMEWORK_REGISTRY[integration];
    session.frameworkConfig = config;
    // Run gatherContext if the framework has it and it hasn't already run
    // (bin.ts runs it early so IntroScreen can show the friendly label)
    const contextAlreadyGathered = Object.keys(session.frameworkContext).length > 0;
    if (config.metadata.gatherContext && !contextAlreadyGathered) {
        try {
            const context = await config.metadata.gatherContext({
                installDir: session.installDir,
                debug: session.debug,
                forceInstall: session.forceInstall,
                default: false,
                authOnboardingPath: session.authOnboardingPath,
                localMcp: session.localMcp,
                ci: session.ci,
                menu: session.menu,
                benchmark: session.benchmark,
            });
            for (const [key, value] of Object.entries(context)) {
                if (!(key in session.frameworkContext)) {
                    session.frameworkContext[key] = value;
                }
            }
        }
        catch {
            // Detection failed — SetupScreen or agent will handle it
        }
    }
    let retry = true;
    while (retry) {
        try {
            await (0, agent_runner_1.runAgentWizard)(config, session, getAdditionalFeatureQueue, featureProgress);
            retry = false;
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            const errorStack = error instanceof Error && error.stack ? error.stack : undefined;
            (0, debug_1.logToFile)(`[Wizard run.ts] ERROR MESSAGE: ${errorMessage} `);
            if (errorStack) {
                (0, debug_1.logToFile)(`[Wizard run.ts] ERROR STACK: ${errorStack}`);
            }
            const debugInfo = session.debug && errorStack ? `\n\n${errorStack}` : '';
            // NoOrgsError is a terminal condition — the user's account literally
            // has no organizations on this zone. Re-running the same wizard with
            // the same auth and zone will produce the same result, so offering
            // a "press R to retry" is misleading. Route straight through abort()
            // so registered cleanup, analytics, and Sentry capture all run, and
            // the process exits with the standard contract instead of looping
            // back through setRunError → setRunError → setRunError forever.
            if (error instanceof zone_probe_1.NoOrgsError) {
                await (0, wizard_abort_1.wizardAbort)({
                    message: errorMessage,
                    error: error,
                });
                return;
            }
            retry = await (0, ui_1.getUI)().setRunError(error);
            if (!retry) {
                await (0, wizard_abort_1.wizardAbort)({
                    message: `Something went wrong: ${errorMessage}\n\nYou can read the documentation at ${config.metadata.docsUrl} to set up Amplitude manually.${debugInfo}`,
                    error: error,
                });
            }
        }
    }
}
/**
 * Race a promise against a timeout, returning the resolved value on the
 * win path or the literal `'timeout'` sentinel if the deadline fires first.
 *
 * Unlike a bare `Promise.race([work(), setTimeout(resolve('timeout'), ms)])`,
 * this clears the timer when `work()` wins so the timer callback never fires
 * after detection completes. Across ~18 frameworks racing in parallel that
 * leak adds up to ~18 stranded timers per detection cycle, each holding the
 * resolved closure (and the framework's installDir reference) alive until
 * `timeoutMs` elapses.
 *
 * Rejections from `work()` propagate to the caller — only timeouts surface
 * as the sentinel value, matching the previous behavior.
 */
async function raceWithTimeoutSentinel(work, timeoutMs) {
    let timer;
    const timeoutPromise = new Promise((resolve) => {
        timer = setTimeout(() => resolve('timeout'), timeoutMs);
    });
    try {
        return await Promise.race([work, timeoutPromise]);
    }
    finally {
        if (timer !== undefined)
            clearTimeout(timer);
    }
}
/**
 * Run all framework detectors in parallel and return the full results array.
 * The winner is the first detected framework in Integration enum order
 * (preserving the existing priority behavior).
 */
async function detectAllFrameworks(installDir, timeoutMs = constants_1.DETECTION_TIMEOUT_MS) {
    // Pre-validate installDir — fail fast instead of running 18 detectors
    try {
        await promises_1.default.access(installDir, promises_1.default.constants.R_OK);
    }
    catch {
        (0, debug_1.logToFile)(`[detection] installDir is not readable: ${installDir}`);
        return Object.values(constants_1.Integration).map((integration) => ({
            integration,
            detected: false,
            durationMs: 0,
            timedOut: false,
            error: 'installDir not readable',
        }));
    }
    try {
        const st = await promises_1.default.stat(installDir);
        if (!st.isDirectory()) {
            (0, debug_1.logToFile)(`[detection] installDir is not a directory: ${installDir}`);
            return Object.values(constants_1.Integration).map((integration) => ({
                integration,
                detected: false,
                durationMs: 0,
                timedOut: false,
                error: 'installDir not a directory',
            }));
        }
    }
    catch {
        (0, debug_1.logToFile)(`[detection] installDir stat failed: ${installDir}`);
        return Object.values(constants_1.Integration).map((integration) => ({
            integration,
            detected: false,
            durationMs: 0,
            timedOut: false,
            error: 'installDir stat failed',
        }));
    }
    const integrations = Object.values(constants_1.Integration);
    const promises = integrations.map(async (integration) => {
        const config = registry_1.FRAMEWORK_REGISTRY[integration];
        const start = performance.now();
        // Both detect() and version check run inside a single Promise.race
        // so a slow getInstalledVersion can't hang the entire detection.
        const work = async () => {
            const detected = await config.detection.detect({ installDir });
            const result = {
                integration,
                detected: Boolean(detected),
                durationMs: Math.round(performance.now() - start),
                timedOut: false,
            };
            // Capture version for diagnostics (agent-runner handles version warnings)
            if (result.detected &&
                (config.detection.getInstalledVersion ||
                    config.detection.getVersionCheckInfo)) {
                try {
                    const versionCheckInfo = await (0, version_check_1.getVersionCheckInfo)(config.detection, {
                        installDir,
                        debug: false,
                        forceInstall: false,
                        default: false,
                        authOnboardingPath: wizard_session_1.AuthOnboardingPath.SignIn,
                        localMcp: false,
                        ci: false,
                        menu: false,
                        benchmark: false,
                    });
                    if (versionCheckInfo.version) {
                        result.version = versionCheckInfo.version;
                    }
                }
                catch (err) {
                    (0, debug_1.logToFile)(`[detection] ${integration} version check failed: ${err instanceof Error ? err.message : err}`);
                }
            }
            result.durationMs = Math.round(performance.now() - start);
            return result;
        };
        try {
            const result = await raceWithTimeoutSentinel(work(), timeoutMs);
            if (result === 'timeout') {
                const durationMs = Math.round(performance.now() - start);
                (0, debug_1.logToFile)(`[detection] ${integration} timed out after ${durationMs}ms`);
                return {
                    integration,
                    detected: false,
                    durationMs,
                    timedOut: true,
                };
            }
            return result;
        }
        catch (err) {
            const durationMs = Math.round(performance.now() - start);
            const errorMsg = err instanceof Error ? err.message : String(err);
            (0, debug_1.logToFile)(`[detection] ${integration} failed after ${durationMs}ms: ${errorMsg}`);
            return {
                integration,
                detected: false,
                durationMs,
                timedOut: false,
                error: errorMsg,
            };
        }
    });
    return Promise.all(promises);
}
async function detectAndResolveIntegration(session) {
    if (!session.menu) {
        const results = await detectAllFrameworks(session.installDir);
        session.detectionResults = results;
        const detected = results.filter((r) => r.detected);
        const winner = detected[0];
        // Analytics: capture detection metrics
        analytics_1.analytics.wizardCapture('framework detection complete', {
            winner: winner?.integration ?? 'none',
            'match count': detected.length,
            'duration ms': Math.max(...results.map((r) => r.durationMs)),
            'error count': results.filter((r) => r.error).length,
            'timed out count': results.filter((r) => r.timedOut).length,
        });
        if (winner) {
            (0, ui_1.getUI)().setDetectedFramework(registry_1.FRAMEWORK_REGISTRY[winner.integration].metadata.name);
            return winner.integration;
        }
        // Framework not detected — fall back to generic Amplitude quickstart.
        (0, ui_1.getUI)().log.warn("Couldn't detect your framework. Falling back to the Amplitude quickstart guide.");
        return constants_1.Integration.generic;
    }
    // Fallback: in TUI mode the IntroScreen would handle this,
    // but for CI mode or when detection fails, abort with guidance.
    return (0, wizard_abort_1.wizardAbort)({
        message: 'Could not auto-detect your framework. Please specify --integration on the command line.',
    });
}
