import { KnowledgeSafetyService } from '../security/knowledge-safety.js';
import { atomEmbeddingText } from './critic.js';
import { redactAtomInput } from './redaction.js';
import { inferSemanticNeighbors } from './inference/semantic-neighbor.js';
import { syncAtomLinks } from './inference/sync.js';
import { getRetrievalPolicy } from '../retrieval/policy.js';
export class AtomExtractor {
    store;
    models;
    critic;
    safety;
    constructor(store, models, critic, safety) {
        this.store = store;
        this.models = models;
        this.critic = critic;
        this.safety = safety ?? new KnowledgeSafetyService();
    }
    async extractFromSession(input) {
        if (!this.models.extractAtoms) {
            return { stored: [], rejected: [], queuedLegacyMigrations: [] };
        }
        const candidates = await this.models.extractAtoms({
            project: input.project,
            sessionPrompt: input.sessionPrompt,
            summary: input.summary,
            changedFiles: input.changedFiles,
            decisions: input.decisions,
            verificationCommands: input.verificationCommands,
        });
        const stored = [];
        const rejected = [];
        const queuedLegacyMigrations = [];
        for (const candidate of candidates) {
            const rawInput = {
                project: input.project,
                claim: candidate.claim,
                type: candidate.type,
                evidence: candidate.evidence,
                trigger: candidate.trigger,
                verification: candidate.verification,
                pitfalls: candidate.pitfalls,
                producedBy: 'agent_session',
                producedAtSessionId: input.sessionId,
            };
            // Redact secrets before the critic embeds and before storage so the
            // stored + embedded text never contains raw secrets.
            const candidateInput = redactAtomInput(rawInput, this.safety);
            const result = await this.critic.evaluate(candidateInput, input.sessionId);
            if (result.outcome === 'accepted' || result.outcome === 'pending') {
                // 'pending' means the LLM critic was unavailable for a borderline atom;
                // we keep it (fail-open) rather than dropping a potentially useful lesson.
                const embedding = await this.models.embed(atomEmbeddingText(candidateInput));
                const created = await this.store.createAtom({ ...candidateInput, embedding });
                // Concern C1 — inline semantic-neighbor inference. Best-effort: a
                // failure here must not lose the atom we just accepted.
                if (getRetrievalPolicy().graphInference.enabled) {
                    try {
                        const links = await inferSemanticNeighbors(created, this.store, this.models);
                        if (links.length) {
                            await syncAtomLinks(created.id, links, this.store, 'semantic');
                        }
                    }
                    catch (error) {
                        process.stderr.write(`[atom-inference] semantic-neighbor failed for ${created.id}: ${error.message}\n`);
                    }
                }
                stored.push((await this.store.getAtom(created.id)) ?? created);
            }
            else if (result.outcome === 'queue_legacy_migration' && result.legacyKnowledgeIdForMigration) {
                // Near-duplicate of a vague legacy memory — surface it for migration
                // instead of storing a competing atom or logging a (misleading) gap.
                queuedLegacyMigrations.push(result.legacyKnowledgeIdForMigration);
            }
            else {
                rejected.push({ candidate: candidateInput, reasons: result.reasons });
            }
        }
        return { stored, rejected, queuedLegacyMigrations };
    }
}
