/**
 * Fit estimation for the AI analysis result step in the onboarding TUI.
 *
 * The on-failure AI flow can return a multi-screen markdown diagnosis. If
 * that text doesn't fit in the user's current terminal viewport we MUST
 * route it through the scrollable `FullscreenAiViewer` — otherwise the
 * earlier lines scroll out of view and the onboarding wizard ends up in
 * an unreadable state.
 *
 * The estimator deliberately errs on the side of "doesn't fit": a
 * false-positive scroll is fine (just one more keystroke for the user),
 * but a false-negative inline render is bad UX (text disappears off the
 * top of the screen).
 */
export declare const AI_RESULT_CHROME_ROWS = 10;
export declare const AI_RUNNING_CHROME_ROWS = 10;
export interface AiPreviewTail {
    /** Lines to render (blank-padded to a constant rendered height). */
    rows: string[];
    /** Logical lines scrolled off the top (0 ⇒ the marker row renders blank). */
    hidden: number;
}
/**
 * Pick the visible tail of the live streaming analysis preview for the
 * CURRENT terminal size.
 *
 * Wrap-aware: each logical line is budgeted as the rows it will actually
 * occupy at `terminalCols` (via `estimateRenderedRows`), so the running frame
 * can never grow past the viewport. No artificial padding: streamed text only
 * appends, so the rendered height grows monotonically on its own — the frame
 * starts compact (matching the other wizard steps) and grows downward, and is
 * capped at the budget so it never overflows the full-height shell frame.
 */
export declare function pickAiPreviewTail(text: string, terminalRows: number, terminalCols: number): AiPreviewTail;
/** Strip ANSI SGR escape codes so length matches what the user actually sees. */
export declare function stripAnsi(text: string): string;
/**
 * Estimate how many terminal rows a multi-line, possibly ANSI-styled string
 * will occupy when rendered by Ink at the given column width.
 *
 * Each logical line (split on '\n') becomes `ceil(visibleLen / cols)` rows,
 * with a floor of 1 to account for empty lines that still consume a row.
 */
export declare function estimateRenderedRows(text: string, terminalCols: number): number;
/**
 * Decide whether the AI analysis text should be routed through the
 * scrollable fullscreen viewer. Conservative — prefers true (scroll) when
 * the estimate is close to the available row budget.
 *
 * @param text         The AI analysis markdown (already rendered to ANSI).
 * @param terminalRows Total terminal rows from `useStdout().stdout?.rows`.
 * @param terminalCols Total terminal cols from `useStdout().stdout?.columns`.
 * @param chromeRows   Reserved rows for the surrounding wizard chrome.
 *                     Defaults to `AI_RESULT_CHROME_ROWS`.
 */
export declare function isAiAnalysisTooTall(text: string, terminalRows: number, terminalCols: number, chromeRows?: number): boolean;
export type AiResultStep = 'ai-analysis-result' | 'ai-analysis-result-scroll';
/**
 * Decide which AI-result step should be active for the CURRENT terminal size.
 *
 * Routing is BIDIRECTIONAL and driven by the single `isAiAnalysisTooTall`
 * predicate, so it settles deterministically at any size — at a given size
 * exactly one outcome is stable, so it can't oscillate:
 *   - inline + now too tall (terminal shrank)  → scroll
 *   - scroll + now fits      (terminal grew)    → inline   ← the missing case
 *
 * Before, only the inline→scroll direction existed: once the viewer opened
 * (e.g. after shrinking), growing the terminal never returned to the inline
 * render — the user was stuck in the scroll viewer showing "all N lines" with
 * empty space.
 *
 * `viewedFull` (the user manually dismissed the viewer with Esc/Enter) pins the
 * inline step so a later resize can't shove a dismissed analysis back into the
 * viewer. It only gates the inline→scroll direction; leaving the viewer when it
 * fits is always allowed.
 *
 * @returns the step to switch to, or `null` when the current step is already
 *   correct (so the caller can skip a no-op `setStep`).
 */
export declare function resolveAiResultRoute(params: {
    current: AiResultStep;
    text: string | null;
    viewedFull: boolean;
    terminalRows: number;
    terminalCols: number;
}): AiResultStep | null;
/**
 * Pick the slice of `lines` starting at `scrollOffset` that PACKS the
 * `viewportRows` rendered rows of a terminal `terminalCols` wide.
 *
 * Packs lines until the cumulative wrapped row count reaches or exceeds
 * `viewportRows`, INCLUDING the line that crosses the boundary. That last line
 * may render past the viewport; the viewer clips it with `overflow: hidden` so
 * the visible area is always FULL of text when more lines remain. (Stopping
 * before the boundary line — the old behaviour — left the unused rows as an
 * empty gap when a long line couldn't fully fit.)
 *
 * Always returns at least one line if the input is non-empty and the
 * `scrollOffset` is in-range — even if that line wraps to more rows than the
 * viewport.
 */
export declare function pickVisibleLines(lines: string[], scrollOffset: number, viewportRows: number, terminalCols: number): string[];
/**
 * Compute the largest `scrollOffset` that still keeps content visible at the
 * bottom of the viewport — i.e. the offset where the LAST line is rendered
 * within the viewport. Walks backwards from the end, packing as many tail
 * lines as fit (accounting for wrap), and returns the offset of the first
 * fully-visible tail line.
 */
export declare function computeMaxScrollOffset(lines: string[], viewportRows: number, terminalCols: number): number;
