"use strict";
/**
 * WizardSession — single source of truth for every decision the wizard needs.
 *
 * Populated in layers:
 *   CLI args / env vars  →  populate fields directly
 *   Auto-detection       →  framework, typescript, package manager
 *   TUI screens          →  region, framework disambiguation, etc.
 *   OAuth                →  credentials
 *
 * Business logic reads from the session. Never calls a prompt.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.OutroKind = exports.SlackOutcome = exports.McpOutcome = exports.OPT_IN_DISCOVERED_FEATURES = exports.TRAILING_FEATURES = exports.INLINE_FEATURES = exports.ADDITIONAL_FEATURE_PROMPTS = exports.ADDITIONAL_FEATURE_LABELS = exports.AdditionalFeature = exports.DiscoveredFeature = exports.PostAgentStepStatus = exports.RunPhase = exports.CredentialsSchema = exports.CliArgsSchema = exports.WorkspaceIdSchema = exports.AppIdSchema = exports.AuthOnboardingPath = void 0;
exports.isCreateAccountOnboarding = isCreateAccountOnboarding;
exports.toAppId = toAppId;
exports.tryToAppId = tryToAppId;
exports.toWorkspaceId = toWorkspaceId;
exports.toCredentialAppId = toCredentialAppId;
exports.isAuthenticated = isAuthenticated;
exports.isConfigured = isConfigured;
exports.isRunning = isRunning;
exports.buildSession = buildSession;
const node_crypto_1 = require("node:crypto");
const zod_1 = require("zod");
const constants_1 = require("./constants");
const install_dir_1 = require("../utils/install-dir");
/**
 * Whether the user is signing into an existing Amplitude account or
 * provisioning a new one during onboarding (TUI picker or non-interactive flags).
 */
exports.AuthOnboardingPath = {
    SignIn: 'sign_in',
    CreateAccount: 'create_account',
};
function isCreateAccountOnboarding(session) {
    return session.authOnboardingPath === exports.AuthOnboardingPath.CreateAccount;
}
// ── Branded ID types ─────────────────────────────────────────────────────
//
// Why brand: prior bugs (see PR #62, "org data mapping") were caused by
// silently passing a workspace id where an app id was expected, or the other
// direction. The two are structurally `string` and `number` and TypeScript
// could not catch the mismatch. Branding them with `z.brand` gives us
// distinct nominal types without runtime cost — a raw `number` can no longer
// be assigned to an `AppId` slot without going through the parser/helper.
/**
 * Numeric Amplitude app id (canonical across Amplitude services).
 * Sourced from `App.id` in the Data API and `app_id` in the Python monorepo.
 */
exports.AppIdSchema = zod_1.z.number().int().positive().brand();
/**
 * UUID-shaped Amplitude workspace id. Sourced from `Workspace.id` in the
 * Data API. Distinct from `selectedAppId` (string env app id) and the
 * stringified app id used in some MCP tool calls.
 */
exports.WorkspaceIdSchema = zod_1.z.string().min(1).brand();
/**
 * Construct an AppId from a raw number. Throws on invalid input — use at
 * trust boundaries (CLI parse, API response decoding, stored config).
 */
function toAppId(value) {
    return exports.AppIdSchema.parse(value);
}
/**
 * Best-effort AppId construction. Returns undefined when the input cannot be
 * coerced to a positive integer. Use when accepting user-supplied strings.
 */
function tryToAppId(value) {
    if (value === null || value === undefined || value === '')
        return undefined;
    const n = typeof value === 'number' ? value : Number(value);
    const result = exports.AppIdSchema.safeParse(n);
    return result.success ? result.data : undefined;
}
/**
 * Construct a WorkspaceId from a raw string. Throws on empty input — use at
 * trust boundaries. Most call sites get workspace ids from API responses
 * that have already been validated, so this just attaches the brand.
 */
