import * as fs from 'node:fs';
import * as path from 'node:path';
import { hashLesson } from './compiler.js';
import { TotemParseError } from './errors.js';
// ─── Constants ─────────────────────────────────────────
export const SENTINEL_START = '<!-- totem:generated:start -->';
export const SENTINEL_END = '<!-- totem:generated:end -->';
// ─── Markdown formatting ─────────────────────────────
/**
 * Format parsed lessons as a markdown block for injection into AI config files.
 * Always generates sentinel markers (even for empty lessons) to preserve idempotency.
 *
 * When `archivedReasonByHash` is provided, lessons whose content-hash is in the
 * map get an `_(archived: <reason>)_` suffix so a reading agent can weight the
 * guidance appropriately. Empty / missing reasons fall back to a plain bullet
 * (Tenet 4 spirit — surface the rule loudly even with degraded metadata).
 * Per mmnto-ai/totem#1873.
 */
export function formatLessonsAsMarkdown(lessons, archivedReasonByHash) {
    const lines = [
        SENTINEL_START,
        '<!-- Auto-generated by `totem compile --export`. Do not edit between these markers. -->',
        '',
        '## Totem Project Rules',
        '',
    ];
    const escapeMarkdown = (s) => s.replace(/[*_]/g, '\\$&');
    // Strip C0 control bytes (incl. CR/LF/TAB) so an archivedReason copied
    // from an operator's terminal session cannot break the bullet shape or
    // bleed across lines. Mirrors the sanitizing discipline applied to
    // lesson body content above (per CR mmnto-ai/totem#1878).
    const sanitizeReason = (s) => escapeMarkdown(s.replace(/[\x00-\x1F\x7F]/g, ' ').trim());
    for (const lesson of lessons) {
        const heading = escapeMarkdown(lesson.heading);
        const bodyOneLine = escapeMarkdown(lesson.body.replace(/\n+/g, ' ').trim());
        const tagSuffix = lesson.tags.length > 0 ? ` _(${lesson.tags.join(', ')})_` : '';
        const rawReason = archivedReasonByHash
            ? archivedReasonByHash.get(hashLesson(lesson.heading, lesson.body))
            : undefined;
        const cleanedReason = rawReason ? sanitizeReason(rawReason) : '';
        const archivedSuffix = cleanedReason ? ` _(archived: ${cleanedReason})_` : '';
        lines.push(`- **${heading}** - ` + bodyOneLine + tagSuffix + archivedSuffix);
    }
    lines.push('', SENTINEL_END);
    return lines.join('\n');
}
// ─── Sentinel injection ──────────────────────────────
/**
 * Inject a generated block between sentinel markers in file content.
 * - If sentinels exist: replaces content between them (inclusive)
 * - If no sentinels: appends block to end of file
 * - If mismatched sentinels (start without end): throws error
 */
export function injectSentinelBlock(existingContent, generatedBlock) {
    const startIdx = existingContent.indexOf(SENTINEL_START);
    const endIdx = existingContent.indexOf(SENTINEL_END);
    if (startIdx !== -1 && endIdx === -1) {
        throw new TotemParseError(`Found ${SENTINEL_START} without matching ${SENTINEL_END}.`, 'Fix the target file manually — ensure both sentinel markers are present and correctly ordered.');
    }
    if (startIdx !== -1 && endIdx !== -1 && endIdx < startIdx) {
        throw new TotemParseError(`Found ${SENTINEL_END} before ${SENTINEL_START}.`, 'Fix the target file manually — ensure sentinel markers appear in the correct order.');
    }
    if (startIdx !== -1 && endIdx !== -1) {
        const before = existingContent.slice(0, startIdx);
        const after = existingContent.slice(endIdx + SENTINEL_END.length);
        return before + generatedBlock + after;
    }
    // No sentinels — append to end (no-op if nothing to inject)
    if (generatedBlock === '')
        return existingContent;
    if (existingContent.trim() === '')
        return generatedBlock + '\n';
    const separator = existingContent.endsWith('\n') ? '\n' : '\n\n';
    return existingContent + separator + generatedBlock + '\n';
}
// ─── File export ──────────────────────────────────────
/**
 * Export formatted lessons to a target file path.
 * Creates directories if needed. Uses sentinel-based injection for idempotent updates.
 *
 * When `archivedReasonByHash` is provided, lessons whose content-hash appears
 * in the map render with an `_(archived: <reason>)_` annotation suffix
 * (mmnto-ai/totem#1873).
 */
export function exportLessons(lessons, targetPath, archivedReasonByHash) {
    const dir = path.dirname(targetPath);
    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
    }
    const generatedBlock = formatLessonsAsMarkdown(lessons, archivedReasonByHash);
    if (fs.existsSync(targetPath)) {
        const existing = fs.readFileSync(targetPath, 'utf-8');
        const updated = injectSentinelBlock(existing, generatedBlock);
        fs.writeFileSync(targetPath, updated, 'utf-8');
    }
    else {
        fs.writeFileSync(targetPath, generatedBlock + '\n', 'utf-8');
    }
}
//# sourceMappingURL=exporter.js.map