import { getModelContextLimit, getSessionContextLimit } from '../models/index.js';
import { createTokenizer } from '../tokenization/index.js';
import { calculateToolDefinitionsTokensFromDefs } from '../usage/calculator.js';
import { compressionBackup } from './compression-backup.js';
import { summariseWithLLM } from './llm-summariser.js';
import { compressMessages } from './message-compression.js';
import { createSessionOverride } from './session-override.js';
// Session overrides for auto-compact. `threshold` is clamped to 50–95.
const autoCompactSession = {
    enabled: createSessionOverride(),
    threshold: createSessionOverride(value => value !== null ? Math.max(50, Math.min(95, value)) : null),
    mode: createSessionOverride(),
    strategy: createSessionOverride(),
};
// Legacy object-style accessor (read by useAppHandlers + performAutoCompact).
export const autoCompactSessionOverrides = new Proxy({}, {
    get(_target, prop) {
        if (prop === 'enabled')
            return autoCompactSession.enabled.get();
        if (prop === 'threshold')
            return autoCompactSession.threshold.get();
        if (prop === 'mode')
            return autoCompactSession.mode.get();
        if (prop === 'strategy')
            return autoCompactSession.strategy.get();
        return undefined;
    },
    set(_target, prop, value) {
        if (prop === 'enabled')
            autoCompactSession.enabled.set(value);
        else if (prop === 'threshold')
            autoCompactSession.threshold.set(value);
        else if (prop === 'mode')
            autoCompactSession.mode.set(value);
        else if (prop === 'strategy')
            autoCompactSession.strategy.set(value);
        return true;
    },
});
/**
 * Perform auto-compact on messages (async)
 * Returns compressed messages if compression was performed, null otherwise.
 *
 * When `strategy` resolves to 'llm' and a client is provided, the compressible
 * segment is replaced by a single synthetic message generated by the model.
 * Falls back to the mechanical path on any LLM error.
 *
 * `nativeTools` are the tool definitions sent to the model out-of-band (native
 * tool calling). They occupy context but live outside the message list, so the
 * trigger would under-count without them and fire late — mirror the context
 * indicator by adding their token cost to the gate. Pass `{}`/undefined under
 * XML/JSON fallback, where the definitions are already inside `systemMessage`.
 */
