"use strict";
/**
 * agent-ops — Pure business logic for agent-mode verbs.
 *
 * These functions power `amplitude-wizard detect | status | auth token | auth status`.
 * They return serializable data so thin CLI wrappers can emit JSON (for agents)
 * or format for humans with the same underlying source of truth.
 *
 * No UI, no process.exit, no console.log — keeps the logic testable and reusable
 * from both the CLI and the future external MCP server.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runDetect = runDetect;
exports.runStatus = runStatus;
exports.getAuthToken = getAuthToken;
exports.runPlan = runPlan;
exports.resolvePlan = resolvePlan;
exports.runVerify = runVerify;
exports.runProjectsList = runProjectsList;
exports.getAuthStatus = getAuthStatus;
const path_1 = __importDefault(require("path"));
const run_1 = require("../run");
const detect_amplitude_1 = require("./detect-amplitude");
const api_key_store_1 = require("../utils/api-key-store");
const ampli_settings_1 = require("../utils/ampli-settings");
const registry_1 = require("./registry");
const agent_plans_js_1 = require("./agent-plans.js");
const event_plan_parser_js_1 = require("./event-plan-parser.js");
// ── Pre-existing event-plan reader ──────────────────────────────────
//
// The persisted event plan lives under `<installDir>/.amplitude/events.json`
// (canonical), with legacy `.amplitude-events.json` still read when present.
// PR #274 made the plan persist across cancel/error so a re-run of `wizard plan`
// can surface the prior plan as hints in the new plan emission.
/**
 * Read the persisted event plan (canonical or legacy path) and return its
 * entries adapted to the WizardPlan `events` shape. Returns `[]` for any
 * non-fatal failure.
 */
function readPreExistingEventHints(installDir) {
    const events = (0, event_plan_parser_js_1.readLocalEventPlan)(installDir);
    if (events.length === 0)
        return [];
    // Adapt each entry to PlannedEvent: drop entries with no usable name,
    // and clamp lengths so we never trip the WizardPlan zod validator
    // (name max 80, desc max 500).
    const adapted = [];
    for (const entry of events) {
        const name = entry.name.trim();
        if (name.length === 0)
            continue;
        adapted.push({
            name: name.slice(0, 80),
            description: entry.description.trim().slice(0, 500),
        });
    }
    return adapted;
}
async function runDetect(installDir) {
    const results = await (0, run_1.detectAllFrameworks)(installDir);
    const hit = results.find((r) => r.detected);
    const integration = hit?.integration ?? null;
    const frameworkName = integration
        ? registry_1.FRAMEWORK_REGISTRY[integration].metadata.name
        : null;
    return {
        integration,
        frameworkName,
        confidence: integration ? 'detected' : 'none',
        signals: results.map(({ integration, detected, durationMs, timedOut, error }) => ({
            integration,
            detected,
            durationMs,
            timedOut,
            ...(error ? { error } : {}),
        })),
    };
}
async function runStatus(installDir) {
    const [detect, amplitudeInstalled] = await Promise.all([
        runDetect(installDir),
        Promise.resolve((0, detect_amplitude_1.detectAmplitudeInProject)(installDir)),
    ]);
    const apiKey = (0, api_key_store_1.readApiKeyWithSource)(installDir);
    const user = (0, ampli_settings_1.getStoredUser)();
    const hasToken = user ? Boolean((0, ampli_settings_1.getStoredToken)(user.id, user.zone)) : false;
    return {
        installDir,
        framework: {
            integration: detect.integration,
            name: detect.frameworkName,
        },
        amplitudeInstalled,
        apiKey: {
            configured: Boolean(apiKey),
            source: apiKey?.source ?? null,
        },
        auth: {
            loggedIn: Boolean(user && hasToken && user.id !== 'pending'),
            email: user?.email ?? null,
            zone: user?.zone ?? null,
        },
    };
}
function getAuthToken() {
    const user = (0, ampli_settings_1.getStoredUser)();
    if (!user || user.id === 'pending') {
        return { token: null, expiresAt: null, zone: null };
    }
    const stored = (0, ampli_settings_1.getStoredToken)(user.id, user.zone);
    if (!stored) {
        return { token: null, expiresAt: null, zone: user.zone };
    }
    return {
        token: stored.accessToken,
        expiresAt: stored.expiresAt,
        zone: user.zone,
    };
}
/**
 * Run the planning phase of the wizard: detect framework, surface any
 * pre-existing event hints from `<installDir>/.amplitude/events.json` (or
 * legacy `.amplitude-events.json`) — the record written by `confirm_event_plan`
 * and preserved across cancel/error since PR #274 — persist a `WizardPlan` to disk,
 * and return it for the caller to emit as NDJSON.
 *
 * Pre-existing events make `wizard plan` resumable: a user who confirmed
 * an event plan, hit Ctrl+C, and re-ran `wizard plan` gets their previous
 * selections reflected in the new plan emission. `readLocalEventPlan` shares
 * the same parsing path as the TUI Event Plan viewer, so casing variants
 * the agent emits in the wild (`name`/`event`/`eventName`/`event_name`)
 * all hydrate. Malformed or missing files quietly degrade to `events: []`
 * — they are diagnostic noise, not a reason to fail the plan.
 *
 * No agent run, no file writes outside the plans directory. Outer agents
 * inspect the returned plan and decide whether to call `apply`.
 */
