/**
 * Orphan Entity Detection Service
 *
 * Finds entities with zero inbound wikilink references across the entire graph.
 * These are "orphans" — they exist but nothing points to them.
 *
 * Supports filtering by entity type, minimum age, and actions like
 * tombstoning (sync-state) or deletion.
 */
import { MarkdownService, slugifyWikilink } from './markdown.js';
/**
 * Build a set of all entity IDs that are referenced by at least one wikilink
 * anywhere in the graph. An entity NOT in this set is an orphan.
 */
function buildReferencedSet(manager, markdown) {
    const referenced = new Set();
    const allRepos = manager.getContentRepoConfigs();
    for (const repoConfig of allRepos) {
        let graph;
        try {
            graph = manager.getGraph(repoConfig.name);
        }
        catch {
            continue;
        }
        const entities = graph.list();
        for (const entity of entities) {
            // Collect all wikilinks from this entity's content + frontmatter
            for (const link of entity.document.wikilinks) {
                // Normalize: strip repo prefix if present, then slugify
                const cleanLink = link.split('/').pop() || link;
                referenced.add(cleanLink);
                referenced.add(slugifyWikilink(cleanLink));
            }
        }
    }
    return referenced;
}
/**
 * Find all orphan entities — entities with zero inbound wikilink references.
 */
export function findOrphans(manager, filters) {
    const markdown = new MarkdownService();
    const referenced = buildReferencedSet(manager, markdown);
    const allRepos = manager.getContentRepoConfigs();
    const repos = filters?.repos
        ? allRepos.filter(r => filters.repos.includes(r.name))
        : allRepos;
    const entityTypeFilter = filters?.entityTypes
        ? new Set(filters.entityTypes)
        : null;
    const now = Date.now();
    const minAgeMs = (filters?.minAgeDays ?? 0) * 86_400_000;
    const orphans = [];
    let totalScanned = 0;
    for (const repoConfig of repos) {
        let graph;
        try {
            graph = manager.getGraph(repoConfig.name);
        }
        catch {
            continue;
        }
        const entities = graph.list();
        for (const entity of entities) {
            const entityType = entity.data.entity_type;
            // Filter by entity type
            if (entityTypeFilter && !entityTypeFilter.has(entityType))
                continue;
            totalScanned++;
            // Check if this entity is referenced anywhere
            const id = entity.id;
            if (referenced.has(id) || referenced.has(slugifyWikilink(id))) {
                continue;
            }
            // Check minimum age
            if (minAgeMs > 0) {
                const createdStr = entity.data.created_at;
                if (createdStr) {
                    const createdMs = new Date(createdStr).getTime();
                    if (now - createdMs < minAgeMs)
                        continue;
                }
            }
            orphans.push({
                repo: repoConfig.name,
                entityType,
                entityId: id,
                createdAt: entity.data.created_at,
                name: entity.data.name,
            });
        }
    }
    return {
        orphans,
        totalScanned,
        totalOrphans: orphans.length,
    };
}
//# sourceMappingURL=orphan-service.js.map