/**
 * Token Supremacy Engine
 *
 * Purpose: Make every token count. This engine runs BEFORE any large context
 * operation and decides what to read, what to skip, what to summarize.
 *
 * Built for AntiGravity to reduce token waste by 50-70%.
 */
import * as path from 'path';
import * as fs from 'fs';
import { resolveNexusStateDir } from './runtime-registry.js';
import { ContextAssembler } from './context-assembler.js';
import { ContinuousAttentionStream } from './attention-stream.js';
// ─────────────────────────────────────────────────────────────────────────────
// Core Engine
// ─────────────────────────────────────────────────────────────────────────────
export class TokenSupremacyEngine {
    static FILE_HEAD_BYTES = 2_000;
    static FILE_HEAD_CACHE_LIMIT = 256;
    sessionBudget;
    sessionPath;
    qualityFloor;
    // Per-session learned relevance: task keyword → file paths that helped
    relevanceCache = new Map();
    relevanceCachePath;
    contextAssembler;
    casEngine;
    fileHeadCache = new Map();
    relevanceScoreCache = new Map();
    _dirsReady = false;
    constructor(sessionBudget = 200_000, options = {}) {
        this.sessionBudget = sessionBudget;
        this.qualityFloor = Number(options.qualityFloor ?? process.env.NEXUS_TOKEN_QUALITY_FLOOR ?? 0.7);
        const stateDir = resolveNexusStateDir();
        this.sessionPath = path.join(stateDir, 'sessions');
        this.relevanceCachePath = path.join(stateDir, 'relevance.json');
        this.contextAssembler = new ContextAssembler();
        this.casEngine = new ContinuousAttentionStream();
        // No sync I/O here — directory creation and cache load deferred to async init().
    }
    /**
     * Lazily ensure state directories exist and relevance cache is loaded.
     * All callers are async — this is the single init path.
     */
    async init() {
        if (this._dirsReady)
            return;
        await fs.promises.mkdir(this.sessionPath, { recursive: true });
        await fs.promises.mkdir(path.dirname(this.relevanceCachePath), { recursive: true });
        await this.loadRelevanceCache();
        this._dirsReady = true;
    }
    // ───────────────────────────────────────────────────────────────────────────
    // Primary API: generate a reading plan for a task
    /**
     * Plan a token-efficient reading strategy for a given task + files.
     */
    /**
     * Plan file reading with optional external file boosts (e.g. from graph memory or n-gram index).
     * fileBoosts: Map<filePath, boostScore> — added to relevance score before thresholding.
     */
    async plan(task, files, fileBoosts) {
        await this.init();
        // Apply hypertuning for 3+ files (always-on optimization)
        if (files.length > 3) {
            this.hypertune(task);
        }
        const taskKeywords = this.extractKeywords(task);
        const lowerTask = task.toLowerCase();
        // Adaptive thresholds based on file count
        const skipThreshold = files.length > 20 ? 0.25 : files.length < 5 ? 0.10 : 0.15;
        const outlineThreshold = files.length > 20 ? 0.45 : files.length < 5 ? 0.30 : 0.40;
        // Parallel file scoring — Promise.all for ultra-fast throughput
        const fileResults = await Promise.all(files.map(async (file) => {
            const baseRelevance = await this.scoreRelevance(file, taskKeywords, taskKeywords);
            const boost = fileBoosts?.get(file.path) ?? 0;
            const relevance = Math.min(1, baseRelevance + boost);
            const estFull = Math.ceil(file.sizeBytes / 4); // ~4 chars per token
            const taskReferenced = this.isTaskReferenced(file.path, lowerTask, taskKeywords);
            const recentlyModified = typeof file.lastModified === 'number' && (Date.now() - file.lastModified) <= (60 * 60 * 1000);
            const criticalBoost = boost >= 0.9;
            const mustReadFull = criticalBoost || recentlyModified;
            let plan;
            if (mustReadFull) {
                plan = {
                    file,
                    action: 'full',
                    tier: 'L2',
                    reason: criticalBoost
                        ? 'critical memory boost requires full read'
                        : 'recently modified within quality guard window',
                    estimatedTokens: estFull,
                };
            }
            else if (relevance < skipThreshold) {
                // Irrelevant — skip entirely
                if (taskReferenced && relevance >= this.qualityFloor * 0.15) {
                    const { start, end } = this.estimateHotLines(file);
                    plan = {
                        file,
                        action: estFull <= 5000 ? 'full' : 'partial',
                        tier: 'L2',
                        startLine: estFull <= 5000 ? undefined : start,
                        endLine: estFull <= 5000 ? undefined : end,
                        reason: `quality floor ${this.qualityFloor.toFixed(2)} prevents skip for task-referenced file`,
                        estimatedTokens: estFull <= 5000 ? estFull : Math.ceil(((end - start) * 80) / 4),
                    };
                }
                else {
                    plan = {
                        file,
                        action: 'skip',
                        tier: 'L0',
                        reason: `low relevance (${relevance.toFixed(2)}) to task`,
                        estimatedTokens: 0
                    };
                }
            }
            else if (estFull < 300) {
                // Small file — always read fully
                plan = {
                    file,
                    action: 'full',
                    tier: 'L2',
                    reason: 'small file, cheap to read',
                    estimatedTokens: estFull
                };
            }
            else if (relevance < outlineThreshold || estFull > 30_000) {
                // Low relevance or huge file — outline only
                plan = {
                    file,
                    action: 'outline',
                    tier: 'L1',
                    reason: relevance < outlineThreshold
                        ? `medium relevance (${relevance.toFixed(2)}), read outline`
                        : `large file (${Math.round(file.sizeBytes / 1024)}KB), read outline`,
                    estimatedTokens: 250
                };
            }
            else if (estFull > 5_000) {
                // Large but relevant — read hot sections
                const { start, end } = this.estimateHotLines(file);
                plan = {
                    file,
                    action: 'partial',
                    tier: 'L2',
                    startLine: start,
                    endLine: end,
                    reason: `relevant but large — reading lines ${start}-${end}`,
                    estimatedTokens: Math.ceil(((end - start) * 80) / 4) // ~80 chars/line
                };
            }
            else {
                // Relevant, reasonable size — full read
                plan = {
                    file,
                    action: 'full',
                    tier: 'L2',
                    reason: `high relevance (${relevance.toFixed(2)})`,
                    estimatedTokens: estFull
                };
            }
            if (plan.action === 'partial' && boost > 0.5) {
                plan = {
                    file,
                    action: 'full',
                    tier: 'L2',
                    reason: `memory boost ${boost.toFixed(2)} promoted partial read to full`,
                    estimatedTokens: estFull,
                };
            }
            return { plan, estFull };
        }));
        const plans = [];
        let totalTokens = 0;
        let fullReadTokens = 0;
        for (const { plan, estFull } of fileResults) {
            fullReadTokens += estFull;
            totalTokens += plan.estimatedTokens;
            plans.push(plan);
        }
        return {
            task,
            files: plans,
            totalEstimatedTokens: totalTokens,
            savings: fullReadTokens - totalTokens,
            sessionBudget: this.sessionBudget,
        };
    }
    /**
     * Legacy compatibility wrapper for NexusPrime.
     */
    async optimize(context, task) {
        const files = context.map((c, i) => ({
            path: `context_${i}.txt`,
            sizeBytes: c.length * 4,
            lastModified: Date.now()
        }));
        return this.plan(task, files);
    }
    // ───────────────────────────────────────────────────────────────────────────
    // Differential Context: only pass what CHANGED
    // Saves ~60-70% tokens on follow-up sessions
    // ───────────────────────────────────────────────────────────────────────────
    differential(prev, curr) {
        const previousFileSHAs = prev.fileSHAs && typeof prev.fileSHAs === 'object'
            ? prev.fileSHAs
            : {};
        const hasComparableFingerprints = Object.keys(previousFileSHAs).length > 0;
        const delta = {
            added: [],
            changed: [],
            unchanged: [],
            summary: prev.summary,
            memoryContextSummary: prev.keyDecisions?.length
                ? `Memory context: ${prev.keyDecisions.slice(0, 5).join(' | ')}`
                : undefined,
        };
        for (const file of curr) {
            const prevSHA = previousFileSHAs[file.path];
            const currSHA = this.fingerprintFile(file);
            if (!prevSHA) {
                if (hasComparableFingerprints) {
                    delta.added.push(file);
                }
                else {
                    delta.changed.push(file);
                }
            }
            else if (prevSHA !== currSHA) {
                delta.changed.push(file);
            }
            else {
                delta.unchanged.push(file); // SKIP — use prior summary
            }
        }
        return delta;
    }
    // ───────────────────────────────────────────────────────────────────────────
    // Multi-Worker Token Budget Allocation
    // Byzantine-inspired: workers bid, budget allocated by expected value
    // ───────────────────────────────────────────────────────────────────────────
    allocateBudget(workers, totalBudget = this.sessionBudget) {
        const bids = workers.map(w => ({
            id: w.id,
            score: w.estimatedValue / Math.max(w.estimatedCost, 1)
        }));
        const totalScore = bids.reduce((s, b) => s + b.score, 0) || 1;
        const allocated = new Map();
        let used = 0;
        for (const bid of bids) {
            const share = Math.floor((bid.score / totalScore) * totalBudget);
            allocated.set(bid.id, share);
            used += share;
        }
        return { total: totalBudget, allocated, remaining: totalBudget - used };
    }
    // ───────────────────────────────────────────────────────────────────────────
    // Learn: record which files were actually useful for a task type
    // ───────────────────────────────────────────────────────────────────────────
    async learn(taskType, usefulFiles, skipFiles) {
        await this.init();
        if (!this.relevanceCache.has(taskType)) {
            this.relevanceCache.set(taskType, new Map());
        }
        const cache = this.relevanceCache.get(taskType);
        for (const f of usefulFiles) {
            cache.set(f, (cache.get(f) ?? 0) + 1.0);
        }
        for (const f of skipFiles) {
            cache.set(f, (cache.get(f) ?? 0) - 0.3); // mild penalty
        }
        this.relevanceScoreCache.clear();
        await this.saveRelevanceCache();
    }
    async loadRelevanceCache() {
        try {
            const raw = await fs.promises.readFile(this.relevanceCachePath, 'utf-8');
            const data = JSON.parse(raw);
            for (const [taskType, fileScores] of Object.entries(data)) {
                const scoreMap = new Map(Object.entries(fileScores));
                this.relevanceCache.set(taskType, scoreMap);
            }
        }
        catch {
            // Cache miss or parse error — start fresh
        }
    }
    async saveRelevanceCache() {
        try {
            const data = {};
            for (const [taskType, scoreMap] of this.relevanceCache.entries()) {
                data[taskType] = Object.fromEntries(scoreMap.entries());
            }
            await fs.promises.writeFile(this.relevanceCachePath, JSON.stringify(data, null, 2));
        }
        catch (e) {
            console.error('Failed to save relevance cache:', e);
        }
    }
    // ───────────────────────────────────────────────────────────────────────────
    // Session summary persistence
    // ───────────────────────────────────────────────────────────────────────────
    async saveSessionSummary(summary) {
        const date = new Date(summary.timestamp).toISOString().slice(0, 10);
        const file = path.join(this.sessionPath, `${date}-${summary.sessionId}.json`);
        await fs.promises.writeFile(file, JSON.stringify(summary, null, 2));
    }
    async loadLatestSessionSummary() {
        try {
            const entries = (await fs.promises.readdir(this.sessionPath))
                .filter(f => f.endsWith('.json'))
                .sort()
                .reverse();
            if (entries.length === 0)
                return null;
            const content = await fs.promises.readFile(path.join(this.sessionPath, entries[0]), 'utf-8');
            return JSON.parse(content);
        }
        catch {
            return null;
        }
    }
    // ───────────────────────────────────────────────────────────────────────────
    // Helpers
    // ───────────────────────────────────────────────────────────────────────────
    extractKeywords(task) {
        // Remove stop words, extract meaningful tokens
        const stopWords = new Set([
            'a', 'an', 'the', 'is', 'are', 'was', 'were', 'be', 'been',
            'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will',
            'would', 'could', 'should', 'may', 'might', 'must', 'can',
            'to', 'of', 'in', 'for', 'on', 'with', 'at', 'by', 'from',
            'and', 'or', 'but', 'if', 'then', 'than', 'that', 'this',
            'it', 'its', 'we', 'our', 'you', 'your', 'i', 'my'
        ]);
        return task
            .toLowerCase()
            // eslint-disable-next-line no-useless-escape
            .replace(/[^a-z0-9\s_\-\/\.]/g, ' ')
            .split(/\s+/)
            .filter(w => w.length > 2 && !stopWords.has(w));
    }
    async scoreRelevance(file, keywords, task) {
        if (keywords.length === 0)
            return 0.5;
        const taskKey = (task ?? keywords).slice(0, 3).join('_');
        const cacheKey = [
            file.path,
            file.lastModified ?? 0,
            file.sizeBytes,
            taskKey,
            keywords.join('|'),
        ].join('::');
        const cachedScore = this.relevanceScoreCache.get(cacheKey);
        if (typeof cachedScore === 'number') {
            return cachedScore;
        }
        // ── Normalize path: strip cwd prefix so keywords match relative segments ──
        const cwd = process.cwd().toLowerCase().replace(/\\/g, '/');
        let normalized = file.path.toLowerCase().replace(/\\/g, '/');
        if (normalized.startsWith(cwd)) {
            normalized = normalized.slice(cwd.length).replace(/^\//, '');
        }
        const parts = normalized.split('/');
        const fileName = parts[parts.length - 1];
        const baseName = fileName.replace(/\.[^.]+$/, ''); // e.g. "memory" from "memory.ts"
        const pathComponents = parts.map(p => p.replace(/\.[^.]+$/, '')); // e.g. ['engines', 'token', 'supremacy']
        const ext = fileName.split('.').pop() ?? '';
        // ── Extension-based baseline: .ts/.js files always get a floor for code tasks ──
        let extBaseline = 0;
        if (['ts', 'js', 'tsx', 'jsx'].includes(ext))
            extBaseline = 0.20;
        else if (['json', 'md'].includes(ext))
            extBaseline = 0.15;
        // ── Path keyword matching ──
        let pathScore = 0;
        let pathMatches = 0;
        for (const kw of keywords) {
            // Check basename directly (e.g. keyword "memory" matches "memory.ts")
            if (baseName === kw || baseName.includes(kw)) {
                pathMatches++;
                pathScore += 1.5;
            }
            else if (normalized.includes(kw)) {
                pathMatches++;
                pathScore += 0.8;
            }
            // Path component matching (e.g., keyword "memory" boosts "src/engines/memory.ts")
            for (const component of pathComponents) {
                if (component === kw || component.includes(kw)) {
                    pathScore += 0.5;
                    pathMatches++;
                    break;
                }
            }
        }
        // ── Content-aware scoring (first 2000 bytes) — weighted heavily ──
        let contentBonus = 0;
        try {
            const fullPath = path.isAbsolute(file.path) ? file.path : path.join(process.cwd(), file.path);
            const contentKeywords = await this.readFileHeadKeywords(fullPath);
            if (contentKeywords) {
                let contentMatches = 0;
                for (const kw of keywords) {
                    if (contentKeywords.includes(kw))
                        contentMatches++;
                }
                // Boost from 0.6 → 0.8 — content is the most reliable signal
                contentBonus = (contentMatches / keywords.length) * 0.8;
            }
        }
        catch { /* ignore */ }
        // ── Learned relevance ──
        const learnedScore = this.relevanceCache.get(taskKey)?.get(file.path) ?? 0;
        // ── Semantic type bonuses ──
        let typeBonus = 0;
        const codeKws = ['fix', 'bug', 'debug', 'error', 'impl', 'add', 'build', 'audit', 'nexus', 'prime', 'tools', 'code', 'source'];
        const memKws = ['memory', 'cache', 'store', 'recall', 'persist'];
        const testKws = ['test', 'spec', 'jest', 'validate', 'verify'];
        if (keywords.some(k => codeKws.includes(k)) && ['ts', 'js', 'json', 'md'].includes(ext)) {
            typeBonus = 0.15;
        }
        if (keywords.some(k => memKws.includes(k)) && (normalized.includes('memory') || baseName.includes('memory'))) {
            typeBonus = 0.4;
        }
        if (keywords.some(k => testKws.includes(k)) && ['test', 'spec'].some(t => fileName.includes(t))) {
            typeBonus = 0.3;
        }
        // ── Combine: baseline + path + content + type + learned ──
        const pathComponent = keywords.length > 0
            ? (pathMatches / keywords.length) * (pathScore / Math.max(pathMatches, 1))
            : 0;
        const score = Math.min(1, Math.max(extBaseline, pathComponent + typeBonus + learnedScore * 0.1 + contentBonus));
        this.relevanceScoreCache.set(cacheKey, score);
        while (this.relevanceScoreCache.size > TokenSupremacyEngine.FILE_HEAD_CACHE_LIMIT * 4) {
            const oldest = this.relevanceScoreCache.keys().next().value;
            if (!oldest)
                break;
            this.relevanceScoreCache.delete(oldest);
        }
        return score;
    }
    async readFileHead(fullPath) {
        let fileHandle = null;
        try {
            const stat = await fs.promises.stat(fullPath);
            const cached = this.fileHeadCache.get(fullPath);
            if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
                this.fileHeadCache.delete(fullPath);
                this.fileHeadCache.set(fullPath, cached);
                return cached.content;
            }
            fileHandle = await fs.promises.open(fullPath, 'r');
            const buffer = Buffer.alloc(TokenSupremacyEngine.FILE_HEAD_BYTES);
            const { bytesRead } = await fileHandle.read(buffer, 0, TokenSupremacyEngine.FILE_HEAD_BYTES, 0);
            const content = buffer.toString('utf-8', 0, bytesRead).toLowerCase();
            this.fileHeadCache.set(fullPath, {
                mtimeMs: stat.mtimeMs,
                size: stat.size,
                content,
            });
            while (this.fileHeadCache.size > TokenSupremacyEngine.FILE_HEAD_CACHE_LIMIT) {
                const oldest = this.fileHeadCache.keys().next().value;
                if (!oldest)
                    break;
                this.fileHeadCache.delete(oldest);
            }
            return content;
        }
        catch {
            return null;
        }
        finally {
            if (fileHandle) {
                await fileHandle.close().catch(() => { });
            }
        }
    }
    async readFileHeadKeywords(fullPath) {
        const content = await this.readFileHead(fullPath);
        if (!content)
            return null;
        const cached = this.fileHeadCache.get(fullPath);
        if (!cached) {
            return this.extractKeywords(content);
        }
        if (!cached.keywords) {
            cached.keywords = this.extractKeywords(cached.content);
            this.fileHeadCache.set(fullPath, cached);
        }
        return cached.keywords;
    }
    estimateHotLines(file) {
        // Without reading the file, estimate hot sections by filename heuristics
        // In production, the Ghost Pass pre-reads and provides actual line hints
        const totalLines = Math.ceil(file.sizeBytes / 80); // ~80 chars/line
        const windowSize = Math.min(200, Math.ceil(totalLines * 0.3));
        // Heuristic: hot code tends to be in first 30% or last 30% of file
        // Default to first section (imports + main logic)
        return {
            start: 1,
            end: Math.min(windowSize, totalLines)
        };
    }
    fingerprintFile(file) {
        // Use mtime + size as cheap fingerprint (no hashing needed)
        return `${file.lastModified ?? 0}-${file.sizeBytes}`;
    }
    isTaskReferenced(filePath, taskLower, keywords) {
        const normalized = filePath.toLowerCase().replace(/\\/g, '/');
        const baseName = normalized.split('/').pop() ?? normalized;
        const stem = baseName.replace(/\.[^.]+$/, '');
        if (taskLower.includes(baseName) || taskLower.includes(stem))
            return true;
        const pathTokens = normalized.split(/[/_.-]+/).filter(Boolean);
        return keywords.some((keyword) => pathTokens.includes(keyword));
    }
    /**
     * Adjust token budget based on task complexity signals.
     * Complex tasks get +25% budget; simple lookups get -25%.
     */
    hypertune(task) {
        const taskLower = task.toLowerCase();
        const originalBudget = this.sessionBudget;
        // Complexity signals
        const complexSignals = ['refactor', 'complex', 'rewrite', 'migration', 'audit', 'architecture', 'redesign'];
        const simpleSignals = ['lookup', 'find', 'list', 'check', 'quick', 'status'];
        const isComplex = complexSignals.some(s => taskLower.includes(s));
        const isSimple = simpleSignals.some(s => taskLower.includes(s));
        if (isComplex) {
            this.sessionBudget = Math.round(originalBudget * 1.25);
        }
        else if (isSimple) {
            this.sessionBudget = Math.round(originalBudget * 0.75);
        }
        if (this.sessionBudget !== originalBudget) {
            console.error(`[Token Engine] Hypertuned: budget ${originalBudget.toLocaleString()} → ${this.sessionBudget.toLocaleString()} tokens (${isComplex ? 'complex' : 'simple'} task detected)`);
        }
    }
    // ─────────────────────────────────────────────────────────────────────────
    // HyperTune Max: Mathematical context-token optimization
    // ─────────────────────────────────────────────────────────────────────────
    /**
     * HyperTune Max — Mathematical context assembly using greedy knapsack.
     * Chunks all files at function/class boundaries, scores each chunk
     * by relevance/recency/connectivity/novelty, and selects the optimal
     * combination that maximizes quality within the token budget.
     */
    async hypertuneMax(task, files, graphConnectivity) {
        // 1. Compute adaptive budget
        const budgetConfig = this.contextAssembler.computeBudget(task, this.sessionBudget);
        // 2. Chunk all candidate files (async I/O per file, in parallel)
        const allChunks = (await Promise.all(files.map(f => {
            const resolved = path.isAbsolute(f.path) ? f.path : path.join(process.cwd(), f.path);
            return this.contextAssembler.chunkFile(resolved);
        }))).flat();
        // 3. Optionally score connectivity from graph (Phase 8D)
        if (graphConnectivity) {
            for (const chunk of allChunks) {
                chunk.quality = graphConnectivity({ source: chunk.source, label: chunk.label });
            }
        }
        // 4. Greedy knapsack selection
        const assembly = this.contextAssembler.assemble(task, allChunks, budgetConfig.effectiveBudget);
        // 5. Convert to ReadingPlan format for backward compatibility
        const fileMap = new Map();
        for (const file of files) {
            const resolved = path.isAbsolute(file.path) ? file.path : path.join(process.cwd(), file.path);
            fileMap.set(resolved, { chunks: [], file });
        }
        for (const chunk of assembly.selected) {
            const entry = fileMap.get(chunk.source);
            if (entry)
                entry.chunks.push(chunk);
        }
        const plans = [];
        let totalTokens = 0;
        let fullReadTokens = 0;
        for (const { chunks, file } of fileMap.values()) {
            const estFull = Math.ceil(file.sizeBytes / 4);
            fullReadTokens += estFull;
            if (chunks.length === 0) {
                plans.push({
                    file,
                    action: 'skip',
                    tier: 'L0',
                    reason: 'below quality threshold',
                    estimatedTokens: 0,
                });
            }
            else {
                // Check if all lines are covered
                const totalLines = Math.ceil(file.sizeBytes / 80);
                const coveredLines = chunks.reduce((s, c) => s + (c.endLine - c.startLine + 1), 0);
                const coverage = coveredLines / Math.max(totalLines, 1);
                if (coverage > 0.7) {
                    plans.push({
                        file,
                        action: 'full',
                        tier: 'L2',
                        reason: `${(coverage * 100).toFixed(0)}% coverage — read fully (q=${chunks.reduce((s, c) => s + c.quality, 0).toFixed(2)})`,
                        estimatedTokens: estFull,
                    });
                    totalTokens += estFull;
                }
                else {
                    // Take the highest-quality chunk's range
                    const best = chunks.sort((a, b) => b.quality - a.quality)[0];
                    const chunkTokens = chunks.reduce((s, c) => s + c.tokens, 0);
                    plans.push({
                        file,
                        action: 'partial',
                        tier: this.contextAssembler.tierForQuality(best.quality),
                        startLine: best.startLine,
                        endLine: best.endLine,
                        reason: `${chunks.length} chunk(s) selected (q=${chunks.reduce((s, c) => s + c.quality, 0).toFixed(2)})`,
                        estimatedTokens: chunkTokens,
                    });
                    totalTokens += chunkTokens;
                }
            }
        }
        // Attention compression: informational — measures keyword redundancy in selected chunks.
        // Does NOT override totalEstimatedTokens; the knapsack total is the authoritative budget figure.
        const assembledContent = assembly.selected.map(c => c.content).join('\n');
        const contentKeywords = this.extractKeywords(assembledContent);
        const casEncoding = this.casEngine.encode(contentKeywords, task);
        const attentionTokens = Math.max(1, Math.ceil(casEncoding.compressed.length / 4));
        const compressionRatio = contentKeywords.length > 0
            ? contentKeywords.length / Math.max(1, this.extractKeywords(casEncoding.compressed).length)
            : 1;
        const plan = {
            task,
            files: plans,
            totalEstimatedTokens: totalTokens,
            savings: fullReadTokens - totalTokens,
            sessionBudget: budgetConfig.effectiveBudget,
            casCompression: {
                enabled: true,
                compressionRatio,
                originalTokens: totalTokens,
                compressedTokens: attentionTokens,
            }
        };
        return { plan, assembly, budgetConfig };
    }
}
// ─────────────────────────────────────────────────────────────────────────────
// Plan formatter (for MCP response)
// ─────────────────────────────────────────────────────────────────────────────
export function formatReadingPlan(plan) {
    const W = 68;
    const top = `┌─ ⚡ TOKEN SUPREMACY ${'─'.repeat(Math.max(0, W - 23))}┐`;
    const bot = `└${'─'.repeat(W)}┘`;
    const mid = `├${'─'.repeat(W)}┤`;
    const pad = (str, len) => {
        if (str.length > len)
            return str.substring(0, len - 3) + '...';
        return str.padEnd(len);
    };
    const row = (left, right) => `│ ${pad(left, 24)} │ ${pad(right, W - 29)} │`;
    const lines = [top];
    // Header Stats
    lines.push(row('Task', plan.task));
    lines.push(row('Total Estimated', `${plan.totalEstimatedTokens.toLocaleString()} tokens`));
    if (plan.casCompression?.enabled) {
        lines.push(row('CAS Compression', `${plan.casCompression.compressionRatio.toFixed(1)}x (${plan.casCompression.originalTokens.toLocaleString()} → ${plan.casCompression.compressedTokens.toLocaleString()})`));
    }
    const pct = Math.round(plan.savings / Math.max(plan.savings + plan.totalEstimatedTokens, 1) * 100);
    lines.push(row('Savings', `${plan.savings.toLocaleString()} tokens (${pct}% vs full)`));
    lines.push(mid);
    // File breakdown
    const byAction = {
        full: [], outline: [], partial: [], skip: []
    };
    for (const fp of plan.files)
        byAction[fp.action].push(fp);
    const formatFiles = (actionName, icon, files) => {
        if (files.length === 0)
            return;
        lines.push(`│ ${icon} ${pad(actionName.toUpperCase(), W - 5)} │`);
        files.forEach(f => {
            const shortPath = f.file.path.split('/').pop() || f.file.path;
            let detail = f.action === 'skip' ? f.reason : `~${f.estimatedTokens}t`;
            if (f.action === 'partial')
                detail = `L${f.startLine}-${f.endLine} (~${f.estimatedTokens}t)`;
            lines.push(row(`   ${shortPath}`, detail));
        });
        lines.push(mid);
    };
    formatFiles('Read Fully', '✅', byAction.full);
    formatFiles('Read Partially', '✂️', byAction.partial);
    formatFiles('Outline Only', '🔍', byAction.outline);
    formatFiles('Skip', '⏭️', byAction.skip);
    lines.pop(); // Remove last mid line
    lines.push(bot);
    return lines.join('\n');
}
export const createTokenSupremacyEngine = (budget) => new TokenSupremacyEngine(budget);
