"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.analytics = exports.WIZARD_FEEDBACK_EVENT_TYPE = exports.Analytics = void 0;
exports.resolveTelemetryApiKey = resolveTelemetryApiKey;
exports.getAmplitudeNodeServerUrl = getAmplitudeNodeServerUrl;
exports.sessionProperties = sessionProperties;
exports.sessionPropertiesCompact = sessionPropertiesCompact;
exports.captureWizardError = captureWizardError;
const analytics_node_1 = require("@amplitude/analytics-node");
const uuid_1 = require("uuid");
const debug_1 = require("./debug");
const constants_1 = require("../lib/constants");
const observability_1 = require("../lib/observability");
const install_id_1 = require("./install-id");
const feature_flags_1 = require("../lib/feature-flags");
// Telemetry keys mirror Lightning's ampli config in amplitude/javascript
// (packages/instrumentation/src/lightning/{agents,wormhole}/src/ampli/index.ts).
// Both keys point at the main `amplitude/Amplitude` project.
const DEV_TELEMETRY_API_KEY = 'ce58b28cace35f7df0eb241b0cd72044';
const PROD_TELEMETRY_API_KEY = 'e5a2c9bdffe949f7da77e6b481e118fa';
/**
 * Telemetry project API key. Empty or whitespace-only env value means “no key”
 * (use default only when the variable is unset).
 */
function resolveTelemetryApiKey() {
    const fromEnv = process.env.AMPLITUDE_API_KEY;
    const defaultKey = constants_1.IS_DEV ? DEV_TELEMETRY_API_KEY : PROD_TELEMETRY_API_KEY;
    const raw = fromEnv !== undefined ? fromEnv : defaultKey;
    return raw.trim();
}
/** HTTP API URL for `@amplitude/analytics-node` (same shape as manual HTTP ingest). */
function getAmplitudeNodeServerUrl() {
    const base = process.env.AMPLITUDE_SERVER_URL ?? 'https://api2.amplitude.com';
    return `${base.replace(/\/$/, '')}/2/httpapi`;
}
/**
 * Extract a standard property bag from the current session.
 * Used by store-level analytics and available for ad-hoc captures.
 */
function sessionProperties(session) {
    return {
        integration: session.integration,
        'detected framework': session.detectedFrameworkLabel,
        typescript: session.typescript,
        'app id': session.credentials?.appId,
        'discovered features': session.discoveredFeatures,
        'additional features': session.additionalFeatureQueue,
        'run phase': session.runPhase,
    };
}
/**
 * Smaller session bag for high-volume wizard events (taxonomy: keep event
 * properties chart-useful and within a small count).
 */