function toWorkspaceId(value) {
    return exports.WorkspaceIdSchema.parse(value);
}
/**
 * `credentials.appId` is `AppId | 0` — `0` is the "unknown" sentinel used
 * when OAuth produced credentials but the user has not yet picked an env.
 * This helper safely coerces raw input (string from CLI, number from API)
 * into the credentials shape: a branded AppId on success, `0` otherwise.
 *
 * Use at the trust boundary where credentials are constructed. After that,
 * the `AppId | 0` type prevents accidental cross-mixing with workspace ids
 * or other unrelated numbers — the same protection PR #62 motivated for
 * the top-level `WizardSession.appId`.
 */
function toCredentialAppId(value) {
    return tryToAppId(value) ?? 0;
}
/**
 * Zod schema for CLI args passed to `buildSession()`.
 * Coerces `appId` from string to a branded positive integer.
 * All boolean flags default to false; `installDir` defaults to cwd.
 */
exports.CliArgsSchema = zod_1.z.object({
    appId: zod_1.z
        .union([zod_1.z.string(), zod_1.z.number()])
        .optional()
        .transform((v) => {
        if (v === undefined || v === '')
            return undefined;
        const result = exports.AppIdSchema.safeParse(Number(v));
        return result.success ? result.data : undefined;
    }),
    installDir: zod_1.z
        .string()
        .default(() => process.cwd())
        // `resolveInstallDir` expands a leading `~` before resolving — bare
        // `path.resolve('~/foo')` does NOT expand `~` (Node treats it as a
        // literal directory name and joins onto cwd). Without this,
        // `--install-dir="~/foo"` from `~/bar` produces `~/bar/~/foo`.
        .transform((p) => (0, install_dir_1.resolveInstallDir)(p)),
    debug: zod_1.z.boolean().default(false),
    verbose: zod_1.z.boolean().default(false),
    ci: zod_1.z.boolean().default(false),
    agent: zod_1.z.boolean().default(false),
    forceInstall: zod_1.z.boolean().default(false),
    authOnboardingPath: zod_1.z.enum(['sign_in', 'create_account']).default('sign_in'),
    localMcp: zod_1.z.boolean().default(false),
    menu: zod_1.z.boolean().default(false),
    benchmark: zod_1.z.boolean().default(false),
    // Agent model tier. See `src/utils/types.ts:WizardMode` for the mapping.
    // Default 'standard' = current behavior (no change for existing users).
    mode: zod_1.z.enum(['fast', 'standard', 'thorough']).default('standard'),
    apiKey: zod_1.z.string().optional(),
    integration: zod_1.z.string().optional(),
    appName: zod_1.z.string().optional(),
    signupEmail: zod_1.z
        .string()
        .regex(constants_1.EMAIL_REGEX, 'Invalid email')
        .nullable()
        .optional()
        .default(null),
    signupFullName: zod_1.z.string().nullable().optional().default(null),
    acceptTos: zod_1.z.boolean().default(false),
    region: zod_1.z.enum(['us', 'eu']).nullable().optional().default(null),
});
/**
 * Zod schema for validated Amplitude credentials.
 * Exported for incremental adoption at credential-construction sites.
 */
exports.CredentialsSchema = zod_1.z.object({
    accessToken: zod_1.z.string().min(1, 'accessToken is required'),
    idToken: zod_1.z.string().optional(),
    projectApiKey: zod_1.z.string().min(1, 'projectApiKey is required'),
    host: zod_1.z.string().url('host must be a valid URL'),
    /**
     * Numeric Amplitude app ID. Branded `AppId` on success or the literal
     * `0` sentinel when the env hasn't been picked yet. Construct via
     * `toCredentialAppId(value)` at the trust boundary.
     */
    appId: zod_1.z.union([exports.AppIdSchema, zod_1.z.literal(0)]),
});
function parseAppIdArg(value) {
    return tryToAppId(value);
}
/**
 * Lifecycle phase of the main work (agent run, MCP install, etc.)
 * NOTE: Duplicated in src/ui/tui/session-constants.ts (ESM/CJS workaround).
 * If you change these values, update that file too — a test enforces sync.
 */
