import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
/**
 * RunScreen — Agent dashboard focused on tasks and progress.
 *
 * Tabs:
 *   - Progress (default): full-width ProgressList, elapsed timer, currently
 *     editing file, inline event plan, and compact conditional tips
 *   - Logs: LogViewer tailing the wizard log file
 *   - Snake: easter egg game
 *
 * Queued additional features (LLM, Session Replay) appear in the task list
 * as pending → in_progress → completed items as the stop hook drains the
 * queue. Stripe stays a passive doc-link tip when detected.
 */
import { Box, Text } from 'ink';
import { useState, useEffect, useRef } from 'react';
import { useWizardStore } from '../hooks/useWizardStore.js';
import { useScreenHints } from '../hooks/useScreenHints.js';
import { useTimedCoaching } from '../hooks/useTimedCoaching.js';
import { TabContainer, ProgressList, LogViewer, SnakeGame, EventPlanViewer, TerminalLink, } from '../primitives/index.js';
import { Colors, Icons, SPINNER_FRAMES, SPINNER_INTERVAL } from '../styles.js';
import { BrailleSpinner } from '../components/BrailleSpinner.js';
import { AnimatedAmplitudeLogo } from '../components/AmplitudeLogo.js';
import { RetryStatusChip } from '../components/RetryBanner.js';
import { FileWritesPanel } from '../components/FileWritesPanel.js';
import { FinalizingPanel } from '../components/FinalizingPanel.js';
import { PostAgentStepStatus } from '../session-constants.js';
import { useStdoutDimensions } from '../hooks/useStdoutDimensions.js';
import { DiscoveredFeature } from '../../../lib/wizard-session.js';
import { ADDITIONAL_FEATURE_LABELS, TRAILING_FEATURES, } from '../session-constants.js';
import { OUTBOUND_URLS } from '../../../lib/constants.js';
import path from 'node:path';
import { getLogFile } from '../../../utils/storage-paths.js';
import { getSessionStartMs } from '../../../lib/observability/index.js';
const RUN_HINTS = Object.freeze([
    { key: '←→', label: 'Tabs' },
    { key: 'Ctrl+C', label: 'Cancel' },
]);
/** Format elapsed seconds as "Xm Ys". */
function formatElapsed(seconds) {
    const m = Math.floor(seconds / 60);
    const s = seconds % 60;
    if (m === 0)
        return `${s}s`;
    return `${m}m ${s}s`;
}
/**
 * Cap a status string for inline display. Belt-and-braces against unbounded
 * streamed content (e.g. raw stream-event JSON forwarded by runAgentLocally
 * before its filter stripped them). Yoga's `truncate-end` is the primary
 * defense once the row is flex-shrinkable, but a JS cap keeps the header
 * sane regardless of layout context.
 */
const STATUS_MAX_LEN = 80;
function truncateStatus(s) {
    if (s.length <= STATUS_MAX_LEN)
        return s;
    return s.slice(0, STATUS_MAX_LEN - 1) + '…';
}
/**
 * Resolve the path of the file the inner agent is currently working on.
 *
 * Source of truth: PreToolUse/PostToolUse hooks populate `store.fileWrites`
 * with structured rows (planned → applied/failed). The most recent row is
 * the file the agent has its hands on right now.
 *
 * The previous implementation regex'd `store.statusMessages` for tokens
 * matching `\S+\.(tsx?|jsx?|py|...)`. That worked when status strings were
 * structured `[STATUS]` markers, but `pushStatus` now also receives raw
 * model text deltas and SDK stream-event protocol fragments — both of
 * which can contain `.js`, `.ts`, etc. Production users were seeing
 * markdown code refs (`` `src/index.js` ``, leading backtick included)
 * and partial JSON like `{"type":"content_block_delta","index":0,...}`
 * surface in the "currently editing" slot. The header lied.
 *
 * Reading from `fileWrites` is the structural fix: PreToolUse only fires
 * for actual Edit/Write/MultiEdit invocations, so prose and protocol
 * frames can't contaminate the value. Returns the relative path when an
 * `installDir` is provided (so wide paths don't blow out the header
 * slot).
 */
