/**
 * Kernel Context — service implementation.
 *
 * KernelContextService is the single concrete ContextCompiler for PR 1.
 * It owns a TokenSupremacyEngine internally and adapts the engine's
 * ReadingPlan into the CompiledContextPack shape.
 *
 * Scope notes (PR 1):
 * - no redesign of token heuristics, relevance learning, CAS, or budgets
 * - no dynamic per-model budget engine
 * - reservedOutputTokens / systemMarginTokens / toolMarginTokens are
 *   accepted and echoed, but not yet pushed into the optimizer
 * - `_getEngineUnsafe()` exists purely as a compat escape hatch for
 *   legacy call sites (hypertuneMax, cross-session differential, etc.)
 *   that will be migrated in PR 3 when the optimizer is split up
 */
import * as path from 'path';
import * as fs from 'fs';
import { TokenSupremacyEngine, } from '../../engines/token-supremacy.js';
// ReadAction on the engine side maps 1:1 to SelectedFileMode on the kernel side.
// We re-declare the mapping explicitly so a future engine change can't silently
// drift the public contract.
function readActionToMode(action) {
    switch (action) {
        case 'full': return 'full';
        case 'partial': return 'partial';
        case 'outline': return 'outline';
        case 'skip': return 'skip';
    }
}
export class KernelContextService {
    engine;
    constructor(options = {}) {
        this.engine = options.engine ?? new TokenSupremacyEngine(options.sessionBudget);
    }
    async compileContext(input) {
        const notes = [];
        const warnings = [];
        const fileRefs = await resolveFileRefs(input.files, warnings);
        const boosts = toBoostMap(input.fileBoosts);
        const plan = await this.engine.plan(input.task, fileRefs, boosts);
        const selectedFiles = [];
        const omittedFiles = [];
        for (const entry of plan.files) {
            const mode = readActionToMode(entry.action);
            if (mode === 'skip') {
                omittedFiles.push({ path: entry.file.path, reason: entry.reason });
            }
            else {
                selectedFiles.push({
                    path: entry.file.path,
                    mode,
                    estimatedTokens: entry.estimatedTokens,
                });
            }
        }
        if (input.reservedOutputTokens !== undefined) {
            notes.push(`reservedOutputTokens=${input.reservedOutputTokens} accepted but not applied to budget in PR 1`);
        }
        if (input.systemMarginTokens !== undefined) {
            notes.push(`systemMarginTokens=${input.systemMarginTokens} accepted but not applied to budget in PR 1`);
        }
        if (input.toolMarginTokens !== undefined) {
            notes.push(`toolMarginTokens=${input.toolMarginTokens} accepted but not applied to budget in PR 1`);
        }
        if (input.files.length > 0 && selectedFiles.length === 0) {
            warnings.push('No files selected — all candidates were omitted by the optimizer');
        }
        const pack = {
            selectedFiles,
            tokenPlan: {
                estimatedInputTokens: plan.totalEstimatedTokens,
                estimatedOutputTokens: input.reservedOutputTokens,
                compressionRatio: plan.casCompression?.compressionRatio,
                savings: plan.savings,
                sessionBudget: plan.sessionBudget,
            },
            omittedFiles,
            warnings,
            debugMeta: {
                backend: 'TokenSupremacyEngine',
                notes: notes.length > 0 ? notes : undefined,
                raw: {
                    readingPlan: plan,
                    runId: input.runId,
                    host: input.host,
                    model: input.model,
                    metadata: input.metadata,
                },
            },
        };
        return pack;
    }
    /**
     * Recover the underlying ReadingPlan from a CompiledContextPack we produced.
     * Legacy formatters (formatReadingPlan) still operate on ReadingPlan; this
     * helper keeps the kernel boundary while letting existing display code work.
     */
    static extractReadingPlan(pack) {
        const raw = pack.debugMeta.raw;
        if (!raw || typeof raw !== 'object')
            return undefined;
        const candidate = raw.readingPlan;
        if (!candidate || typeof candidate !== 'object')
            return undefined;
        const rp = candidate;
        if (typeof rp.task !== 'string' || !Array.isArray(rp.files))
            return undefined;
        return rp;
    }
    /** Initialize the underlying engine (loads CAS cache, session dirs). */
    async init() {
        await this.engine.init();
    }
    /**
     * Hypertune context to maximum quality for a given task + file set.
     * Returns the reading plan, assembled context, and budget config so
     * callers can render both a file list and the assembled context string.
     */
    hypertuneMax(task, files, graphConnectivity) {
        return this.engine.hypertuneMax(task, files, graphConnectivity);
    }
    /**
     * Compute which files changed since the last session summary.
     * Returns three sets: added (new), changed (modified), unchanged (cached).
     */
    differential(prev, curr) {
        return this.engine.differential(prev, curr);
    }
    /** Load the most recent persisted session summary, or null if none. */
    async loadLatestSessionSummary() {
        return this.engine.loadLatestSessionSummary();
    }
    /**
     * Allocate a token budget across a set of workers. Used by Synapse to
     * assign per-operative token limits from a shared session budget.
     */
    allocateBudget(workers, totalBudget) {
        return this.engine.allocateBudget(workers, totalBudget);
    }
}
// ─── Shared singleton + file-ref resolution ──────────────────────────────────
let _shared;
/** Return the process-wide singleton service. Used by CLI + MCP helpers. */
export function getKernelContextService() {
    if (!_shared)
        _shared = new KernelContextService();
    return _shared;
}
/** Test-only reset. Production code never calls this. */
export function __resetKernelContextServiceForTests() {
    _shared = undefined;
}
/**
 * Resolve a list of paths to FileRef with stat info.
 * Missing files are kept with sizeBytes=0 and a warning is emitted — this
 * matches what the CLI and MCP handlers did before the kernel boundary
 * existed, so we don't drift behavior.
 */
async function resolveFileRefs(paths, warnings) {
    if (paths.length === 0)
        return [];
    const refs = await Promise.all(paths.map(async (p) => {
        const abs = path.isAbsolute(p) ? p : path.join(process.cwd(), p);
        try {
            const stat = await fs.promises.stat(abs);
            return { path: abs, sizeBytes: stat.size, lastModified: stat.mtimeMs };
        }
        catch {
            warnings.push(`stat failed for ${p} — treating as 0-byte file`);
            return { path: abs, sizeBytes: 0 };
        }
    }));
    return refs;
}
function toBoostMap(boosts) {
    if (!boosts)
        return undefined;
    const map = new Map();
    for (const [k, v] of Object.entries(boosts))
        map.set(k, v);
    return map;
}
