/**
 * Comments: sidecar JSON storage for inline user feedback (formerly "agent marks").
 * Each document gets a sidecar file at DATA_DIR/_marks/{filename}.json.
 * Storage directory name `_marks/` is retained for backwards compatibility with
 * existing user data; the public vocabulary is "comment" everywhere else.
 */
import { join } from 'path';
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, unlinkSync, renameSync } from 'fs';
import { randomUUID } from 'crypto';
import { getDataDir, ensureDataDir } from './helpers.js';
function isResolved(c) {
    return typeof c.resolvedAt === 'string' && c.resolvedAt.length > 0;
}
function getCommentsDir() { return join(getDataDir(), '_marks'); }
function ensureCommentsDir() {
    ensureDataDir();
    if (!existsSync(getCommentsDir()))
        mkdirSync(getCommentsDir(), { recursive: true });
}
function commentFilePath(filename) {
    const safe = filename.replace(/[/\\]/g, '_');
    return join(getCommentsDir(), `${safe}.json`);
}
function readCommentFile(filename) {
    const path = commentFilePath(filename);
    if (!existsSync(path))
        return { marks: [] };
    try {
        return JSON.parse(readFileSync(path, 'utf-8'));
    }
    catch {
        return { marks: [] };
    }
}
function writeCommentFile(filename, data) {
    ensureCommentsDir();
    const path = commentFilePath(filename);
    if (data.marks.length === 0) {
        if (existsSync(path))
            unlinkSync(path);
        return;
    }
    writeFileSync(path, JSON.stringify(data, null, 2));
}
export function addComment(filename, text, note, nodeId, nodeIds) {
    const data = readCommentFile(filename);
    const comment = {
        id: randomUUID().slice(0, 8),
        text,
        note,
        nodeId,
        ...(nodeIds && nodeIds.length > 1 ? { nodeIds } : {}),
        createdAt: new Date().toISOString(),
    };
    data.marks.push(comment);
    writeCommentFile(filename, data);
    return comment;
}
export function getComments(filename, opts = {}) {
    const keep = (list) => opts.includeResolved ? list : list.filter((c) => !isResolved(c));
    if (filename) {
        const data = readCommentFile(filename);
        const list = keep(data.marks);
        if (list.length === 0)
            return {};
        return { [filename]: list };
    }
    ensureCommentsDir();
    const result = {};
    try {
        const files = readdirSync(getCommentsDir());
        for (const file of files) {
            if (!file.endsWith('.json'))
                continue;
            const docFilename = file.replace(/\.json$/, '').replace(/_/g, ' ');
            const path = join(getCommentsDir(), file);
            try {
                const data = JSON.parse(readFileSync(path, 'utf-8'));
                const list = keep(data.marks);
                if (list.length > 0)
                    result[docFilename] = list;
            }
            catch { /* skip corrupt files */ }
        }
    }
    catch { /* dir doesn't exist yet */ }
    return result;
}
export function getCommentCount(filename) {
    return readCommentFile(filename).marks.filter((c) => !isResolved(c)).length;
}
/** Count unresolved comments across all documents, optionally excluding one filename. */
export function getGlobalCommentSummary(excludeFilename) {
    ensureCommentsDir();
    let totalComments = 0;
    let docCount = 0;
    try {
        const files = readdirSync(getCommentsDir());
        for (const file of files) {
            if (!file.endsWith('.json'))
                continue;
            if (excludeFilename) {
                const safe = excludeFilename.replace(/[/\\]/g, '_');
                if (file === `${safe}.json`)
                    continue;
            }
            const path = join(getCommentsDir(), file);
            try {
                const data = JSON.parse(readFileSync(path, 'utf-8'));
                const unresolved = data.marks.filter((c) => !isResolved(c));
                if (unresolved.length > 0) {
                    totalComments += unresolved.length;
                    docCount++;
                }
            }
            catch { /* skip */ }
        }
    }
    catch { /* dir doesn't exist */ }
    return { totalComments, docCount };
}
export function editComment(filename, id, note) {
    const data = readCommentFile(filename);
    const comment = data.marks.find((m) => m.id === id);
    if (!comment)
        return null;
    comment.note = note;
    writeCommentFile(filename, data);
    return comment;
}
/** Mark comments as resolved (state change, NOT deletion). The records stay
 *  on disk but get filtered out of normal `getComments` listings — so the
 *  decoration disappears in the browser without losing the history. */