function extractCurrentFile(fileWrites, installDir) {
    if (fileWrites.length === 0)
        return null;
    const filePath = fileWrites[fileWrites.length - 1].path;
    if (installDir &&
        filePath.startsWith(installDir) &&
        (filePath.length === installDir.length ||
            filePath[installDir.length] === '/' ||
            filePath[installDir.length] === '\\')) {
        const rel = path.relative(installDir, filePath);
        return rel === '' ? filePath : rel;
    }
    return filePath;
}
/** Compact inline display of planned event names. */
const InlineEventPlan = ({ store }) => {
    const events = store.eventPlan.filter((e) => e.name);
    if (events.length === 0)
        return null;
    return (_jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsxs(Text, { color: Colors.secondary, children: [_jsxs(Text, { bold: true, color: Colors.accent, children: [Icons.diamond, " Events:"] }), ' ', events.map((e) => e.name).join(', ')] }) }));
};
/** Compact conditional tips — Stripe doc link only (other features are queued tasks). */
const ConditionalTips = ({ store }) => {
    const { discoveredFeatures } = store.session;
    const tips = [];
    if (discoveredFeatures.includes(DiscoveredFeature.Stripe)) {
        tips.push(_jsxs(Text, { color: Colors.secondary, children: [_jsx(Text, { color: Colors.accent, children: Icons.diamond }), " Stripe detected", Icons.dash, " add as data source:", ' ', _jsx(TerminalLink, { url: OUTBOUND_URLS.stripeDataSource, children: OUTBOUND_URLS.stripeDataSource })] }, "stripe"));
    }
    if (tips.length === 0)
        return null;
    return (_jsx(Box, { flexDirection: "column", marginTop: 1, children: tips }));
};
/** The main Progress tab content. */
const MIN_COLS_FOR_LOGO = 90;
const MIN_ROWS_FOR_LOGO = 22;
const ProgressTab = ({ store }) => {
    const [cols, rows] = useStdoutDimensions();
    const showLogo = cols >= MIN_COLS_FOR_LOGO && rows >= MIN_ROWS_FOR_LOGO;
    // Single interval drives the spinner, logo animation, and elapsed timer.
    // All three re-render in the same batch — no extra render cycles.
    const [tick, setTick] = useState(0);
    useEffect(() => {
        const id = setInterval(() => setTick((t) => t + 1), SPINNER_INTERVAL);
        return () => clearInterval(id);
    }, []);
    // Start time lives in the session so the elapsed counter keeps climbing
    // when the user tabs away and back (TabContainer unmounts inactive tabs).
    // The ?? fallback is defensive — setRunPhase(Running) always stamps this.
    const startedAt = store.session.runStartedAt ?? Date.now();
    const elapsed = Math.floor((Date.now() - startedAt) / 1000);
    const spinnerFrame = tick % SPINNER_FRAMES.length;
    const progressItems = store.tasks.map((t) => ({
        label: t.label,
        activeForm: t.activeForm,
        status: t.status,
    }));
    // Synthesize task items for trailing additional features: completed (in
    // run order), then the one currently being processed by the stop hook
    // (in_progress), then the rest of the queue (pending). Inline features
    // (e.g. Session Replay) are configured during SDK init and surface via
    // the agent's own TodoWrite items, not here.
    const { additionalFeatureCompleted, additionalFeatureCurrent } = store.session;
    const queueRemainder = store.session.additionalFeatureQueue.filter((f) => f !== additionalFeatureCurrent &&
        !additionalFeatureCompleted.includes(f) &&
        TRAILING_FEATURES.has(f));
    for (const feature of additionalFeatureCompleted.filter((f) => TRAILING_FEATURES.has(f))) {
        const label = ADDITIONAL_FEATURE_LABELS[feature];
        progressItems.push({
            label: `Set up ${label}`,
            activeForm: `Setting up ${label}...`,
            status: 'completed',
        });
    }
    if (additionalFeatureCurrent &&
        TRAILING_FEATURES.has(additionalFeatureCurrent)) {
        const label = ADDITIONAL_FEATURE_LABELS[additionalFeatureCurrent];
        progressItems.push({
            label: `Set up ${label}`,
            activeForm: `Setting up ${label}...`,
            status: 'in_progress',
        });
    }
    for (const feature of queueRemainder) {
        const label = ADDITIONAL_FEATURE_LABELS[feature];
        progressItems.push({
            label: `Set up ${label}`,
            activeForm: `Setting up ${label}...`,
            status: 'pending',
        });
    }
    // Synthetic 6th task for the post-agent dashboard fallback. The agent's
    // 5-task TodoWrite list is locked at five and "Build your starter dashboard"
    // covers the agent-side dashboard work. When the agent didn't actually
    // create the dashboard (skill drift, retry exhaustion, abort), the
    // post-agent `createDashboardStep` runs as a slow fallback and sets
    // `dashboardFallbackPhase` to `in_progress`. Without this row the user
    // sees a "5 / 5 tasks complete" header for the duration of the spinner —
    // the bug we fixed in PR #479.
    //
    // Guard: only render this synthetic task when postAgentSteps has NOT
    // been seeded. Once the FinalizingPanel is active it owns the
    // "Create your starter dashboard" row — showing both is a confusing
    // duplicate.
    if (store.session.dashboardFallbackPhase === 'in_progress' &&
        store.session.postAgentSteps.length === 0) {
        progressItems.push({
            label: 'Create your starter dashboard',
            activeForm: 'Creating your starter dashboard...',
            status: 'in_progress',
        });
    }
    const rawFile = extractCurrentFile(store.fileWrites, store.session.installDir);
    const lastFileRef = useRef(null);
    if (rawFile)
        lastFileRef.current = rawFile;
    const currentFile = lastFileRef.current;
    const completed = progressItems.filter((t) => t.status === 'completed').length;
    const inProgress = progressItems.filter((t) => t.status === 'in_progress').length;
    const pending = progressItems.filter((t) => t.status === 'pending').length;
    const total = progressItems.length;
    // High-water mark for the "done" counter. The agent can grow its
    // TodoWrite list mid-run, which means the count we display would
    // otherwise visibly regress: 5/5 done → 5/8 done as soon as 3 new
    // tasks land. Pinning the displayed "done" count to its maximum
    // observed value keeps the user-perceived progress monotonically
    // forward; the "to go" side is always the live pending+inProgress
    // count, so new tasks still surface clearly without rewriting the
    // history of work the user already saw finish.
    const completedHighRef = useRef(0);
    if (completed > completedHighRef.current)
        completedHighRef.current = completed;
    const completedDisplay = completedHighRef.current;
    // Coaching tiers for "spinner spins forever". The progress signal must
    // change every time the agent does ANYTHING the user can see — finishing
    // a task, emitting a new status line, or writing a file. Using just the
    // task count was a bug: post-#347 the TodoWrite list is locked at exactly
    // 5 todos, so `total` never changes after the first frame, and the tier-1
    // line ("Still working — switch to Logs") plus the tier-2 line ("This is
    // unusually slow") fired on every run at 90s/5min regardless of activity.
    // Calling a 5-minute wizard run "unusually slow" while the agent is
    // actively shipping status updates undermines trust — the wizard
    // shouldn't lie to the user about what it's doing.
    //
    // The new signal concatenates three monotonically-increasing counters:
    //   - `completedDisplay` — high-water-marked completed task count
    //   - `store.statusMessages.length` — every [STATUS] line from the agent
    //   - `store.fileWritesTotal` — every file write the agent initiates (monotonic;
    //     unlike `fileWrites.length`, it keeps climbing past the FIFO cap)
    // Any one of them ticking forward resets the coaching timer. True silence
    // (no status, no file write, no completion) for 90s now means the agent
    // really is on a long thought, and the coaching copy is honest.
    // Tiers fire at 90s (calm reassurance) and 5min (escalated suggestion).
    // RUN_COACHING_TIER_T1_S=90, RUN_COACHING_TIER_T2_S=300.
    // Include `dashboardFallbackPhase` so the coaching timer resets when the
    // post-agent fallback starts running. Otherwise the agent silence right
    // before the fallback could trip the 90s "unusually slow" tier just as
    // we're entering a known-slow path.
    const progressSignal = `${completedDisplay}|${store.statusMessages.length}|${store.fileWritesTotal}|${store.session.dashboardFallbackPhase ?? ''}`;
    const { tier: coachingTier } = useTimedCoaching({
        thresholds: [90, 300],
        progressSignal,
    });
    // Cold-start UX: until the agent finishes its first task the counter sits
    // at "0 done · N to go" with the timer climbing — every screenshot the
    // wizard's collected of users Ctrl+C-ing during Setup has been in this
    // exact gap. The agent IS working (its [STATUS] shows up at the bottom),
    // but the user's eye lands on the spinner header and the header looks
    // dead. Surface the latest status next to the counter, and after 30s of
    // zero completed tasks add an explanatory hint so a slow first response
    // doesn't read as a hung wizard.
    const lastStatus = store.statusMessages.length > 0
        ? store.statusMessages[store.statusMessages.length - 1]
        : undefined;
    const showColdStartHint = completedDisplay === 0 && total > 0 && elapsed >= 30;
    return (_jsxs(Box, { flexDirection: "row", flexGrow: 1, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Box, { gap: 1, children: [_jsx(BrailleSpinner, { color: Colors.active, frame: spinnerFrame }), _jsx(Text, { color: Colors.body, bold: true, children: total > 0
                                                    ? // Avoid "X / Y" — Y can grow as the agent adds new tasks
                                                        // mid-run, which makes the progress bar look like it's
                                                        // going backwards (6 tasks → 9 tasks). Show absolute
                                                        // counts instead so the user sees forward motion.
                                                        // `completedDisplay` is a high-water mark, so the "done"
                                                        // count never regresses if new tasks appear after the
                                                        // user already saw earlier ones finish.
                                                        pending + inProgress > 0
                                                            ? `${completedDisplay} done · ${inProgress + pending} to go`
                                                            : `${completedDisplay} tasks complete`
                                                    : 'Agent running' }), _jsxs(Text, { color: Colors.muted, children: [Icons.dot, " ", formatElapsed(elapsed)] }), _jsx(RetryStatusChip, { retryState: store.session.retryState, now: Date.now() })] }), currentFile && (_jsx(Text, { color: Colors.muted, wrap: "truncate-end", children: currentFile }))] }), completedDisplay === 0 && lastStatus && (_jsxs(Text, { color: Colors.muted, wrap: "truncate-end", children: [Icons.dot, " ", truncateStatus(lastStatus)] })), showColdStartHint && (_jsxs(Text, { color: Colors.muted, children: [Icons.dot, " Still on the agent's first response \u2014 cold start can take 30\u201360s while it loads skills and reads your project. The status above shows it's working, not stuck."] }))] }), _jsx(ProgressList, { items: progressItems, title: "Tasks" }), _jsx(FileWritesPanel, { entries: store.fileWrites, installDir: store.session.installDir, spinnerFrame: spinnerFrame }), coachingTier >= 1 && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: Colors.muted, children: coachingTier >= 2
                                ? "This is unusually slow. Press ← / → to switch to the Logs tab and see what's stuck — or Ctrl+C to cancel."
                                : "Still working — press ← / → to switch to the Logs tab and see what's happening, or Ctrl+C to cancel." }) })), _jsx(FinalizingPanel, { steps: store.session.postAgentSteps }), _jsx(InlineEventPlan, { store: store }), _jsx(ConditionalTips, { store: store })] }), showLogo && (_jsx(Box, { flexShrink: 0, marginTop: 1, marginRight: 1, children: _jsx(AnimatedAmplitudeLogo, { tick: tick }) }))] }));
};
export const RunScreen = ({ store }) => {
    useWizardStore(store);
    useScreenHints(RUN_HINTS);
    // Footer status: when a post-agent step is in_progress, drive the
    // footer line from the step's `activeForm` so the visible task and
    // the spinner footer always agree (single source of truth). Without
    // this, the agent's last pushStatus("Creating charts and dashboard…")
    // sits frozen beneath the FinalizingPanel and the message can lag
    // the actual step state. Falls back to the agent's last status when
    // no post-agent step is active (during the run, before seeding, or
    // after all steps finish).
    const activePostAgentStep = store.session.postAgentSteps.find((s) => s.status === PostAgentStepStatus.InProgress);
    const lastStatus = activePostAgentStep?.activeForm ??
        (store.statusMessages.length > 0
            ? store.statusMessages[store.statusMessages.length - 1]
            : undefined);
    const hasEvents = store.eventPlan.length > 0;
    const tabs = [
        {
            id: 'progress',
            label: 'Progress',
            component: _jsx(ProgressTab, { store: store }),
        },
        ...(hasEvents
            ? [
                {
                    id: 'events',
                    label: 'Events',
                    component: _jsx(EventPlanViewer, { events: store.eventPlan }),
                },
            ]
            : []),
        {
            id: 'logs',
            label: 'Logs',
            // Per-project log file under ~/.amplitude/wizard/runs/<hash>/log.txt.
            // Resolving from session.installDir keeps two parallel runs in
            // separate logs (vs. the previous global /tmp/amplitude-wizard.log).
            // PR 322 added getLogFilePath() with a runtime AMPLITUDE_WIZARD_LOG
            // override; per-project pathing supersedes it. If a future PR wants
            // both, getLogFile() can grow an env-override branch.
            // Scope the live tail to the current wizard session by default. The
            // per-project log file is append-only across runs (5 MB rotation),
            // so without this scope users see yesterday's runs above today's
            // startup banner. `a` toggles to show the full historical log.
            component: (_jsx(LogViewer, { filePath: getLogFile(store.session.installDir), sessionStartMs: getSessionStartMs() })),
        },
        {
            id: 'snake',
            label: 'Snake (WASD)',
            component: (_jsx(SnakeGame, { music: false, keybindings: {
                    up: ['w'],
                    down: ['s'],
                    left: ['a'],
                    right: ['d'],
                } })),
        },
    ];
    return (_jsx(TabContainer, { tabs: tabs, statusMessage: lastStatus, requestedTab: store.requestedTab, onTabConsumed: () => store.clearRequestedTab() }));
};
