/**
 * Self-Summarization Engine
 *
 * LLM-based context compression inspired by Cursor's self-summarization approach.
 * At configurable token thresholds, generates ~1000 token semantic summaries
 * preserving task state, key decisions, and file context.
 *
 * Summaries are stored in Nexus memory and reused on subsequent sessions
 * to dramatically reduce token consumption on repeat project visits.
 */
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
// ─────────────────────────────────────────────────────────────────────────────
// Constants
// ─────────────────────────────────────────────────────────────────────────────
const DEFAULT_TOKEN_THRESHOLD = Number(process.env.NEXUS_SUMMARY_THRESHOLD) || 40_000;
const DEFAULT_SUMMARY_BUDGET = Number(process.env.NEXUS_SUMMARY_BUDGET) || 1000;
const NEXUS_STATE_DIR = path.join(os.homedir(), '.nexus-prime');
const SUMMARIES_DIR = path.join(NEXUS_STATE_DIR, 'summaries');
// ─────────────────────────────────────────────────────────────────────────────
// Summarization prompt
// ─────────────────────────────────────────────────────────────────────────────
function buildSummarizationPrompt(context) {
    const historyBlock = context.conversationHistory.slice(-20).join('\n---\n');
    const filesBlock = context.activeFiles.slice(0, 30).join('\n');
    return `You are summarizing a coding session context. Generate a concise summary in approximately ${DEFAULT_SUMMARY_BUDGET} tokens.

PRESERVE (critical for session continuity):
1. Current task and plan state — what's being worked on and what step we're at
2. Key decisions made and their rationale — why choices were made
3. File modifications and their purpose — what changed and why
4. Remaining tasks — what still needs to be done
5. Any errors encountered and their resolution status

DO NOT include:
- Raw file contents or code blocks
- Verbose explanations — be terse and information-dense
- Implementation details that can be re-read from files

Current task state: ${context.taskState}

Active files:
${filesBlock}

Recent session history:
${historyBlock}

Prior summarizations: ${context.priorSummaryCount}

Generate a summary that would let a future session continue this work without re-reading unchanged files:`;
}
// ─────────────────────────────────────────────────────────────────────────────
// SelfSummarizer
// ─────────────────────────────────────────────────────────────────────────────
export class SelfSummarizer {
    tokenThreshold;
    summaryBudget;
    projectId;
    summaryCount = 0;
    constructor(options) {
        this.tokenThreshold = options?.tokenThreshold ?? DEFAULT_TOKEN_THRESHOLD;
        this.summaryBudget = options?.summaryBudget ?? DEFAULT_SUMMARY_BUDGET;
        this.projectId = options?.projectId ?? 'default';
        this.ensureSummariesDir();
    }
    // ── Trigger check ────────────────────────────────────────────────────────
    /**
     * Check if summarization should trigger based on current token count.
     */
    shouldSummarize(currentTokenCount) {
        return currentTokenCount >= this.tokenThreshold;
    }
    // ── Summarize ────────────────────────────────────────────────────────────
    /**
     * Generate a self-summary from current session context.
     * Uses a local heuristic-based approach (no LLM call required) that
     * extracts key information from the conversation history.
     *
     * For LLM-based summarization, the caller should pass the prompt from
     * buildSummarizationPrompt() to their preferred LLM endpoint.
     */
    summarize(context) {
        this.summaryCount++;
        // Heuristic extraction: pull key signals from conversation history
        const keyDecisions = [];
        const actionItems = [];
        for (const entry of context.conversationHistory) {
            // Extract decisions (lines with decision-like patterns)
            const decisionPatterns = /(?:decided|chose|selected|using|switched to|going with|will use|picked)\s+(.{10,80})/gi;
            let match;
            while ((match = decisionPatterns.exec(entry)) !== null) {
                keyDecisions.push(match[0].trim());
            }
            // Extract remaining tasks
            const todoPatterns = /(?:TODO|FIXME|still need|remaining|next step|then we need)\s*:?\s*(.{10,80})/gi;
            while ((match = todoPatterns.exec(entry)) !== null) {
                actionItems.push(match[0].trim());
            }
        }
        // Build compact summary
        const summaryParts = [
            `## Session Summary (auto-generated, summarization #${this.summaryCount})`,
            `**Task:** ${context.taskState}`,
            `**Active files:** ${context.activeFiles.length} files`,
            context.activeFiles.slice(0, 10).map(f => `  - ${path.basename(f)}`).join('\n'),
        ];
        if (keyDecisions.length > 0) {
            summaryParts.push(`**Key decisions:**`);
            summaryParts.push(...keyDecisions.slice(0, 8).map(d => `  - ${d}`));
        }
        if (actionItems.length > 0) {
            summaryParts.push(`**Remaining:**`);
            summaryParts.push(...actionItems.slice(0, 5).map(a => `  - ${a}`));
        }
        summaryParts.push(`**Compression:** ${context.currentTokenCount.toLocaleString()} tokens → ~${this.summaryBudget} tokens`);
        const summary = summaryParts.join('\n');
        const summaryTokens = Math.ceil(summary.length / 4);
        const result = {
            summary,
            taskState: context.taskState,
            keyDecisions: keyDecisions.slice(0, 8),
            fileFingerprints: {},
            priorSummaryCount: this.summaryCount,
            timestamp: Date.now(),
            projectId: this.projectId,
            summaryTokens,
            originalTokens: context.currentTokenCount,
        };
        // Persist locally
        this.persistSummary(result);
        return result;
    }
    /**
     * Get the LLM prompt for external summarization.
     * The caller should send this to Anthropic API and store the result.
     */
    getSummarizationPrompt(context) {
        return buildSummarizationPrompt(context);
    }
    // ── Bootstrap from prior summary ─────────────────────────────────────────
    /**
     * Attempt to bootstrap from a previously stored summary for this project.
     * Returns null if no summary exists or it's too old.
     */
    bootstrapFromSummary(projectId) {
        const pid = projectId ?? this.projectId;
        const summaryPath = path.join(SUMMARIES_DIR, `${sanitizeId(pid)}-latest.json`);
        try {
            if (!fs.existsSync(summaryPath))
                return null;
            const data = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
            // Check staleness: summaries older than 7 days are too stale
            const ageMs = Date.now() - data.timestamp;
            const maxAgeMs = 7 * 24 * 60 * 60 * 1000;
            if (ageMs > maxAgeMs)
                return null;
            return data;
        }
        catch {
            return null;
        }
    }
    // ── Persistence ──────────────────────────────────────────────────────────
    persistSummary(summary) {
        try {
            const filename = `${sanitizeId(summary.projectId)}-latest.json`;
            fs.writeFileSync(path.join(SUMMARIES_DIR, filename), JSON.stringify(summary, null, 2), 'utf8');
        }
        catch { /* best effort */ }
    }
    ensureSummariesDir() {
        try {
            if (!fs.existsSync(SUMMARIES_DIR)) {
                fs.mkdirSync(SUMMARIES_DIR, { recursive: true });
            }
        }
        catch { /* best effort */ }
    }
}
// ─────────────────────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────────────────────
function sanitizeId(id) {
    return id.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64);
}
// ─────────────────────────────────────────────────────────────────────────────
// Singleton
// ─────────────────────────────────────────────────────────────────────────────
const _sharedSummarizers = new Map();
export function getSharedSummarizer(options) {
    const projectId = options?.projectId ?? 'default';
    const existing = _sharedSummarizers.get(projectId);
    if (existing) {
        return existing;
    }
    const summarizer = new SelfSummarizer(options);
    _sharedSummarizers.set(projectId, summarizer);
    return summarizer;
}
