/**
 * Data provider for the TUI footer bar.
 *
 * Aggregates git branch, extension statuses, credit balance, sandbox state,
 * and current agent mode into a single observable source. The footer
 * component reads from this; extensions write via setExtensionStatus().
 *
 * Git branch is watched via fs.watch on the .git/HEAD directory so the
 * footer updates immediately on checkout / branch switch.
 */
import { existsSync, readFileSync, statSync, watch } from 'fs';
import { dirname, join, resolve } from 'path';
import { getCurrentMode } from '../mode-names.js';
/**
 * Find the git HEAD path by walking up from cwd.
 * Handles both regular git repos (.git is a directory) and worktrees (.git is a file).
 */
function findGitHeadPath() {
    let dir = process.cwd();
    while (true) {
        const gitPath = join(dir, '.git');
        if (existsSync(gitPath)) {
            try {
                const stat = statSync(gitPath);
                if (stat.isFile()) {
                    const content = readFileSync(gitPath, 'utf8').trim();
                    if (content.startsWith('gitdir: ')) {
                        const gitDir = content.slice(8);
                        const headPath = resolve(dir, gitDir, 'HEAD');
                        if (existsSync(headPath))
                            return headPath;
                    }
                }
                else if (stat.isDirectory()) {
                    const headPath = join(gitPath, 'HEAD');
                    if (existsSync(headPath))
                        return headPath;
                }
            }
            catch {
                return null;
            }
        }
        const parent = dirname(dir);
        if (parent === dir)
            return null;
        dir = parent;
    }
}
/**
 * Provides git branch and extension statuses - data not otherwise accessible to extensions.
 * Token stats, model info available via ctx.sessionManager and ctx.model.
 */
export class FooterDataProvider {
    extensionStatuses = new Map();
    cachedBranch = undefined;
    gitWatcher = null;
    branchChangeCallbacks = new Set();
    availableProviderCount = 0;
    constructor() {
        this.setupGitWatcher();
    }
    /** Current git branch, null if not in repo, "detached" if detached HEAD */
    getGitBranch() {
        if (this.cachedBranch !== undefined)
            return this.cachedBranch;
        try {
            const gitHeadPath = findGitHeadPath();
            if (!gitHeadPath) {
                this.cachedBranch = null;
                return null;
            }
            const content = readFileSync(gitHeadPath, 'utf8').trim();
            this.cachedBranch = content.startsWith('ref: refs/heads/') ? content.slice(16) : 'detached';
        }
        catch {
            this.cachedBranch = null;
        }
        return this.cachedBranch;
    }
    /** Extension status texts set via ctx.ui.setStatus() */
    getExtensionStatuses() {
        return this.extensionStatuses;
    }
    /** Subscribe to git branch changes. Returns unsubscribe function. */
    onBranchChange(callback) {
        this.branchChangeCallbacks.add(callback);
        return () => this.branchChangeCallbacks.delete(callback);
    }
    /** Internal: set extension status */
    setExtensionStatus(key, text) {
        if (text === undefined) {
            this.extensionStatuses.delete(key);
        }
        else {
            this.extensionStatuses.set(key, text);
        }
    }
    /** Internal: clear extension statuses */
    clearExtensionStatuses() {
        this.extensionStatuses.clear();
    }
    /** Number of unique providers with available models (for footer display) */
    getAvailableProviderCount() {
        return this.availableProviderCount;
    }
    /** Internal: update available provider count */
    setAvailableProviderCount(count) {
        this.availableProviderCount = count;
    }
    /** Shortcut credit balance (null = not fetched or not using shortcut) */
    creditBalance = null;
    /** Initial credit balance captured at session start, used to compute credits used */
    initialCreditBalance = null;
    /** True when the active model uses credits (suppresses $cost even when balance fetch fails) */
    _usesCredits = false;
    _sandboxActive = false;
    getCreditBalance() {
        return this.creditBalance;
    }
    /** Credits consumed this session (initial - current). Returns 0 if not yet known. */
    getCreditsUsed() {
        if (this.initialCreditBalance === null || !this.creditBalance)
            return 0;
        return Math.max(0, this.initialCreditBalance - this.creditBalance.creditsRemaining);
    }
    setCreditBalance(balance) {
        // Capture the first non-null balance as the session baseline
        if (balance && this.initialCreditBalance === null) {
            this.initialCreditBalance = balance.creditsRemaining;
        }
        this.creditBalance = balance;
    }
    get usesCredits() {
        return this._usesCredits;
    }
    setUsesCredits(value) {
        this._usesCredits = value;
    }
    get sandboxActive() {
        return this._sandboxActive;
    }
    setSandboxActive(value) {
        this._sandboxActive = value;
    }
    /** Reads directly from the canonical shared mode state — no sync needed. */
    get currentMode() {
        return getCurrentMode();
    }
    /** Internal: cleanup */
    dispose() {
        if (this.gitWatcher) {
            this.gitWatcher.close();
            this.gitWatcher = null;
        }
        this.branchChangeCallbacks.clear();
    }
    setupGitWatcher() {
        if (this.gitWatcher) {
            this.gitWatcher.close();
            this.gitWatcher = null;
        }
        const gitHeadPath = findGitHeadPath();
        if (!gitHeadPath)
            return;
        // Watch the directory containing HEAD, not HEAD itself.
        // Git uses atomic writes (write temp, rename over HEAD), which changes the inode.
        // fs.watch on a file stops working after the inode changes.
        const gitDir = dirname(gitHeadPath);
        try {
            this.gitWatcher = watch(gitDir, (_eventType, filename) => {
                if (filename === 'HEAD') {
                    this.cachedBranch = undefined;
                    for (const cb of this.branchChangeCallbacks)
                        cb();
                }
            });
        }
        catch {
            // Silently fail if we can't watch
        }
    }
}
//# sourceMappingURL=footer-data-provider.js.map