/**
 * Per-commit "what changed" summary for the entity History panel.
 *
 * The panel shows one entity's history, so instead of repeating the entity's
 * name/type on every row (the friendly commit message — see commit-messages.ts)
 * it shows what actually changed at each commit: which frontmatter fields moved
 * and whether the body was edited. Computed at display time by diffing the file
 * at a commit against its parent, so it works retroactively on existing history.
 *
 * Frontmatter is parsed with js-yaml directly (not the shared gray-matter
 * MarkdownService) on purpose: gray-matter's module-level cache returned a
 * stale entity_type for historical git content in this flow, which would
 * corrupt the diff. js-yaml is a pure parse with no cache.
 */
import diff from 'microdiff';
import yaml from 'js-yaml';
/** Bookkeeping frontmatter that changes on every write — noise in a change feed. */
const IGNORED_KEYS = new Set(['updated_at', 'updated_by', 'created_at', 'created_by']);
/** Whitespace-delimited word counts. */
function wordBag(text) {
    const bag = new Map();
    for (const w of text.match(/\S+/g) ?? [])
        bag.set(w, (bag.get(w) ?? 0) + 1);
    return bag;
}
/**
 * Approximate words added/removed between two bodies via a multiset diff —
 * order-insensitive, no LCS. A reworded word counts as one added + one removed;
 * a moved word counts as neither. Good enough for a magnitude indicator (the
 * UI prefixes counts with ~). Cheap: two tokenizations + a key walk.
 */
function wordDelta(prev, cur) {
    const a = wordBag(prev);
    const b = wordBag(cur);
    let wordsAdded = 0;
    let wordsRemoved = 0;
    for (const key of new Set([...a.keys(), ...b.keys()])) {
        const d = (b.get(key) ?? 0) - (a.get(key) ?? 0);
        if (d > 0)
            wordsAdded += d;
        else
            wordsRemoved += -d;
    }
    return { wordsAdded, wordsRemoved };
}
/**
 * Parse a markdown doc's YAML frontmatter directly, with no cache. Use this for
 * HISTORICAL git content: gray-matter's shared MarkdownService.parseContent has
 * a module-level cache that returned a stale entity_type for past-commit content
 * in this codebase. js-yaml is a pure parse. Returns {} when there's no
 * frontmatter or it doesn't parse.
 */
export function parseFrontmatterCacheFree(raw) {
    const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
    if (!m)
        return {};
    try {
        return yaml.load(m[1]) ?? {};
    }
    catch {
        return {};
    }
}
/** Split a markdown doc into frontmatter + body via a direct YAML parse. */
function splitDoc(raw) {
    const bodyMatch = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
    return {
        frontmatter: parseFrontmatterCacheFree(raw),
        body: bodyMatch ? bodyMatch[1] ?? '' : raw,
    };
}
/**
 * Compute the change summary between a parent and current version of one
 * entity file. `parentRaw === null` means the file didn't exist at the parent
 * commit (created here, or — under the no-`--follow` limitation — renamed into
 * this path).
 */
export function computeChange(currentRaw, parentRaw) {
    const cur = splitDoc(currentRaw);
    if (parentRaw === null) {
        return { created: true, fields: [], body: null };
    }
    const prev = splitDoc(parentRaw);
    const fields = [];
    for (const d of diff(prev.frontmatter, cur.frontmatter)) {
        const key = String(d.path[0]);
        if (IGNORED_KEYS.has(key))
            continue;
        if (fields.some((f) => f.key === key))
            continue; // collapse nested/array deltas to one field
        // Only top-level scalar moves get a clean add/remove/change op + from/to.
        // Nested or array deltas (path length > 1) collapse to a generic "change"
        // on the owning field — we can't express element-level add/remove at field
        // granularity, and "Tags changed" reads cleaner than "Tags added" anyway.
        if (d.path.length > 1) {
            fields.push({ key, op: 'change' });
        }
        else if (d.type === 'CREATE') {
            fields.push({ key, op: 'add', to: d.value });
        }
        else if (d.type === 'REMOVE') {
            fields.push({ key, op: 'remove', from: d.oldValue });
        }
        else {
            fields.push({ key, op: 'change', from: d.oldValue, to: d.value });
        }
    }
    return {
        created: false,
        fields,
        body: cur.body === prev.body ? null : wordDelta(prev.body, cur.body),
    };
}
//# sourceMappingURL=history-diff.js.map