export function resolveComments(ids) {
    const idSet = new Set(ids);
    const resolved = [];
    const now = new Date().toISOString();
    ensureCommentsDir();
    try {
        const files = readdirSync(getCommentsDir());
        for (const file of files) {
            if (!file.endsWith('.json'))
                continue;
            const filePath = join(getCommentsDir(), file);
            try {
                const data = JSON.parse(readFileSync(filePath, 'utf-8'));
                let changed = false;
                for (const c of data.marks) {
                    if (idSet.has(c.id) && !isResolved(c)) {
                        c.resolvedAt = now;
                        resolved.push(c.id);
                        changed = true;
                    }
                }
                if (changed) {
                    const docFilename = file.replace(/\.json$/, '').replace(/_/g, ' ');
                    writeCommentFile(docFilename, data);
                }
            }
            catch { /* skip */ }
        }
    }
    catch { /* dir doesn't exist */ }
    return resolved;
}
/** Clear the resolved state on comments. Inverse of resolveComments. */
export function unresolveComments(ids) {
    const idSet = new Set(ids);
    const cleared = [];
    ensureCommentsDir();
    try {
        const files = readdirSync(getCommentsDir());
        for (const file of files) {
            if (!file.endsWith('.json'))
                continue;
            const filePath = join(getCommentsDir(), file);
            try {
                const data = JSON.parse(readFileSync(filePath, 'utf-8'));
                let changed = false;
                for (const c of data.marks) {
                    if (idSet.has(c.id) && isResolved(c)) {
                        delete c.resolvedAt;
                        cleared.push(c.id);
                        changed = true;
                    }
                }
                if (changed) {
                    const docFilename = file.replace(/\.json$/, '').replace(/_/g, ' ');
                    writeCommentFile(docFilename, data);
                }
            }
            catch { /* skip */ }
        }
    }
    catch { /* dir doesn't exist */ }
    return cleared;
}
/** Permanently remove comments from the sidecar. Distinct from resolveComments —
 *  resolve is a state change ("addressed, archive it"), delete is the destructive
 *  "this record never should have existed" path. */
export function deleteComments(ids) {
    const idSet = new Set(ids);
    const deleted = [];
    ensureCommentsDir();
    try {
        const files = readdirSync(getCommentsDir());
        for (const file of files) {
            if (!file.endsWith('.json'))
                continue;
            const filePath = join(getCommentsDir(), file);
            try {
                const data = JSON.parse(readFileSync(filePath, 'utf-8'));
                const before = data.marks.length;
                data.marks = data.marks.filter((m) => {
                    if (idSet.has(m.id)) {
                        deleted.push(m.id);
                        return false;
                    }
                    return true;
                });
                if (data.marks.length !== before) {
                    const docFilename = file.replace(/\.json$/, '').replace(/_/g, ' ');
                    writeCommentFile(docFilename, data);
                }
            }
            catch { /* skip */ }
        }
    }
    catch { /* dir doesn't exist */ }
    return deleted;
}
export function pruneStaleComments(filename, validNodeIds) {
    const data = readCommentFile(filename);
    if (data.marks.length === 0)
        return 0;
    const validSet = new Set(validNodeIds);
    const before = data.marks.length;
    data.marks = data.marks.filter((m) => {
        if (m.nodeIds && m.nodeIds.length > 0) {
            return m.nodeIds.some((id) => validSet.has(id));
        }
        return validSet.has(m.nodeId);
    });
    const pruned = before - data.marks.length;
    if (pruned > 0)
        writeCommentFile(filename, data);
    return pruned;
}
/** Rename a comment sidecar file when a document is renamed. */
export function renameComments(oldFilename, newFilename) {
    const oldPath = commentFilePath(oldFilename);
    if (!existsSync(oldPath))
        return;
    const newPath = commentFilePath(newFilename);
    renameSync(oldPath, newPath);
}
