/**
 * AgentManifestCache - Two-tier agent loading with LRU cache
 * Lightweight manifests at warmup, full content on demand
 */
import { promises as fs } from 'fs';
import path from 'path';
import { glob } from 'glob';
import { parseAgentMarkdown, extractCategory } from './agentDiscovery.js';
import { TokenBudgetTracker } from '../lib/TokenBudgetTracker.js';
import { TOKENS } from '../lib/constants.js';
// ============================================================================
// Constants
// ============================================================================
const AGENTS_DIRS = ['.codex/agents', '.claude/agents', 'agents'];
const WARMUP_PREVIEW_LINES = 20;
const DEFAULT_MAX_FULL_CACHE = 5;
// ============================================================================
// AgentManifestCache
// ============================================================================
export class AgentManifestCache {
    static instances = new Map();
    static defaultInstance = null;
    manifests = new Map();
    fullCache = new Map();
    accessOrder = [];
    maxFullCacheSize;
    projectPath;
    agentsBasePath = null;
    constructor(projectPath, maxFullCacheSize) {
        this.projectPath = projectPath;
        this.maxFullCacheSize = maxFullCacheSize;
    }
    static getInstance(projectPath = process.cwd(), maxFullCacheSize = DEFAULT_MAX_FULL_CACHE) {
        const existing = AgentManifestCache.instances.get(projectPath);
        if (existing)
            return existing;
        const inst = new AgentManifestCache(projectPath, maxFullCacheSize);
        AgentManifestCache.instances.set(projectPath, inst);
        return inst;
    }
    async warmUp() {
        this.agentsBasePath = await this.findAgentsDir();
        if (!this.agentsBasePath)
            return;
        const files = await glob('**/*.md', {
            cwd: this.agentsBasePath,
            absolute: true,
        });
        for (const filePath of files) {
            try {
                const content = await this.readPreview(filePath);
                const stat = await fs.stat(filePath);
                const parsed = parseAgentMarkdown(content, filePath);
                const category = extractCategory(filePath, this.agentsBasePath);
                const estimatedTokens = Math.ceil(stat.size * TOKENS.PER_CHAR_ESTIMATE);
                const name = parsed.name || path.basename(filePath, '.md');
                this.manifests.set(name, {
                    name,
                    path: path.relative(this.projectPath, filePath),
                    category,
                    description: parsed.description || '',
                    estimatedTokens,
                });
            }
            catch {
                // Skip individual file errors
            }
        }
    }
    getManifest(name) {
        return this.manifests.get(name) ?? null;
    }
    async getFullAgent(name) {
        const cached = this.fullCache.get(name);
        if (cached) {
            cached.lastAccess = Date.now();
            this.touchAccessOrder(name);
            return cached;
        }
        const manifest = this.manifests.get(name);
        if (!manifest)
            return null;
        try {
            const fullPath = path.resolve(this.projectPath, manifest.path);
            const content = await fs.readFile(fullPath, 'utf-8');
            const entry = {
                manifest,
                content,
                loadedAt: Date.now(),
                lastAccess: Date.now(),
            };
            this.evictIfNeeded();
            this.fullCache.set(name, entry);
            this.accessOrder.push(name);
            return entry;
        }
        catch {
            return null;
        }
    }
    listByCategory(category) {
        return [...this.manifests.values()].filter(m => m.category === category);
    }
    listAll() {
        return [...this.manifests.values()];
    }
    estimateTokenCost(names) {
        const breakdown = {};
        let total = 0;
        for (const name of names) {
            const manifest = this.manifests.get(name);
            if (manifest) {
                breakdown[name] = manifest.estimatedTokens;
                total += manifest.estimatedTokens;
            }
        }
        let wouldExceedBudget = false;
        try {
            const tracker = TokenBudgetTracker.getInstance(this.projectPath);
            wouldExceedBudget = total > tracker.getRemainingBudget();
        }
        catch {
            // Tracker not available
        }
        return { total, breakdown, wouldExceedBudget };
    }
    getStats() {
        const categories = new Set();
        let totalEstimatedTokens = 0;
        for (const m of this.manifests.values()) {
            categories.add(m.category);
            totalEstimatedTokens += m.estimatedTokens;
        }
        return {
            manifestCount: this.manifests.size,
            fullCacheSize: this.fullCache.size,
            fullCacheMaxSize: this.maxFullCacheSize,
            totalEstimatedTokens,
            categories: [...categories],
        };
    }
    invalidate() {
        this.manifests.clear();
        this.fullCache.clear();
        this.accessOrder = [];
    }
    static resetInstance() {
        AgentManifestCache.instances.clear();
        AgentManifestCache.defaultInstance = null;
    }
    // ========================================================================
    // Private helpers
    // ========================================================================
    async findAgentsDir() {
        for (const dir of AGENTS_DIRS) {
            const candidate = path.join(this.projectPath, dir);
            try {
                await fs.access(candidate);
                return candidate;
            }
            catch {
                // Try next
            }
        }
        return null;
    }
    async readPreview(filePath) {
        const content = await fs.readFile(filePath, 'utf-8');
        const lines = content.split('\n');
        return lines.slice(0, WARMUP_PREVIEW_LINES).join('\n');
    }
    evictIfNeeded() {
        while (this.fullCache.size >= this.maxFullCacheSize) {
            const oldest = this.accessOrder.shift();
            if (oldest) {
                this.fullCache.delete(oldest);
            }
            else {
                break;
            }
        }
    }
    touchAccessOrder(name) {
        const idx = this.accessOrder.indexOf(name);
        if (idx !== -1) {
            this.accessOrder.splice(idx, 1);
        }
        this.accessOrder.push(name);
    }
}
//# sourceMappingURL=AgentManifestCache.js.map