exports.RunPhase = {
    /** Still gathering input (intro, setup screens) */
    Idle: 'idle',
    /** Main work is in progress */
    Running: 'running',
    /** Main work finished successfully */
    Completed: 'completed',
    /** Main work finished with an error */
    Error: 'error',
};
/**
 * Status of a single post-agent step (commit planned events, create
 * dashboard, etc.) — work that runs between the Claude agent finishing
 * and the wizard transitioning to the MCP screen. Modeled separately
 * from agent TodoWrite tasks because these are framework-controlled,
 * not agent-controlled, and rendering them in the same list would break
 * the `syncTodos` "agent-authoritative" invariant.
 *
 * NOTE: Mirrored in src/ui/tui/session-constants.ts (ESM/CJS workaround).
 */
exports.PostAgentStepStatus = {
    Pending: 'pending',
    InProgress: 'in_progress',
    Completed: 'completed',
    Skipped: 'skipped',
};
/** Features discovered by the feature-discovery subagent */
exports.DiscoveredFeature = {
    Stripe: 'stripe',
    LLM: 'llm',
    SessionReplay: 'session_replay',
    Engagement: 'engagement',
};
/** Additional features the agent can integrate after the main setup */
exports.AdditionalFeature = {
    LLM: 'llm',
    SessionReplay: 'session_replay',
    Engagement: 'engagement',
};
/** Human-readable labels for additional features (used in TUI progress) */
exports.ADDITIONAL_FEATURE_LABELS = {
    [exports.AdditionalFeature.LLM]: 'LLM analytics',
    [exports.AdditionalFeature.SessionReplay]: 'Session Replay',
    [exports.AdditionalFeature.Engagement]: 'Guides & Surveys',
};
/** Agent prompts for each additional feature, injected via the stop hook */
exports.ADDITIONAL_FEATURE_PROMPTS = {
    [exports.AdditionalFeature.LLM]: `Now integrate LLM analytics with Amplitude. Use the Amplitude MCP server to find the appropriate LLM analytics skill, install it, and follow its workflow. Amplitude basics are already installed. Update the setup report markdown file when complete with additions from this task. `,
    [exports.AdditionalFeature.SessionReplay]: `The user wants to enable Amplitude Session Replay. Please configure it now:

1. If the project uses @amplitude/unified (preferred), add a sessionReplay block to the existing initAll() call: sessionReplay: { sampleRate: 1 }.
2. If the project uses @amplitude/analytics-browser standalone, install @amplitude/plugin-session-replay-browser and register it as a plugin with sampleRate: 1.
3. Do not add any comments about sample rates or production tuning.

After making changes, give a one-sentence summary of what was configured.`,
    [exports.AdditionalFeature.Engagement]: `The user wants to enable Amplitude Guides & Surveys (the engagement plugin). Please configure it now:

1. If the project uses @amplitude/unified (preferred), add an engagement block to the existing initAll() call: engagement: {}. The empty object is sufficient — the plugin initializes with sensible defaults and reads remote config.
2. If the project uses @amplitude/analytics-browser standalone, install @amplitude/engagement-browser using the wizard-tools detect_package_manager output, then import { plugin as engagementPlugin } from '@amplitude/engagement-browser' and register it via amplitude.add(engagementPlugin()) before init().
3. Do not add config options the user didn't ask for. Do not add comments about server zone or production tuning.
4. Update the setup report markdown file when complete with what was configured.

After making changes, give a one-sentence summary of what was configured.`,
};
/**
 * Features that the agent configures inline as part of SDK initialization.
 * Their prompts are appended to the initial integration prompt and they are
 * NOT drained by the stop hook or rendered as separate task items.
 */
