// engine/nli-context.ts — NLI session context management
// Manages NLIContext per session: active spec, last resource, conversation memory,
// pronoun resolution, and vocabulary learning.
import { randomUUID } from 'node:crypto';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
/** Maximum conversation memory entries retained per session. */
const MAX_MEMORY_ENTRIES = 20;
/** Maximum specs tracked in lastSpecs history. */
const MAX_SPEC_HISTORY = 5;
/** Maximum tools tracked in lastTools history. */
const MAX_TOOL_HISTORY = 10;
/**
 * Pronoun tokens mapped to their resolution strategy.
 * Longer multi-word tokens must be checked before single words.
 */
const PRONOUN_STRATEGIES = [
    // Multi-word — checked first
    {
        tokens: [
            'la última',
            'el último',
            'la de',
            'the last',
            'the latest',
            'the current',
            'a última',
            'o último',
        ],
        strategy: 'lastSpec',
    },
    // Spanish
    { tokens: ['esta', 'este'], strategy: 'activeSpec' },
    { tokens: ['esa', 'ese', 'aquella'], strategy: 'lastSpec' },
    { tokens: ['eso', 'esto', 'aquello'], strategy: 'lastResource' },
    // English
    { tokens: ['this'], strategy: 'activeSpec' },
    { tokens: ['that'], strategy: 'lastSpec' },
    { tokens: ['it'], strategy: 'lastResource' },
    // Portuguese
    { tokens: ['essa', 'esse'], strategy: 'lastSpec' },
    { tokens: ['isso'], strategy: 'lastResource' },
];
// ---------------------------------------------------------------------------
// Factory
// ---------------------------------------------------------------------------
/**
 * Create a fresh NLIContext for a new session.
 */
export function createNLIContext(projectId) {
    return {
        sessionId: randomUUID(),
        currentProject: projectId,
        activeSpec: undefined,
        lastResource: undefined,
        lastSpecs: [],
        lastTools: [],
        conversationMemory: [],
        userPatterns: [],
        detectedLanguage: undefined,
    };
}
// ---------------------------------------------------------------------------
// Context updaters (return new context — immutable style)
// ---------------------------------------------------------------------------
/** Set the active spec, moving the previous one into lastSpecs history. */
export function setActiveSpec(ctx, specId) {
    const prev = ctx.activeSpec;
    const newLastSpecs = prev ? [prev, ...ctx.lastSpecs].slice(0, MAX_SPEC_HISTORY) : ctx.lastSpecs;
    return { ...ctx, activeSpec: specId, lastSpecs: newLastSpecs };
}
/** Update the last referenced resource. */
export function setLastResource(ctx, ref) {
    return { ...ctx, lastResource: ref };
}
/** Record a tool usage, keeping the most recent MAX_TOOL_HISTORY entries. */
export function recordToolUsage(ctx, toolName) {
    const lastTools = [toolName, ...ctx.lastTools].slice(0, MAX_TOOL_HISTORY);
    return { ...ctx, lastTools };
}
/** Set the detected language from the latest input. */
export function setDetectedLanguage(ctx, lang) {
    return { ...ctx, detectedLanguage: lang };
}
/** Append a message to conversation memory, trimming to MAX_MEMORY_ENTRIES. */
export function appendMemory(ctx, message) {
    const memory = [...ctx.conversationMemory, message].slice(-MAX_MEMORY_ENTRIES);
    return { ...ctx, conversationMemory: memory };
}
/**
 * Learn a user pattern: add it to the context, incrementing frequency
 * if the same phrase+tool already exists.
 */
export function learnPattern(ctx, pattern) {
    const now = new Date().toISOString();
    const existing = ctx.userPatterns.find((p) => p.phrase === pattern.phrase && p.mappedTool === pattern.mappedTool);
    if (existing) {
        const updated = ctx.userPatterns.map((p) => p.phrase === pattern.phrase && p.mappedTool === pattern.mappedTool
            ? { ...p, frequency: p.frequency + 1, lastUsed: now }
            : p);
        return { ...ctx, userPatterns: updated };
    }
    return {
        ...ctx,
        userPatterns: [...ctx.userPatterns, { ...pattern, lastUsed: now }],
    };
}
// ---------------------------------------------------------------------------
// Pronoun resolution
// ---------------------------------------------------------------------------
/** Resolve a single token to a concrete ID using the session context. */
export function resolvePronoun(token, ctx) {
    const lower = token.toLowerCase().trim();
    for (const { tokens, strategy } of PRONOUN_STRATEGIES) {
        if (!tokens.includes(lower)) {
            continue;
        }
        switch (strategy) {
            case 'activeSpec':
                if (ctx.activeSpec) {
                    return { resolvedId: ctx.activeSpec, resolvedLabel: ctx.activeSpec };
                }
                if (ctx.lastSpecs[0]) {
                    return { resolvedId: ctx.lastSpecs[0], resolvedLabel: ctx.lastSpecs[0] };
                }
                return null;
            case 'lastSpec': {
                const spec = ctx.lastSpecs[0] ?? ctx.activeSpec;
                return spec ? { resolvedId: spec, resolvedLabel: spec } : null;
            }
            case 'lastResource':
                return ctx.lastResource
                    ? { resolvedId: ctx.lastResource.id, resolvedLabel: ctx.lastResource.label }
                    : null;
            /* v8 ignore next 3 */
            case 'project':
                return ctx.currentProject
                    ? { resolvedId: ctx.currentProject, resolvedLabel: ctx.currentProject }
                    : null;
        }
    }
    return null;
}
/**
 * Scan input text and replace pronoun tokens with resolved IDs.
 * Multi-word pronouns are checked before single words.
 */
export function resolvePronounsInText(input, ctx) {
    let text = input;
    let wasResolved = false;
    // Multi-word pronouns first
    const multiWord = PRONOUN_STRATEGIES[0]?.tokens ?? [];
    for (const pronoun of multiWord) {
        if (text.toLowerCase().includes(pronoun)) {
            const resolution = resolvePronoun(pronoun, ctx);
            if (resolution) {
                text = text.replace(new RegExp(pronoun, 'gi'), resolution.resolvedId);
                wasResolved = true;
            }
        }
    }
    // Single-word pronouns
    const words = text.split(/\s+/);
    const resolvedWords = words.map((word) => {
        const clean = word.replace(/[.,!?;:]/g, '').toLowerCase();
        const resolution = resolvePronoun(clean, ctx);
        if (resolution) {
            wasResolved = true;
            return resolution.resolvedId;
        }
        return word;
    });
    return { resolved: resolvedWords.join(' '), wasResolved };
}
//# sourceMappingURL=nli-context.js.map