/**
 * Prompt layer types for system prompt assembly and compaction.
 *
 * Each layer in the system prompt has metadata that determines how it behaves
 * under token budget pressure — whether it can be expired or truncated, and
 * at what priority it should be preserved.
 */
// ── Compaction function ─────────────────────────────────────────────────
/**
 * Compact system prompt layers to fit within a token budget.
 *
 * Applies compact strategies from lowest priority up:
 * - "expire" layers are dropped first.
 * - "truncate" layers are cut to their maxTokens.
 * - "preserve" layers are never touched.
 *
 * @param layers  The prompt layers to potentially compact.
 * @param budgetTokens  The remaining token budget.
 * @returns The compacted layers and tokens saved.
 */
export function compactSystemPromptLayers(layers, budgetTokens) {
    const originalEstimated = layers.reduce((sum, l) => sum + (l.estimatedTokens ?? estimatePromptLayerTokens(l.value)), 0);
    if (originalEstimated <= budgetTokens) {
        return {
            layers,
            tokensSaved: 0,
            originalEstimatedTokens: originalEstimated,
            finalEstimatedTokens: originalEstimated,
            budgetTokens,
            overBudgetTokens: 0,
        };
    }
    // Work on mutable copies sorted by priority descending (compact lowest priority first)
    const indexed = layers.map((l, i) => ({ ...l, originalIndex: i }));
    const sorted = indexed.sort((a, b) => b.priority - a.priority);
    let totalEstimated = originalEstimated;
    for (const layer of sorted) {
        if (totalEstimated <= budgetTokens)
            break;
        const currentTokens = layer.estimatedTokens ?? estimatePromptLayerTokens(layer.value);
        if (layer.compactStrategy === "expire") {
            totalEstimated -= currentTokens;
            layer.value = undefined;
            layer.estimatedTokens = 0;
        }
        else if (layer.compactStrategy === "truncate" && layer.maxTokens !== undefined) {
            const remainingWithoutLayer = totalEstimated - currentTokens;
            const budgetForLayer = Math.max(0, budgetTokens - remainingWithoutLayer);
            const target = Math.min(Math.max(0, layer.maxTokens), currentTokens, budgetForLayer);
            const saved = currentTokens - target;
            if (saved > 0 && layer.value) {
                // Rough character-based truncation (~4 chars per token)
                const maxChars = target * 4;
                if (layer.value.length > maxChars) {
                    const rawCompacted = layer.compact
                        ? layer.compact(layer.value, target)
                        : compactPromptLayerText(layer.value, maxChars);
                    const compacted = estimatePromptLayerTokens(rawCompacted) <= target
                        ? rawCompacted
                        : compactPromptLayerText(rawCompacted, maxChars);
                    const nextTokens = estimatePromptLayerTokens(compacted);
                    layer.value = compacted;
                    layer.estimatedTokens = nextTokens;
                    totalEstimated -= currentTokens - nextTokens;
                }
            }
        }
        // "preserve" — never touched
    }
    // Reconstruct in original order
    const result = sorted
        .sort((a, b) => a.originalIndex - b.originalIndex)
        .map(({ originalIndex: _originalIndex, ...rest }) => rest);
    return {
        layers: result,
        tokensSaved: Math.max(0, originalEstimated - totalEstimated),
        originalEstimatedTokens: originalEstimated,
        finalEstimatedTokens: totalEstimated,
        budgetTokens,
        overBudgetTokens: Math.max(0, totalEstimated - budgetTokens),
    };
}
// ── Helpers ─────────────────────────────────────────────────────────────
/**
 * Rough token estimate: ~4 characters per token.
 * This matches the existing estimateTextTokens heuristic used elsewhere.
 */
export function estimatePromptLayerTokens(value) {
    if (!value)
        return 0;
    return Math.ceil(value.length / 4);
}
export function compactRecentPromptLayerLines(value, maxTokens) {
    const maxChars = Math.max(0, maxTokens * 4);
    if (value.length <= maxChars) {
        return value;
    }
    if (maxChars <= 0) {
        return "";
    }
    const lines = value
        .split("\n")
        .map((line) => line.trimEnd())
        .filter((line) => line.length > 0);
    if (lines.length === 0) {
        return value.slice(0, maxChars);
    }
    const marker = "[...older entries compacted...]";
    if (maxChars <= marker.length + 2) {
        return value.slice(value.length - maxChars);
    }
    const kept = [];
    let used = marker.length + 1;
    for (let index = lines.length - 1; index >= 0; index -= 1) {
        const line = lines[index];
        const nextUsed = used + line.length + 1;
        if (nextUsed > maxChars) {
            break;
        }
        kept.unshift(line);
        used = nextUsed;
    }
    if (kept.length === 0) {
        return value.slice(value.length - maxChars);
    }
    return [marker, ...kept].join("\n");
}
export function compactConversationMemoryLayer(value, maxTokens) {
    const maxChars = Math.max(0, maxTokens * 4);
    if (value.length <= maxChars) {
        return value;
    }
    if (maxChars <= 0) {
        return "";
    }
    const lines = value
        .split("\n")
        .map((line) => line.trimEnd())
        .filter((line) => line.length > 0);
    if (lines.length === 0) {
        return value.slice(0, maxChars);
    }
    const importantLines = lines.filter(isHighValueMemoryLine);
    const recentText = compactRecentPromptLayerLines(value, maxTokens);
    const candidates = [
        ...importantLines,
        importantLines.length > 0 ? "[...recent memory...]" : undefined,
        ...lines.slice(-4),
        ...recentText.split("\n"),
    ].filter((line) => Boolean(line && line.length > 0));
    const selected = [];
    const seen = new Set();
    for (const line of candidates) {
        if (seen.has(line)) {
            continue;
        }
        const nextText = [...selected, line].join("\n");
        if (nextText.length > maxChars) {
            continue;
        }
        selected.push(line);
        seen.add(line);
    }
    if (selected.length > 0) {
        return selected.join("\n");
    }
    return compactRecentPromptLayerLines(value, maxTokens);
}
function isHighValueMemoryLine(line) {
    const normalized = line.replace(/^- /u, "").toLowerCase();
    return (normalized.startsWith("objective:") ||
        normalized.startsWith("current objective:") ||
        normalized.startsWith("latest user intent:") ||
        normalized.startsWith("user constraints:") ||
        normalized.startsWith("active constraints:") ||
        normalized.startsWith("open obligations:") ||
        normalized.startsWith("active obligations:") ||
        normalized.startsWith("pending obligations:") ||
        normalized.startsWith("unresolved tasks:") ||
        normalized.startsWith("open blockers:") ||
        normalized.startsWith("open questions:") ||
        normalized.startsWith("do not repeat:"));
}
function compactPromptLayerText(value, maxChars) {
    if (maxChars <= 0) {
        return "";
    }
    if (value.length <= maxChars) {
        return value;
    }
    const marker = "\n[...middle compacted...]\n";
    if (maxChars <= marker.length + 8) {
        return value.slice(0, maxChars);
    }
    const preservedChars = maxChars - marker.length;
    const headChars = Math.ceil(preservedChars / 2);
    const tailChars = Math.floor(preservedChars / 2);
    return `${value.slice(0, headChars)}${marker}${value.slice(value.length - tailChars)}`;
}
//# sourceMappingURL=prompt-layers.js.map