async function runPlan(installDir) {
    // Resolve to an absolute path before anything else — including before
    // persistence. A relative `installDir` (e.g. `.` or `./my-project`)
    // resolves against the *plan-time* cwd and that resolution must be
    // baked into the persisted plan; otherwise `apply` re-resolves against
    // its own (possibly-different) cwd and the run lands in the wrong dir.
    const resolvedInstallDir = path_1.default.resolve(installDir);
    const detect = await runDetect(resolvedInstallDir);
    const integration = detect.integration;
    // Map integration → SDK package. The FrameworkConfig.detection block
    // already tracks the canonical npm/pip/etc package name the wizard cares
    // about, so we surface that here. (FrameworkConfig has no per-framework
    // "init file" field today; surfacing concrete file paths is a follow-up
    // once the three-phase Planner/Integrator split lands.)
    const sdk = integration && registry_1.FRAMEWORK_REGISTRY[integration]
        ? registry_1.FRAMEWORK_REGISTRY[integration].detection.packageName
        : null;
    // Pre-existing event plan from a prior cancelled/errored run, if any.
    // Empty array on missing/malformed file — see `readPreExistingEventHints`.
    const events = readPreExistingEventHints(resolvedInstallDir);
    // File-change hints are intentionally empty until the Planner phase can
    // emit them. Outer agents see `fileChanges: []` and know the plan is
    // detection-only on that axis; the real list arrives during `apply`
    // via inner-agent lifecycle events (Gap 2 PR).
    const fileChanges = [];
    const plan = (0, agent_plans_js_1.createAndPersistPlan)({
        installDir: resolvedInstallDir,
        framework: integration ?? 'generic',
        frameworkName: detect.frameworkName,
        sdk,
        events,
        fileChanges,
    });
    return { plan, detected: integration !== null };
}
/** Resolve a plan by ID with structured error reporting for the CLI layer. */
async function resolvePlan(planId) {
    return (0, agent_plans_js_1.loadPlan)(planId);
}
/**
 * Lightweight verification step. Mirrors the checks the wizard runs at the
 * end of an `apply` to confirm the install landed: SDK package present,
 * API key configured, framework still detected.
 *
 * Real ingestion verification (events flowing into the project) requires
 * an authenticated MCP call and is gated by the existing `pollForDataIngestion`
 * helper in `agent-runner.ts`. This op is the cheap, no-network version
 * outer agents can call between `apply` and ingestion polling.
 */