function sessionPropertiesCompact(session) {
    return {
        integration: session.integration,
        'detected framework': session.detectedFrameworkLabel,
        'run phase': session.runPhase,
        'app id': session.credentials?.appId,
    };
}
class Analytics {
    /**
     * Session-scoped event properties — spread into every `capture()` call.
     *
     * Use for values that are genuinely session-wide and relevant to every event:
     *   mode, wizard_version, platform, node_version, integration, package-manager
     *
     * Do NOT use for step-specific state (vercel-detected, prettier-installed, etc.)
     * — pass those as event properties directly on the relevant event.
     */
    sessionProperties = {};
    distinctId;
    anonymousId;
    appName = 'wizard';
    activeFlags = null;
    client;
    initPromise = null;
    startedAt = Date.now();
    constructor() {
        this.sessionProperties = { $app_name: this.appName };
        // Persistent install ID stitches pre-auth runs across invocations;
        // fall back to a per-process UUID if disk access fails.
        this.anonymousId = (0, install_id_1.getOrCreateInstallId)() ?? (0, uuid_1.v4)();
        this.distinctId = undefined;
        this.client = (0, analytics_node_1.createInstance)();
    }
    /** Expose the anonymous device ID for cross-system correlation (logs, Sentry). */
    getAnonymousId() {
        return this.anonymousId;
    }
    setDistinctId(distinctId) {
        this.distinctId = distinctId;
        (0, observability_1.setSentryUser)(distinctId);
        // Re-evaluate flags now that we know the user_id. Fire-and-forget —
        // a no-op if the Experiment client hasn't been initialized yet, and
        // flag-refresh failures are non-fatal.
        void this.refreshFlags().catch(() => { });
    }
    /**
     * Send an identify call to associate user properties with the current user.
     * Call after setDistinctId() has been called with a valid email.
     */
    identifyUser(properties) {
        const apiKey = resolveTelemetryApiKey();
        if (!apiKey || !this.distinctId) {
            (0, debug_1.debug)('identifyUser skipped (no API key or distinctId):', properties);
            return;
        }
        this.ensureInitStarted();
        const identifyObj = new analytics_node_1.Identify();
        if (properties.email) {
            identifyObj.setOnce('email', properties.email);
        }
        if (properties.org_id)
            identifyObj.set('org id', properties.org_id);
        if (properties.org_name)
            identifyObj.set('org name', properties.org_name);
        if (properties.project_id)
            identifyObj.set('project id', properties.project_id);
        if (properties.project_name)
            identifyObj.set('project name', properties.project_name);
        if (properties.app_id != null)
            identifyObj.set('app id', String(properties.app_id));
        if (properties.env_name)
            identifyObj.set('env name', properties.env_name);
        if (properties.region)
            identifyObj.set('region', properties.region);
        if (properties.integration)
            identifyObj.set('integration', properties.integration);
        const eventOptions = {
            device_id: this.anonymousId,
            user_id: this.distinctId,
        };
        this.client.identify(identifyObj, eventOptions);
        // Associate org-level group (matches main product convention)
        if (properties.org_id) {
            this.client.setGroup('org id', properties.org_id, eventOptions);
            const groupProps = new analytics_node_1.Identify();
            if (properties.org_name)
                groupProps.set('org name', properties.org_name);
            groupProps.set('last used wizard', new Date().toISOString());
            this.client.groupIdentify('org id', properties.org_id, groupProps, eventOptions);
        }
        (0, debug_1.debug)('identifyUser:', properties);
    }
    /**
     * Set a session-scoped property that appears on every subsequent event.
     *
     * Only use for values relevant to ALL events in the session:
     *   mode, wizard_version, platform, node_version, integration, package-manager
     *
     * For step-specific data (vercel status, prettier, etc.), pass as event
     * properties directly on the relevant `wizardCapture()` call instead.
     */
    setSessionProperty(key, value) {
        this.sessionProperties[key] = value;
    }
    /**
     * @deprecated Use `setSessionProperty()` for session-wide data, or pass
     * step-specific data as event properties on `wizardCapture()`.
     */
    setTag(key, value) {
        this.sessionProperties[key] = value;
    }
    captureException(error, properties = {}) {
        this.capture('$error', {
            ...properties,
            'error message': error.message,
            'error name': error.name,
        });
    }
    capture(eventName, properties) {
        const apiKey = resolveTelemetryApiKey();
        if (!apiKey) {
            (0, debug_1.debug)('capture (no API key):', eventName, properties);
            return;
        }
        this.ensureInitStarted();
        const eventProps = {
            ...this.sessionProperties,
            'session id': (0, observability_1.getSessionId)(),
            'run id': (0, observability_1.getRunId)(),
            ...properties,
        };
        const options = {
            device_id: this.anonymousId,
        };
        if (this.distinctId) {
            options.user_id = this.distinctId;
        }
        this.client.track(eventName, eventProps, options);
        (0, debug_1.debug)('capture:', eventName, properties);
    }
    /**
     * Capture a wizard-specific event. Automatically prepends "wizard cli: " to the event name.
     * All new wizard analytics should use this method instead of capture() directly.
     * Use lowercase with spaces for eventName (e.g. "agent started", "api key submitted")
     * per Amplitude quickstart taxonomy guidelines.
     */
    wizardCapture(eventName, properties) {
        this.capture(`wizard cli: ${eventName}`, properties);
    }
    /**
     * Apply feature-flag–based opt-out to the Amplitude SDK.
     * Defaults to ON — only opts out when `wizard-agent-analytics` is explicitly 'off'/'false'.
     */
    applyOptOut() {
        const flagValue = (0, feature_flags_1.getFlag)(feature_flags_1.FLAG_AGENT_ANALYTICS);
        const optOut = flagValue === 'off' || flagValue === 'false';
        this.client.setOptOut(optOut);
        if (optOut) {
            (0, debug_1.debug)('analytics: opted out via wizard-agent-analytics flag');
        }
    }
    ensureInitStarted() {
        if (this.initPromise !== null) {
            return;
        }
        const apiKey = resolveTelemetryApiKey();
        if (!apiKey) {
            return;
        }
        this.initPromise = this.client.init(apiKey, {
            serverUrl: getAmplitudeNodeServerUrl(),
            flushQueueSize: 10,
            flushIntervalMillis: 1000,
        }).promise;
    }
    /**
     * Initialize the Amplitude Experiment feature-flag client.
     * Call once early in startup (e.g. after obtaining a user/device ID).
     */
    async initFlags() {
        await (0, feature_flags_1.initFeatureFlags)(this.distinctId, this.anonymousId);
    }
    /**
     * Re-evaluate flags after the user identity changes (e.g. post-login).
     */
    async refreshFlags() {
        await (0, feature_flags_1.refreshFlags)(this.distinctId, this.anonymousId);
        this.activeFlags = (0, feature_flags_1.getAllFlags)();
    }
    // eslint-disable-next-line @typescript-eslint/require-await
    async getFeatureFlag(flagKey) {
        return (0, feature_flags_1.getFlag)(flagKey);
    }
    /**
     * Check if a flag is enabled (variant is 'on' or 'true').
     */
    isFeatureFlagEnabled(flagKey) {
        return (0, feature_flags_1.isFlagEnabled)(flagKey);
    }
    /**
     * Evaluate all feature flags for the current user at the start of a run.
     * Result is cached; subsequent calls in the same run return the same map.
     * Returns flag key -> string value (booleans become 'true'/'false').
     */
    // eslint-disable-next-line @typescript-eslint/require-await
    async getAllFlagsForWizard() {
        if (this.activeFlags !== null) {
            return this.activeFlags;
        }
        this.activeFlags = (0, feature_flags_1.getAllFlags)();
        return this.activeFlags;
    }
    /**
     * Flush pending events without ending the session.
     * Use for mid-session flushes (e.g. feedback command) where
     * the wizard continues running after the flush.
     */
    async flush() {
        if (this.initPromise === null)
            return;
        try {
            await this.initPromise;
            await this.client.flush().promise;
        }
        catch (err) {
            (0, debug_1.debug)('analytics flush error:', err);
        }
    }
    async shutdown(status) {
        this.wizardCapture('session ended', {
            status,
            'session duration ms': Date.now() - this.startedAt,
        });
        if (this.initPromise === null) {
            return;
        }
        try {
            await this.initPromise;
            await this.client.flush().promise;
        }
        catch (err) {
            (0, debug_1.debug)('analytics shutdown flush error:', err);
        }
    }
}
exports.Analytics = Analytics;
/**
 * Full Amplitude `event_type` for CLI/TUI product feedback.
 * Same string as `wizardCapture('feedback submitted', …)`.
 */
exports.WIZARD_FEEDBACK_EVENT_TYPE = 'wizard cli: feedback submitted';
exports.analytics = new Analytics();
/**
 * Unified wizard error telemetry (aligns with starter taxonomy “Error Encountered”).
 * Emits `wizard cli: error encountered` with category / message / context.
 */
function captureWizardError(errorCategory, errorMessage, errorContext, extra) {
    exports.analytics.wizardCapture('error encountered', {
        'error category': errorCategory,
        'error message': errorMessage,
        'error context': errorContext,
        ...extra,
    });
}