exports.INLINE_FEATURES = new Set([
    exports.AdditionalFeature.SessionReplay,
    exports.AdditionalFeature.Engagement,
]);
/**
 * Features that run as a separate "Set up X" task after the main agent run,
 * drained one at a time via the stop hook and rendered as trailing task items.
 */
exports.TRAILING_FEATURES = new Set([
    exports.AdditionalFeature.LLM,
]);
/**
 * Discovered features that map to an opt-in AdditionalFeature.
 * Stripe is discovered but not opt-in — it's a passive doc link.
 */
exports.OPT_IN_DISCOVERED_FEATURES = new Set(Object.values(exports.AdditionalFeature));
/** Outcome of the MCP server installation step */
exports.McpOutcome = {
    NoClients: 'no_clients',
    Skipped: 'skipped',
    Installed: 'installed',
    Failed: 'failed',
};
/** Outcome of the Slack integration setup step */
exports.SlackOutcome = {
    Skipped: 'skipped',
    Configured: 'configured',
};
/** Outcome kind for the outro screen */
exports.OutroKind = {
    Success: 'success',
    Error: 'error',
    Cancel: 'cancel',
};
/**
 * True iff OAuth has produced credentials and an org id is recorded.
 * Callers that only need credentials can use this and stop sprinkling
 * `if (!session.credentials)` early-returns.
 */
function isAuthenticated(session) {
    return session.credentials !== null && session.selectedOrgId !== null;
}
/**
 * True iff the session has everything needed to make scoped Data API calls:
 * credentials + org + project + region.
 */
function isConfigured(session) {
    return (isAuthenticated(session) &&
        session.selectedProjectId !== null &&
        session.region !== null);
}
/**
 * True iff the agent run is currently in flight. Used by retry/error paths
 * that should only act on a live run.
 */
function isRunning(session) {
    return (session.runPhase === exports.RunPhase.Running &&
        isConfigured(session) &&
        session.integration !== null &&
        session.runStartedAt !== null);
}
function resolveAuthOnboardingPathFromArgs(args) {
    if (args.authOnboardingPath !== undefined) {
        return args.authOnboardingPath;
    }
    if (args.authOnboarding === 'create-account') {
        return exports.AuthOnboardingPath.CreateAccount;
    }
    if (args.authOnboarding === 'sign-in') {
        return exports.AuthOnboardingPath.SignIn;
    }
    if (args.accountCreationFlow === true || args.signup === true) {
        return exports.AuthOnboardingPath.CreateAccount;
    }
    return exports.AuthOnboardingPath.SignIn;
}
/**
 * Build a WizardSession from CLI args, pre-populating whatever is known.
 *
 * **Auth path (`sign_in` vs `create_account`):** Prefer `authOnboarding` /
 * `authOnboardingPath` from `--auth-onboarding` (or the Intro menu in TUI).
 * `signup` / `accountCreationFlow` are legacy boolean inputs still merged
 * here so older scripts and env-injected argv keep working; both map to
 * create-account when true.
 */