async function runVerify(installDir) {
    const status = await runStatus(installDir);
    const failures = [];
    if (status.amplitudeInstalled.confidence === 'none') {
        failures.push('amplitude SDK is not installed in the project');
    }
    if (!status.apiKey.configured) {
        failures.push('amplitude API key is not configured');
    }
    if (!status.framework.integration) {
        failures.push('no framework detected (cannot validate setup)');
    }
    return {
        installDir,
        framework: status.framework,
        amplitudeInstalled: status.amplitudeInstalled,
        apiKeyConfigured: status.apiKey.configured,
        outcome: failures.length === 0 ? 'pass' : 'fail',
        failures,
    };
}
/**
 * List the authenticated user's accessible Amplitude projects/environments,
 * one row per (org, workspace, env) tuple that has an API key. Powers the
 * `projects list --agent --query <q>` command, which the
 * `environment_selection` `needs_input` event references via
 * `pagination.nextCommand` so outer agents can hand a search box to humans
 * without dumping 500-row lists into context.
 *
 * Reads the cached OAuth token + zone from `~/.ampli.json`; returns a
 * warning instead of throwing when the user is logged out so the CLI can
 * still emit a useful NDJSON envelope.
 */
async function runProjectsList(input = {}) {
    const limit = Math.max(1, Math.min(input.limit ?? 25, 200));
    const offset = Math.max(0, input.offset ?? 0);
    const query = input.query?.trim().toLowerCase() ?? null;
    const user = (0, ampli_settings_1.getStoredUser)();
    if (!user || user.id === 'pending') {
        return {
            total: 0,
            returned: 0,
            choices: [],
            query,
            warning: 'Not logged in. Run `npx @amplitude/wizard login` first.',
        };
    }
    const stored = (0, ampli_settings_1.getStoredToken)(user.id, user.zone);
    if (!stored?.idToken) {
        return {
            total: 0,
            returned: 0,
            choices: [],
            query,
            warning: 'No stored Amplitude id_token. Run `npx @amplitude/wizard login` first.',
        };
    }
    const { fetchAmplitudeUser } = await import('./api.js');
    const userInfo = await fetchAmplitudeUser(stored.idToken, user.zone);
    // Flatten orgs/workspaces/environments to one choice per (env with apiKey).
    const allChoices = [];
    for (const org of userInfo.orgs) {
        for (const ws of org.projects) {
            const envs = (ws.environments ?? [])
                .filter((e) => e.app?.apiKey)
                .sort((a, b) => a.rank - b.rank);
            for (const env of envs) {
                const appId = env.app?.id ?? '';
                if (!appId)
                    continue;
                allChoices.push({
                    appId,
                    label: `${org.name} / ${ws.name} / ${env.name}`,
                    description: `${org.name} > ${ws.name} > ${env.name}`,
                    orgId: org.id,
                    orgName: org.name,
                    workspaceId: ws.id,
                    workspaceName: ws.name,
                    envName: env.name,
                    rank: env.rank,
                    resumeFlags: ['--app-id', appId],
                });
            }
        }
    }
    // Filter by query — match across every label field, case-insensitive.
    const matching = query
        ? allChoices.filter((c) => {
            const haystack = [
                c.label,
                c.appId,
                c.orgName,
                c.workspaceName,
                c.envName,
            ]
                .join(' ')
                .toLowerCase();
            return haystack.includes(query);
        })
        : allChoices;
    // Stable sort by rank then label so pagination is deterministic across
    // calls — outer agents that walk pages depend on consistent ordering.
    matching.sort((a, b) => a.rank - b.rank || a.label.localeCompare(b.label));
    const page = matching.slice(offset, offset + limit);
    return {
        total: matching.length,
        returned: page.length,
        choices: page,
        query,
    };
}
function getAuthStatus() {
    const user = (0, ampli_settings_1.getStoredUser)();
    if (!user || user.id === 'pending') {
        return { loggedIn: false, user: null, tokenExpiresAt: null };
    }
    const stored = (0, ampli_settings_1.getStoredToken)(user.id, user.zone);
    return {
        loggedIn: Boolean(stored),
        user: {
            email: user.email,
            firstName: user.firstName,
            lastName: user.lastName,
            zone: user.zone,
        },
        tokenExpiresAt: stored?.expiresAt ?? null,
    };
}
