/**
 * Session footer — renders token stats, context usage, model info, and mode label.
 *
 * Shortcut credit users see a gamified footer with tiered coloring on context %
 * and query counts. Non-credit users see cumulative cost and standard thresholds.
 */
import stripAnsi from 'strip-ansi';
import { APPROVAL_MODE } from '../../../app/settings-manager.js';
import { MODE } from '../../../mode-names.js';
import { getEditorKeybindings, truncateToWidth, visibleWidth } from '../../../tui/index.js';
import { theme } from '../../theme/runtime.js';
/**
 * Sanitize text for display in a single-line status.
 * Removes newlines, tabs, carriage returns, and other control characters.
 */
function sanitizeStatusText(text) {
    return text
        .replace(/[\r\n\t]/g, ' ')
        .replace(/ +/g, ' ')
        .trim();
}
/**
 * Format token counts (similar to web-ui)
 */
function formatTokens(count) {
    if (count < 1000)
        return count.toString();
    if (count < 10000)
        return `${(count / 1000).toFixed(1)}k`;
    if (count < 1000000)
        return `${Math.round(count / 1000)}k`;
    if (count < 10000000)
        return `${(count / 1000000).toFixed(1)}M`;
    return `${Math.round(count / 1000000)}M`;
}
/** Format a credits-used count with locale-aware grouping for the footer. */
function formatCreditsUsed(count) {
    return `${count.toLocaleString(undefined, { maximumFractionDigits: 2 })} cr`;
}
/** Compose a left/right justified footer row, truncating to the given width when needed. */
function renderFooterRow(left, right, width) {
    const minPadding = left && right ? 2 : 0;
    const leftWidth = visibleWidth(left);
    const rightWidth = visibleWidth(right);
    if (!right) {
        return leftWidth > width ? truncateToWidth(left, width, '...') : left;
    }
    if (!left) {
        if (rightWidth >= width) {
            return truncateToWidth(right, width, '...');
        }
        return ' '.repeat(width - rightWidth) + right;
    }
    if (leftWidth + minPadding + rightWidth <= width) {
        return left + ' '.repeat(width - leftWidth - rightWidth) + right;
    }
    const maxLeftWidth = Math.max(0, width - rightWidth - minPadding);
    if (maxLeftWidth > 0) {
        const truncatedLeft = truncateToWidth(left, maxLeftWidth, '...');
        const truncatedLeftWidth = visibleWidth(truncatedLeft);
        if (truncatedLeftWidth + minPadding + rightWidth <= width) {
            return truncatedLeft + ' '.repeat(width - truncatedLeftWidth - rightWidth) + right;
        }
    }
    return truncateToWidth(left, width, '...');
}
/** Render an extension status line with the appropriate color and width truncation. */
function renderExtensionStatus(key, text, width) {
    const status = sanitizeStatusText(text);
    const color = getExtensionStatusColor(key, status);
    return truncateToWidth(applyFooterStatusColor(color, status), width, applyFooterStatusColor(color, '...'));
}
const ANSI = {
    turquoise: '\x1b[38;2;96;225;255m',
    reset: '\x1b[0m'
};
function renderFastModeToggle(enabled, available) {
    const shortcutHint = theme.fg('dim', ' (alt+f)');
    if (!available) {
        return `${theme.fg('dim', 'fast: off')}${shortcutHint}`;
    }
    if (enabled) {
        return `${theme.fg('success', '↯ fast: on')}${shortcutHint}`;
    }
    return `${theme.fg('text', 'fast: off')}${shortcutHint}`;
}
function abbreviateFooterModelName(model) {
    if (!model)
        return 'no-model';
    const candidates = [model.id, model.name].filter((value) => !!value);
    for (const candidate of candidates) {
        const normalized = candidate.toLowerCase();
        const claudeMatch = normalized.match(/\b(?:claude[-\s]*)?(opus|sonnet)[-\s]*(\d)[-\s.]*(\d)\b/);
        if (claudeMatch) {
            return `${claudeMatch[1]}${claudeMatch[2]}${claudeMatch[3]}`;
        }
        const gptMatch = normalized.match(/\bgpt[-\s]*(\d)(?:[-\s.]*(\d))?/);
        if (gptMatch) {
            return `gpt${gptMatch[1]}${gptMatch[2] ?? ''}`;
        }
    }
    return model.id || model.name || 'no-model';
}
function appendRightPinnedSuffix(base, suffix, maxWidth) {
    const full = `${base}${suffix}`;
    if (visibleWidth(full) <= maxWidth)
        return full;
    const suffixWidth = visibleWidth(suffix);
    if (suffixWidth >= maxWidth)
        return truncateToWidth(suffix, maxWidth, '...');
    return `${truncateToWidth(base, maxWidth - suffixWidth, '...')}${suffix}`;
}
/** Pick the footer color used for an extension status line based on key and content. */
function getExtensionStatusColor(key, status) {
    if (key === 'agents')
        return 'turquoise';
    if (key !== 'proposals')
        return 'dim';
    return status.startsWith('Skill proposal: error') ? 'error' : 'warning';
}
/** Wrap status text in the appropriate ANSI sequences for the chosen footer color. */
function applyFooterStatusColor(color, text) {
    if (color === 'turquoise')
        return `${ANSI.turquoise}${text}\x1b[39m`;
    return theme.fg(color, text);
}
/** Resolve a footer highlight color to its raw ANSI foreground sequence. */
function getFooterHighlightForegroundAnsi(color) {
    if (color === 'turquoise')
        return ANSI.turquoise;
    return theme.getFgAnsi(color);
}
/** Map a status color to the highlight color used when its action row is focused. */
function getActionHighlightColor(color) {
    if (color === 'turquoise' || color === 'error')
        return color;
    return 'warning';
}
/** Translate an ANSI foreground escape into the equivalent background escape. */
function foregroundAnsiToBackgroundAnsi(ansi) {
    return ansi
        .replace(/\x1b\[38;/g, '\x1b[48;')
        .replace(/\x1b\[3([0-7])m/g, '\x1b[4$1m')
        .replace(/\x1b\[9([0-7])m/g, '\x1b[10$1m');
}
/** Count user-role messages in the session, used as the "queries" stat in the footer. */
function countUserQueries(session) {
    let count = 0;
    for (const entry of session.sessionManager.getEntries()) {
        if (entry.type === 'message' && entry.message.role === 'user') {
            count++;
        }
    }
    return count;
}
export class FooterComponent {
    session;
    footerData;
    actions;
    focused = false;
    selectedActionIndex = 0;
    actionRows = [];
    /** Construct the footer with its session client, data provider, and optional action handlers. */
    constructor(session, footerData, actions = {}) {
        this.session = session;
        this.footerData = footerData;
        this.actions = actions;
    }
    /** Replace the footer's action callbacks (used after rebinding hosts on session swap). */
    setActionHandlers(actions) {
        this.actions = actions;
    }
    /** No-op; footer recomputes its layout each render so it has nothing to invalidate. */
    invalidate() { }
    /** No-op; the footer holds no resources that need teardown. */
    dispose() { }
    /** Move keyboard focus to the first available action row, returning false when there are none. */
    focusFirstAction() {
        if (this.actionRows.length === 0)
            return false;
        this.selectedActionIndex = 0;
        return true;
    }
    /** Handle keyboard input while the footer is focused (move selection or activate an action). */
    handleInput(data) {
        const kb = getEditorKeybindings();
        if (kb.matches(data, 'cursorUp')) {
            if (this.selectedActionIndex <= 0) {
                this.actions.onExitFocus?.();
                return;
            }
            this.selectedActionIndex -= 1;
            return;
        }
        if (kb.matches(data, 'cursorDown')) {
            if (this.selectedActionIndex >= this.actionRows.length - 1) {
                this.actions.onExitFocus?.();
                return;
            }
            this.selectedActionIndex += 1;
            return;
        }
        if (kb.matches(data, 'selectCancel')) {
            this.actions.onExitFocus?.();
            return;
        }
        if (kb.matches(data, 'selectConfirm')) {
            void this.activateSelectedAction();
        }
    }
    /** Invoke the action handler bound to the currently selected footer row. */
    async activateSelectedAction() {
        const row = this.actionRows[this.selectedActionIndex];
        if (!row)
            return;
        switch (row.key) {
            case 'approval':
                this.actions.onToggleAutoApprove?.();
                break;
            case 'runtime-permissions':
                await this.actions.onToggleRuntimePermissionBypass?.();
                break;
            case 'agents':
                this.actions.onShowAgents?.();
                break;
            case 'proposals':
                await this.actions.onShowProposals?.();
                break;
        }
    }
    /** Record metadata for the most recently appended footer line so it can be activated as an action. */
    markActionLine(lines, key, highlightColor, highlightText, focusHint) {
        this.actionRows.push({
            key,
            lineIndex: lines.length - 1,
            highlightColor,
            highlightText,
            focusHint
        });
    }
    /** Apply selected-action highlighting to a footer line when it matches the currently focused row. */
    maybeHighlightActionLine(line, row, width) {
        if (!this.focused)
            return line;
        const actionIndex = this.actionRows.findIndex((candidate) => candidate.lineIndex === row.lineIndex);
        if (actionIndex !== this.selectedActionIndex)
            return line;
        const rawLine = stripAnsi(line);
        const target = row.highlightText;
        const targetIndex = rawLine.indexOf(target);
        if (targetIndex < 0)
            return line;
        const before = rawLine.slice(0, targetIndex);
        const after = rawLine.slice(targetIndex + target.length);
        const hint = row.focusHint ? theme.fg('dim', `  ${row.focusHint}`) : '';
        const bg = foregroundAnsiToBackgroundAnsi(getFooterHighlightForegroundAnsi(row.highlightColor));
        const highlightedTarget = `${bg}\x1b[30m${target}\x1b[39m\x1b[49m`;
        return truncateToWidth(`${before}${highlightedTarget}${hint}${after}`, width, '...');
    }
    /** Compose all footer lines (stats, model, mode, status rows) for the current width. */
    render(width) {
        const state = this.session.state;
        let totalInput = 0;
        let totalOutput = 0;
        let totalCacheRead = 0;
        let totalCacheWrite = 0;
        let totalCost = 0;
        for (const entry of this.session.sessionManager.getEntries()) {
            if (entry.type === 'message' && entry.message.role === 'assistant') {
                totalInput += entry.message.usage.input;
                totalOutput += entry.message.usage.output;
                totalCacheRead += entry.message.usage.cacheRead;
                totalCacheWrite += entry.message.usage.cacheWrite;
                totalCost += entry.message.usage.cost.total;
            }
        }
        const contextUsage = this.session.getContextUsage();
        const contextPercentValue = contextUsage?.percent ?? 0;
        const contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : '?';
        const queryCount = countUserQueries(this.session);
        const sessionName = this.session.sessionManager.getSessionName();
        const creditBalance = this.footerData.getCreditBalance();
        const isShortcutCredits = !!creditBalance;
        const showSessionCredits = this.footerData.usesCredits && creditBalance !== null && !creditBalance.hasTeamBilling;
        const infoLine = sessionName ? theme.fg('dim', sessionName) : '';
        const statsParts = [];
        if (totalInput)
            statsParts.push(`↑${formatTokens(totalInput)}`);
        if (totalOutput)
            statsParts.push(`↓${formatTokens(totalOutput)}`);
        if (this.session.settingsManager.getShowCacheTokens()) {
            if (totalCacheRead)
                statsParts.push(`R${formatTokens(totalCacheRead)}`);
            if (totalCacheWrite)
                statsParts.push(`W${formatTokens(totalCacheWrite)}`);
        }
        if (!isShortcutCredits && !this.footerData.usesCredits) {
            const usingSubscription = state.model
                ? this.session.modelRegistry.isUsingOAuth(state.model)
                : false;
            if (totalCost || usingSubscription) {
                statsParts.push(`$${totalCost.toFixed(3)}${usingSubscription ? ' (sub)' : ''}`);
            }
        }
        const statsLine = statsParts.join('  ');
        let contextLine;
        const contextPercentDisplay = contextPercent === '?' ? '? context' : `${contextPercent}% context`;
        if (isShortcutCredits) {
            if (contextPercentValue >= 75) {
                contextLine = theme.fg('error', contextPercentDisplay);
            }
            else if (contextPercentValue >= 50) {
                contextLine = theme.fg('warning', contextPercentDisplay);
            }
            else {
                contextLine = contextPercentDisplay;
            }
        }
        else {
            const display = `${contextPercentDisplay} (auto)`;
            if (contextPercentValue > 90) {
                contextLine = theme.fg('error', display);
            }
            else if (contextPercentValue > 70) {
                contextLine = theme.fg('warning', display);
            }
            else {
                contextLine = display;
            }
        }
        if (isShortcutCredits) {
            const queriesText = `${queryCount} queries`;
            let styledQueries = queriesText;
            if (queryCount > 15) {
                styledQueries = theme.fg('error', queriesText);
            }
            else if (queryCount > 10) {
                styledQueries = theme.fg('warning', queriesText);
            }
            contextLine = `${contextLine}  ${styledQueries}`;
        }
        if (!isShortcutCredits) {
            contextLine = theme.fg('dim', contextLine);
        }
        const topRightParts = [];
        if (showSessionCredits) {
            topRightParts.push(formatCreditsUsed(this.footerData.getCreditsUsed()));
        }
        else if (statsLine) {
            topRightParts.push(isShortcutCredits ? statsLine : theme.fg('dim', statsLine));
        }
        topRightParts.push(contextLine);
        const topRight = topRightParts.join('  ');
        const currentMode = this.footerData.currentMode;
        let modeLabel = '';
        let compactModeLabel = '';
        if (currentMode) {
            let styledMode;
            switch (currentMode) {
                case MODE.ACTION:
                    styledMode = theme.fg('success', currentMode);
                    break;
                case MODE.PLAN:
                    styledMode = `${ANSI.turquoise}${currentMode}${ANSI.reset}`;
                    break;
                case MODE.ASK:
                    styledMode = theme.fg('warning', currentMode);
                    break;
                default:
                    styledMode = currentMode;
            }
            modeLabel = `${styledMode}${theme.fg('dim', ' (alt+m)')} • `;
            compactModeLabel = `${styledMode} • `;
        }
        const modelId = abbreviateFooterModelName(state.model);
        let modelDetails = '';
        if (state.model?.reasoning) {
            modelDetails += ` • 🧠 ${state.thinkingLevel || 'off'}${theme.fg('dim', ' (alt+t)')}`;
        }
        if (this.footerData.sandboxActive) {
            modelDetails += ' • sandbox';
        }
        const renderBaseModelLine = (label) => `${label}${modelId}${theme.fg('dim', ' (alt+p)')}${modelDetails}`;
        let baseModelLine = renderBaseModelLine(modeLabel);
        const autoApprove = this.session.approvalState?.getAutoApprove?.() ??
            this.session.settingsManager.getAutoApproveDefault?.() ??
            false;
        const approvalLine = autoApprove
            ? theme.fg('success', `\u23F5\u23F5 ${APPROVAL_MODE.AUTO}`) +
                theme.fg('dim', '  (shift+tab to toggle)')
            : theme.fg('dim', `\u23F8 ${APPROVAL_MODE.ASK}  (shift+tab to toggle)`);
        const runtimeBypassEnabled = this.session.settingsManager.getDangerouslySkipRuntimePermissions?.() ?? false;
        const runtimePermissionLine = runtimeBypassEnabled
            ? theme.fg('error', '● Bypass file/shell permissions') +
                theme.fg('dim', '  (alt+r to restore)')
            : theme.fg('dim', '⏸ Enforce file/shell permissions  (alt+r to bypass)');
        const maxModelLineWidth = Math.max(0, width - visibleWidth(runtimePermissionLine) - 2);
        const fastModeAvailable = this.session.isFastModeAvailable;
        const fastModeSuffix = `${theme.fg('dim', ' • ')}${renderFastModeToggle(this.session.isFastModeEnabled, fastModeAvailable)}`;
        if (compactModeLabel && visibleWidth(`${baseModelLine}${fastModeSuffix}`) > maxModelLineWidth) {
            baseModelLine = renderBaseModelLine(compactModeLabel);
        }
        if (this.footerData.getAvailableProviderCount() > 1 && state.model) {
            const withProvider = `(${state.model.provider}) ${baseModelLine}`;
            if (visibleWidth(`${withProvider}${fastModeSuffix}`) <= maxModelLineWidth) {
                baseModelLine = withProvider;
            }
        }
        const modelLine = theme.fg('dim', appendRightPinnedSuffix(baseModelLine, fastModeSuffix, maxModelLineWidth || width));
        const extensionStatuses = this.footerData.getExtensionStatuses();
        const separator = theme.fg('dim', '─'.repeat(width));
        const lines = [separator];
        this.actionRows = [];
        const INLINE_STATUS_KEYS = new Set(['loop']);
        const PRIORITY_STATUS_KEYS = new Set(['agents', 'proposals']);
        const priorityStatuses = Array.from(extensionStatuses.entries())
            .filter(([key]) => PRIORITY_STATUS_KEYS.has(key))
            .sort(([a], [b]) => a.localeCompare(b));
        for (const [key, status] of priorityStatuses) {
            lines.push(renderExtensionStatus(key, status, width));
            const statusColor = getExtensionStatusColor(key, sanitizeStatusText(status));
            this.markActionLine(lines, key, getActionHighlightColor(statusColor), sanitizeStatusText(status).split(' - ')[0] ?? sanitizeStatusText(status), 'enter to view');
        }
        if (infoLine) {
            lines.push(infoLine);
        }
        lines.push(renderFooterRow(approvalLine, topRight, width));
        this.markActionLine(lines, 'approval', autoApprove ? 'success' : 'warning', autoApprove ? APPROVAL_MODE.AUTO : APPROVAL_MODE.ASK, 'enter to toggle');
        lines.push(renderFooterRow(runtimePermissionLine, modelLine, width));
        this.markActionLine(lines, 'runtime-permissions', runtimeBypassEnabled ? 'error' : 'warning', runtimeBypassEnabled ? 'Bypass file/shell permissions' : 'Enforce file/shell permissions', 'enter to toggle');
        if (extensionStatuses.size > 0) {
            const sortedStatuses = Array.from(extensionStatuses.entries())
                .filter(([key]) => !INLINE_STATUS_KEYS.has(key) && !PRIORITY_STATUS_KEYS.has(key))
                .sort(([a], [b]) => a.localeCompare(b));
            for (const [key, status] of sortedStatuses) {
                lines.push(renderExtensionStatus(key, status, width));
            }
        }
        if (this.selectedActionIndex >= this.actionRows.length) {
            this.selectedActionIndex = Math.max(0, this.actionRows.length - 1);
        }
        for (const row of this.actionRows) {
            lines[row.lineIndex] = this.maybeHighlightActionLine(lines[row.lineIndex] ?? '', row, width);
        }
        return lines;
    }
}
//# sourceMappingURL=footer.js.map