function buildSession(args) {
    const resolvedAuthPath = resolveAuthOnboardingPathFromArgs(args);
    // Validate CLI args via Zod — warn on bad input but fall back to defaults
    const parsed = exports.CliArgsSchema.safeParse({
        ...args,
        authOnboardingPath: resolvedAuthPath,
    });
    if (!parsed.success) {
        console.warn(`[wizard] Invalid CLI args (falling back to defaults): ${parsed.error.issues
            .map((i) => `${i.path.join('.')}: ${i.message}`)
            .join(', ')}`);
    }
    // Use Zod-validated data (with coerced appId and defaults) when available
    const validated = parsed.success ? parsed.data : args;
    return {
        debug: validated.debug ?? false,
        verbose: validated.verbose ?? false,
        forceInstall: validated.forceInstall ?? false,
        installDir: (0, install_dir_1.resolveInstallDir)(validated.installDir),
        ci: validated.ci ?? false,
        agent: false,
        authOnboardingPath: parsed.success
            ? parsed.data.authOnboardingPath
            : resolveAuthOnboardingPathFromArgs(args),
        // On parse failure we intentionally reject raw args for the signup
        // fields — otherwise a malformed email would skip zod's .email() check
        // via the fallback and reach the signup endpoint. Null here means the
        // signup wrapper short-circuits with "missing email or fullName".
        signupEmail: parsed.success ? validated.signupEmail ?? null : null,
        signupFullName: parsed.success ? validated.signupFullName ?? null : null,
        localMcp: validated.localMcp ?? false,
        apiKey: validated.apiKey,
        menu: validated.menu ?? false,
        benchmark: validated.benchmark ?? false,
        mode: validated.mode ?? 'standard',
        // The CLI boundary stamps this directly on the session after
        // buildSession returns; default to null here so unit tests and any
        // other buildSession caller that doesn't wire `--context-file`
        // start with a known-empty value.
        orchestratorContext: null,
        appId: parsed.success ? parsed.data.appId : parseAppIdArg(args.appId),
        // Stable across the entire wizard run; forwarded to the LLM gateway as
        // x-amp-wizard-session-id so every /v1/messages call shares one Agent
        // Analytics session.
        agentSessionId: (0, node_crypto_1.randomUUID)(),
        setupConfirmed: false,
        integration: validated.integration ?? null,
        frameworkContext: {},
        frameworkContextAnswerOrder: [],
        typescript: false,
        detectedFrameworkLabel: null,
        detectionComplete: false,
        detectionResults: null,
        projectHasData: null,
        activationLevel: null,
        localInstrumentationComplete: false,
        activationOptionsComplete: false,
        snippetConfigured: false,
        // --region (alias: --zone) pre-populates region so non-TUI signup
        // targets the right DC. Same parse-failure guard as signupEmail above.
        region: parsed.success ? validated.region ?? null : null,
        regionForced: false,
        postAgentSteps: [],
        runPhase: exports.RunPhase.Idle,
        runStartedAt: null,
        discoveredFeatures: [],
        llmOptIn: false,
        sessionReplayOptIn: false,
        engagementOptIn: false,
        loggingOut: false,
        mcpComplete: false,
        mcpOutcome: null,
        mcpInstalledClients: [],
        slackComplete: false,
        slackOutcome: null,
        pendingOrgs: null,
        pendingAuthIdToken: null,
        pendingAuthAccessToken: null,
        scopeFilterMismatch: null,
        selectedOrgId: null,
        selectedOrgName: null,
        selectedProjectId: null,
        selectedProjectName: null,
        selectedEnvName: null,
        selectedAppId: null,
        loginUrl: null,
        credentials: null,
        apiKeyNotice: null,
        serviceStatus: null,
        retryState: null,
        outroData: null,
        introConcluded: false,
        requiresAccountConfirmation: false,
        additionalFeatureQueue: [],
        additionalFeatureCurrent: null,
        additionalFeatureCompleted: [],
        optInFeaturesComplete: false,
        frameworkConfig: null,
        amplitudePreDetected: false,
        amplitudePreDetectedChoicePending: false,
        dataIngestionConfirmed: false,
        checklistDashboardUrl: null,
        dashboardFallbackPhase: null,
        signupMagicLinkUrl: null,
        userEmail: null,
        _restoredFromCheckpoint: false,
        // --accept-tos pre-accepts ToS for non-TUI signup; in TUI mode the
        // ToSScreen still owns the UX but its `isComplete` check sees `true`
        // and skips. Email capture is independently flagged below.
        tosAccepted: validated.acceptTos === true ? true : null,
        emailCaptureComplete: false,
        signupTokensObtained: false,
        createProject: {
            pending: false,
            source: args.appName ? 'cli-flag' : null,
            suggestedName: args.appName ?? null,
        },
    };
}
