import { truncateToWidth, visibleWidth } from "@threadwell/tui";
import { DISTRO_IDENTITY } from "../../../core/identity.js";
import { theme } from "../theme/theme.js";
import { getVisualTokens } from "../theme/visual-profile.js";
/**
 * Sanitize text for display in a single-line status.
 * Removes newlines, tabs, carriage returns, and other control characters.
 */
function sanitizeStatusText(text) {
    // Replace newlines, tabs, carriage returns with space, then collapse multiple spaces
    return text
        .replace(/[\r\n\t]/g, " ")
        .replace(/ +/g, " ")
        .trim();
}
/**
 * Format token counts (similar to compact UI displays)
 */
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`;
}
function getContextUsageColor(percent) {
    if (percent > 90)
        return (text) => theme.fg("error", text);
    if (percent > 70)
        return (text) => theme.fg("warning", text);
    return (text) => theme.fg("dim", text);
}
function renderContextBar(width, percent, autoCompactEnabled) {
    if (width < 2)
        return truncateToWidth("██", width, "");
    const visual = getVisualTokens();
    const clampedPercent = percent === null ? 0 : Math.max(0, Math.min(100, percent));
    const fillColor = getContextUsageColor(clampedPercent);
    const separator = visual.enhanced ? visual.glyphs.separator : "·";
    const label = ` context ${percent === null ? "?" : `${clampedPercent.toFixed(1)}%`} ${separator} auto ${autoCompactEnabled ? "on" : "off"} `;
    if (visual.enhanced) {
        const labelWidth = visibleWidth(label);
        if (width <= labelWidth)
            return truncateToWidth(theme.fg("muted", label.trim()), width, "");
        const lineWidth = width - labelWidth;
        const activeWidth = Math.floor((lineWidth * clampedPercent) / 100);
        const leftWidth = Math.floor(lineWidth / 2);
        const rightWidth = lineWidth - leftWidth;
        const activeLeftWidth = Math.min(leftWidth, activeWidth);
        const activeRightWidth = Math.max(0, activeWidth - leftWidth);
        const left = fillColor("━".repeat(activeLeftWidth)) + theme.fg("dim", "─".repeat(leftWidth - activeLeftWidth));
        const right = fillColor("━".repeat(Math.min(rightWidth, activeRightWidth))) +
            theme.fg("dim", "─".repeat(Math.max(0, rightWidth - activeRightWidth)));
        return `${left}${theme.fg("muted", label)}${right}`;
    }
    const innerWidth = Math.max(0, width - 2);
    const exactFilledWidth = (clampedPercent / 100) * innerWidth;
    const filledWidth = Math.min(innerWidth, Math.floor(exactFilledWidth));
    const partial = exactFilledWidth - filledWidth;
    const partialChar = partial <= 0 ? "" : partial < 1 / 3 ? "░" : partial < 2 / 3 ? "▒" : "▓";
    const labelStart = Math.max(0, Math.floor((width - visibleWidth(label)) / 2));
    const labelChars = [...label];
    let labelIndex = 0;
    let result = "";
    for (let column = 0; column < width; column++) {
        const labelEnd = labelStart + labelChars.length;
        if (column >= labelStart && column < labelEnd) {
            const labelChar = labelChars[labelIndex++] ?? "";
            const innerColumn = column - 1;
            const isOverFill = column > 0 && column < width - 1 && innerColumn < exactFilledWidth;
            result += isOverFill ? theme.inverse(fillColor(labelChar)) : theme.fg("dim", labelChar);
            continue;
        }
        if (column === 0 || column === width - 1) {
            result += theme.fg("dim", "█");
            continue;
        }
        const innerColumn = column - 1;
        if (innerColumn < filledWidth) {
            result += fillColor("█");
        }
        else if (innerColumn === filledWidth && partialChar) {
            result += fillColor(partialChar);
        }
        else {
            result += " ";
        }
    }
    return result;
}
/**
 * Footer component that shows pwd, token stats, and context usage.
 * Computes token/context stats from session, gets git branch and extension statuses from provider.
 */
export class FooterComponent {
    session;
    footerData;
    autoCompactEnabled = true;
    constructor(session, footerData) {
        this.session = session;
        this.footerData = footerData;
    }
    setSession(session) {
        this.session = session;
    }
    setAutoCompactEnabled(enabled) {
        this.autoCompactEnabled = enabled;
    }
    /**
     * No-op: git branch caching now handled by provider.
     * Kept for compatibility with existing call sites in interactive-mode.
     */
    invalidate() {
        // No-op: git branch is cached/invalidated by provider
    }
    /**
     * Clean up resources.
     * Git watcher cleanup now handled by provider.
     */
    dispose() {
        // Git watcher cleanup handled by provider
    }
    formatGitStatus() {
        const visual = getVisualTokens();
        const branch = this.footerData.getGitBranch();
        if (!branch)
            return undefined;
        return [theme.fg("dim", visual.glyphs.git), theme.fg("accent", branch)].join(" ");
    }
    render(width) {
        const state = this.session.state;
        const visual = getVisualTokens();
        const glyphs = visual.glyphs;
        // Calculate cumulative usage from ALL session entries (not just post-compaction messages)
        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;
            }
        }
        // Calculate context usage from session (handles compaction correctly).
        // After compaction, tokens are unknown until the next LLM response.
        const contextUsage = this.session.getContextUsage();
        const contextPercentValue = contextUsage?.percent ?? 0;
        const contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : "?";
        // Replace home directory with ~
        let pwd = this.session.sessionManager.getCwd();
        const home = process.env.HOME || process.env.USERPROFILE;
        if (home && pwd.startsWith(home)) {
            pwd = `~${pwd.slice(home.length)}`;
        }
        // Add session name if set
        const sessionName = this.session.sessionManager.getSessionName();
        if (sessionName) {
            pwd = `${pwd} • ${sessionName}`;
        }
        // Build stats line
        const statsParts = [];
        if (totalInput)
            statsParts.push(`↑${formatTokens(totalInput)}`);
        if (totalOutput)
            statsParts.push(`↓${formatTokens(totalOutput)}`);
        if (totalCacheRead)
            statsParts.push(`R${formatTokens(totalCacheRead)}`);
        if (totalCacheWrite)
            statsParts.push(`W${formatTokens(totalCacheWrite)}`);
        // Show cost with "(sub)" indicator if using OAuth subscription
        const usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;
        if (totalCost || usingSubscription) {
            const costStr = `$${totalCost.toFixed(3)}${usingSubscription ? " (sub)" : ""}`;
            statsParts.push(costStr);
        }
        const continuityEnabled = this.session.settingsManager.getContinuityEnabled();
        const continuityStatus = continuityEnabled
            ? visual.enhanced
                ? `${glyphs.continuity} continuity active`
                : "continuity active"
            : "continuity off";
        const memorySettings = this.session.settingsManager.getMemorySettings();
        const memoryStatus = memorySettings.enabled
            ? visual.enhanced
                ? `${glyphs.memory} ${memorySettings.inject ? "memory injecting" : "memory on"}`
                : memorySettings.inject
                    ? "memory injecting"
                    : "memory on"
            : undefined;
        const statsSeparator = visual.enhanced ? theme.fg("muted", ` ${glyphs.separator} `) : " ";
        let statsLeft = visual.enhanced
            ? [
                theme.fg("accent", DISTRO_IDENTITY.name),
                theme.fg(continuityEnabled ? "accent" : "dim", continuityStatus),
                ...(memoryStatus ? [theme.fg(memorySettings.inject ? "accent" : "muted", memoryStatus)] : []),
                ...statsParts.map((part) => theme.fg("dim", part)),
            ].join(statsSeparator)
            : [
                theme.fg("accent", DISTRO_IDENTITY.name),
                theme.fg("dim", continuityStatus),
                ...(memoryStatus ? [theme.fg("dim", memoryStatus)] : []),
                ...statsParts,
            ].join(" ");
        // Add model name on the right side, plus thinking level if model supports it
        const modelName = state.model?.id || "no-model";
        let statsLeftWidth = visibleWidth(statsLeft);
        // If statsLeft is too wide, truncate it
        if (statsLeftWidth > width) {
            statsLeft = truncateToWidth(statsLeft, width, "...");
            statsLeftWidth = visibleWidth(statsLeft);
        }
        // Calculate available space for padding (minimum 2 spaces between stats and model)
        const minPadding = 2;
        // Add thinking level indicator if model supports reasoning
        let rightSideWithoutProvider = modelName;
        if (state.model?.reasoning) {
            const thinkingLevel = state.thinkingLevel || "off";
            rightSideWithoutProvider =
                thinkingLevel === "off" ? `${modelName} • thinking off` : `${modelName} • ${thinkingLevel}`;
        }
        if (visual.enhanced) {
            const thinkingLevel = state.model?.reasoning
                ? state.thinkingLevel || "off"
                : undefined;
            const thinkingColor = thinkingLevel ? theme.getThinkingBorderColor(thinkingLevel) : undefined;
            rightSideWithoutProvider = [
                `${theme.fg("dim", glyphs.model)} ${theme.fg("muted", modelName)}`,
                ...(thinkingLevel && thinkingColor ? [thinkingColor(thinkingLevel)] : []),
            ].join(statsSeparator);
        }
        // Prepend the provider in parentheses if there are multiple providers and there's enough room
        let rightSide = rightSideWithoutProvider;
        if (this.footerData.getAvailableProviderCount() > 1 && state.model) {
            rightSide = `(${state.model.provider}) ${rightSideWithoutProvider}`;
            if (statsLeftWidth + minPadding + visibleWidth(rightSide) > width) {
                // Too wide, fall back
                rightSide = rightSideWithoutProvider;
            }
        }
        const rightSideWidth = visibleWidth(rightSide);
        const totalNeeded = statsLeftWidth + minPadding + rightSideWidth;
        let statsLine;
        if (totalNeeded <= width) {
            // Both fit - add padding to right-align model
            const padding = " ".repeat(width - statsLeftWidth - rightSideWidth);
            statsLine = statsLeft + padding + rightSide;
        }
        else {
            // Need to truncate right side
            const availableForRight = width - statsLeftWidth - minPadding;
            if (availableForRight > 0) {
                const truncatedRight = truncateToWidth(rightSide, availableForRight, "");
                const truncatedRightWidth = visibleWidth(truncatedRight);
                const padding = " ".repeat(Math.max(0, width - statsLeftWidth - truncatedRightWidth));
                statsLine = statsLeft + padding + truncatedRight;
            }
            else {
                // Not enough space for right side at all
                statsLine = statsLeft;
            }
        }
        // Apply dim to each part separately. statsLeft may contain color codes (for context %)
        // that end with a reset, which would clear an outer dim wrapper. So we dim the parts
        // before and after the colored section independently.
        const dimStatsLeft = visual.enhanced ? statsLeft : theme.fg("dim", statsLeft);
        const remainder = statsLine.slice(statsLeft.length); // padding + rightSide
        const dimRemainder = visual.enhanced ? remainder : theme.fg("dim", remainder);
        const folder = glyphs.folder;
        const gitStatus = this.formatGitStatus();
        const pwdText = `${theme.fg("dim", folder)} ${theme.fg("dim", pwd)}${gitStatus ? `${theme.fg("muted", ` ${glyphs.separator} `)}${gitStatus}` : ""}`;
        const pwdLine = truncateToWidth(pwdText, width, theme.fg("dim", "..."));
        const contextBarLine = renderContextBar(width, contextPercent === "?" ? null : contextPercentValue, this.autoCompactEnabled);
        const lines = [pwdLine, dimStatsLeft + dimRemainder, contextBarLine];
        // Add extension statuses on a single line, sorted by key alphabetically
        const extensionStatuses = this.footerData.getExtensionStatuses();
        if (extensionStatuses.size > 0) {
            const sortedStatuses = Array.from(extensionStatuses.entries())
                .sort(([a], [b]) => a.localeCompare(b))
                .map(([, text]) => sanitizeStatusText(text));
            const statusLine = sortedStatuses.join(" ");
            // Truncate to terminal width with dim ellipsis for consistency with footer style
            lines.push(truncateToWidth(statusLine, width, theme.fg("dim", "...")));
        }
        return lines;
    }
}
//# sourceMappingURL=footer.js.map