export async function performAutoCompact(messages, systemMessage, provider, model, config, onNotify, client, nativeTools) {
    // Check if auto compact is enabled
    const enabled = autoCompactSessionOverrides.enabled !== null
        ? autoCompactSessionOverrides.enabled
        : config.enabled;
    if (!enabled) {
        return null;
    }
    // Get threshold
    const threshold = autoCompactSessionOverrides.threshold !== null
        ? autoCompactSessionOverrides.threshold
        : config.threshold;
    // Get mode
    const mode = autoCompactSessionOverrides.mode !== null
        ? autoCompactSessionOverrides.mode
        : config.mode;
    // Get strategy — session override wins, then config, then 'llm' default
    const strategy = autoCompactSessionOverrides.strategy !== null
        ? autoCompactSessionOverrides.strategy
        : (config.strategy ?? 'llm');
    // Get context limit: session override takes priority
    let contextLimit;
    const sessionLimit = getSessionContextLimit();
    if (sessionLimit !== null) {
        contextLimit = sessionLimit;
    }
    else {
        try {
            contextLimit = await getModelContextLimit(model, {
                providerConfig: client?.getProviderConfig(),
            });
        }
        catch {
            return null;
        }
    }
    if (!contextLimit) {
        return null;
    }
    // Create tokenizer
    let tokenizer;
    try {
        tokenizer = createTokenizer(provider, model);
    }
    catch {
        return null;
    }
    try {
        // Calculate current token count
        const allMessages = [systemMessage, ...messages];
        let messageTokens = 0;
        for (const msg of allMessages) {
            messageTokens += tokenizer.countTokens(msg);
        }
        // Native tool definitions occupy context but aren't in the message list
        // (under XML/JSON fallback callers pass nothing because the definitions are
        // already baked into systemMessage). Adding them keeps the trigger aligned
        // with the displayed ctx% so it doesn't fire late on tool-heavy setups.
        const toolDefTokens = nativeTools
            ? calculateToolDefinitionsTokensFromDefs(nativeTools, tokenizer)
            : 0;
        const totalTokens = messageTokens + toolDefTokens;
        // Check if threshold is reached
        const usagePercentage = (totalTokens / contextLimit) * 100;
        if (usagePercentage < threshold) {
            return null;
        }
        compressionBackup.storeBackup(messages);
        // LLM strategy: ask the model to summarise the compressible segment into
        // a single synthetic message. Falls back to mechanical on any failure
        // (network error, malformed response, etc.) so a transient model issue
        // never blocks compaction.
        if (strategy === 'llm' && client) {
            try {
                const llmCompressed = await summariseWithLLM({
                    messages,
                    systemMessage,
                    client,
                    tokenizer,
                });
                if (llmCompressed) {
                    // Tool definitions are constant across compaction, so include them on
                    // both sides — otherwise the reduction % is inflated by tokens that
                    // never get removed.
                    const compressedTokenCount = [systemMessage, ...llmCompressed].reduce((sum, msg) => sum + tokenizer.countTokens(msg), 0) + toolDefTokens;
                    const reductionPercentage = totalTokens > 0
                        ? ((totalTokens - compressedTokenCount) / totalTokens) * 100
                        : 0;
                    if (config.notifyUser && onNotify) {
                        const reduction = Math.round(reductionPercentage);
                        onNotify(`Context at ${Math.round(usagePercentage)}% capacity - auto-compacting (LLM summary)...\n\nContext Compacted: ${totalTokens.toLocaleString()} tokens → ${compressedTokenCount.toLocaleString()} tokens (${reduction}% reduction)`);
                    }
                    return llmCompressed;
                }
            }
            catch (_error) {
                // fall through to mechanical
            }
        }
        // Include system message in compression input so compressMessages()
        // preserves it and only compresses user/assistant/tool messages — matching
        // the behaviour of manual `/compact`.
        const result = compressMessages(allMessages, tokenizer, { mode });
        // Filter out system messages from the compressed output (they are re-injected
        // by the chat handler on each LLM call)
        const compressedUserMessages = result.compressedMessages.filter(msg => msg.role !== 'system');
        // Show notification if enabled
        if (config.notifyUser && onNotify) {
            const reduction = Math.round(result.reductionPercentage);
            onNotify(`Context at ${Math.round(usagePercentage)}% capacity - auto-compacting...\n\nContext Compacted: ${result.originalTokenCount.toLocaleString()} tokens → ${result.compressedTokenCount.toLocaleString()} tokens (${reduction}% reduction)`);
        }
        // Return compressed user messages (without system message)
        return compressedUserMessages;
    }
    finally {
        // Clean up tokenizer
        if (tokenizer?.free) {
            tokenizer.free();
        }
    }
}
// Set session override for auto-compact enabled state
export function setAutoCompactEnabled(enabled) {
    autoCompactSession.enabled.set(enabled);
}
// Set session override for auto-compact threshold
export function setAutoCompactThreshold(threshold) {
    autoCompactSession.threshold.set(threshold);
}
// Set session override for auto-compact mode
export function setAutoCompactMode(mode) {
    autoCompactSession.mode.set(mode);
}
// Set session override for auto-compact strategy
export function setAutoCompactStrategy(strategy) {
    autoCompactSession.strategy.set(strategy);
}
// Reset all session overrides
export function resetAutoCompactSession() {
    autoCompactSession.enabled.reset();
    autoCompactSession.threshold.reset();
    autoCompactSession.mode.reset();
    autoCompactSession.strategy.reset();
}
//# sourceMappingURL=auto-compact.js.map