/**
 * Cross-Linker for Intelligent Living Docs Sync
 *
 * Generates cross-references between related documents for traceability.
 *
 * @module living-docs/cross-linker
 */
import * as fs from '../../utils/fs-native.js';
import path from 'path';
import { ContentCategory } from './content-classifier.js';
/**
 * Link types
 */
export var LinkType;
(function (LinkType) {
    LinkType["Implements"] = "implements";
    LinkType["DependsOn"] = "depends-on";
    LinkType["References"] = "references";
    LinkType["RelatedTo"] = "related-to";
    LinkType["DefinedIn"] = "defined-in";
    LinkType["TestsFor"] = "tests-for";
})(LinkType || (LinkType = {}));
/**
 * Cross-Linker class
 */
export class CrossLinker {
    constructor(options = {}) {
        this.options = {
            basePath: options.basePath || path.join(process.cwd(), '.specweave', 'docs', 'internal'),
            generateBacklinks: options.generateBacklinks ?? true,
            updateExisting: options.updateExisting ?? true,
            dryRun: options.dryRun ?? false,
        };
        this.links = [];
    }
    /**
     * Generate cross-links for distributed files
     */
    async generateLinks(result) {
        this.links = [];
        // Group files by category
        const byCategory = this.groupByCategory(result);
        // Generate links between categories
        await this.generateArchitectureLinks(byCategory);
        await this.generateOperationsLinks(byCategory);
        await this.generateDeliveryLinks(byCategory);
        await this.generateStrategyLinks(byCategory);
        // Generate backlinks if enabled
        if (this.options.generateBacklinks) {
            await this.generateBacklinks();
        }
        // Update documents with links
        if (this.options.updateExisting && !this.options.dryRun) {
            await this.updateDocuments();
        }
        return this.links;
    }
    /**
     * Group files by category
     */
    groupByCategory(result) {
        const groups = new Map();
        const allFiles = [...result.created, ...result.updated];
        for (const file of allFiles) {
            if (!groups.has(file.category)) {
                groups.set(file.category, []);
            }
            groups.get(file.category).push(file);
        }
        return groups;
    }
    /**
     * Generate links between two categories
     */
    async generateCategoryLinks(groups, sourceCategory, targetCategory, linkType, description) {
        const sources = groups.get(sourceCategory) || [];
        const targets = groups.get(targetCategory) || [];
        for (const source of sources) {
            for (const target of targets) {
                if (await this.documentsRelated(source.path, target.path)) {
                    this.links.push({
                        source: source.path,
                        target: target.path,
                        type: linkType,
                        description,
                    });
                }
            }
        }
    }
    /**
     * Generate links from specs to architecture
     */
    async generateArchitectureLinks(groups) {
        await this.generateCategoryLinks(groups, ContentCategory.UserStory, ContentCategory.Architecture, LinkType.Implements, 'User story implements architecture design');
        await this.generateCategoryLinks(groups, ContentCategory.UserStory, ContentCategory.ADR, LinkType.References, 'User story references architecture decision');
    }
    /**
     * Generate links from operations to architecture
     */
    async generateOperationsLinks(groups) {
        await this.generateCategoryLinks(groups, ContentCategory.Operations, ContentCategory.Architecture, LinkType.DependsOn, 'Operational procedure depends on architecture');
    }
    /**
     * Generate links from delivery to specs
     */
    async generateDeliveryLinks(groups) {
        await this.generateCategoryLinks(groups, ContentCategory.Delivery, ContentCategory.UserStory, LinkType.TestsFor, 'Test strategy covers user story');
    }
    /**
     * Generate links from strategy to specs
     */
    async generateStrategyLinks(groups) {
        await this.generateCategoryLinks(groups, ContentCategory.Strategy, ContentCategory.UserStory, LinkType.DefinedIn, 'Business requirement defined in strategy');
    }
    /**
     * Check if two documents are related (heuristic)
     */
    async documentsRelated(path1, path2) {
        try {
            // Extract key terms from filenames
            const name1 = path.basename(path1, '.md').toLowerCase();
            const name2 = path.basename(path2, '.md').toLowerCase();
            // Simple heuristic: if they share significant words
            const words1 = name1.split(/[-_]/).filter((w) => w.length > 3);
            const words2 = name2.split(/[-_]/).filter((w) => w.length > 3);
            const sharedWords = words1.filter((w) => words2.includes(w));
            // If 2+ shared words, consider related
            if (sharedWords.length >= 2) {
                return true;
            }
            // Check content for cross-references (more expensive)
            if (fs.existsSync(path1) && fs.existsSync(path2)) {
                const content1 = await fs.readFile(path1, 'utf-8');
                const content2 = await fs.readFile(path2, 'utf-8');
                // Check if path1 mentions path2's filename
                const name2Clean = path.basename(path2, '.md');
                if (content1.toLowerCase().includes(name2Clean.toLowerCase())) {
                    return true;
                }
                // Check if path2 mentions path1's filename
                const name1Clean = path.basename(path1, '.md');
                if (content2.toLowerCase().includes(name1Clean.toLowerCase())) {
                    return true;
                }
            }
            return false;
        }
        catch (error) {
            return false;
        }
    }
    /**
     * Generate backlinks (bidirectional)
     */
    async generateBacklinks() {
        const backlinks = [];
        for (const link of this.links) {
            // Create reverse link
            backlinks.push({
                source: link.target,
                target: link.source,
                type: this.getReverseType(link.type),
                description: this.getReverseDescription(link),
            });
        }
        this.links.push(...backlinks);
    }
    /**
     * Get reverse link type
     */
    getReverseType(type) {
        const reverseMap = {
            [LinkType.Implements]: LinkType.DefinedIn,
            [LinkType.DependsOn]: LinkType.DefinedIn,
            [LinkType.References]: LinkType.References,
            [LinkType.RelatedTo]: LinkType.RelatedTo,
            [LinkType.DefinedIn]: LinkType.Implements,
            [LinkType.TestsFor]: LinkType.DefinedIn,
        };
        return reverseMap[type] || LinkType.RelatedTo;
    }
    /**
     * Get reverse description
     */
    getReverseDescription(link) {
        const name = path.basename(link.source, '.md').replace(/-/g, ' ');
        return `Referenced by ${name}`;
    }
    /**
     * Update documents with generated links
     */
    async updateDocuments() {
        // Group links by source file
        const linksBySource = new Map();
        for (const link of this.links) {
            if (!linksBySource.has(link.source)) {
                linksBySource.set(link.source, []);
            }
            linksBySource.get(link.source).push(link);
        }
        // Update each source file
        for (const [sourcePath, links] of linksBySource.entries()) {
            await this.updateDocument(sourcePath, links);
        }
    }
    /**
     * Update a single document with links
     */
    async updateDocument(filePath, links) {
        if (!fs.existsSync(filePath)) {
            return;
        }
        try {
            let content = await fs.readFile(filePath, 'utf-8');
            // Check if links section already exists
            const linksSection = '\n\n## Related Documents\n\n';
            const hasLinksSection = content.includes('## Related Documents');
            if (hasLinksSection) {
                // Update existing section
                const regex = /## Related Documents\n\n[\s\S]*?(?=\n## |\n---|\n\*\*Source\*\*|$)/;
                content = content.replace(regex, linksSection + this.generateLinksMarkdown(links));
            }
            else {
                // Add new section before footer
                const footerIndex = content.lastIndexOf('\n---\n');
                if (footerIndex !== -1) {
                    const before = content.substring(0, footerIndex);
                    const after = content.substring(footerIndex);
                    content = before + linksSection + this.generateLinksMarkdown(links) + '\n' + after;
                }
                else {
                    // No footer - append to end
                    content += linksSection + this.generateLinksMarkdown(links);
                }
            }
            // Write updated content
            await fs.writeFile(filePath, content, 'utf-8');
        }
        catch (error) {
            console.warn(`Failed to update links in ${filePath}:`, error);
        }
    }
    /**
     * Generate markdown for links section
     */
    generateLinksMarkdown(links) {
        let markdown = '';
        // Group by type
        const byType = new Map();
        for (const link of links) {
            if (!byType.has(link.type)) {
                byType.set(link.type, []);
            }
            byType.get(link.type).push(link);
        }
        // Generate sections for each type
        for (const [type, typeLinks] of byType.entries()) {
            markdown += `### ${this.getLinkTypeTitle(type)}\n\n`;
            for (const link of typeLinks) {
                const relativePath = this.getRelativePath(link.source, link.target);
                const title = path.basename(link.target, '.md').replace(/-/g, ' ');
                markdown += `- [${title}](${relativePath})`;
                if (link.description) {
                    markdown += ` - ${link.description}`;
                }
                markdown += '\n';
            }
            markdown += '\n';
        }
        return markdown;
    }
    /**
     * Get link type display title
     */
    getLinkTypeTitle(type) {
        const titles = {
            [LinkType.Implements]: 'Implements',
            [LinkType.DependsOn]: 'Depends On',
            [LinkType.References]: 'References',
            [LinkType.RelatedTo]: 'Related To',
            [LinkType.DefinedIn]: 'Defined In',
            [LinkType.TestsFor]: 'Tests For',
        };
        return titles[type] || type;
    }
    /**
     * Get relative path from source to target
     */
    getRelativePath(source, target) {
        const sourceDir = path.dirname(source);
        return path.relative(sourceDir, target);
    }
    /**
     * Get all links
     */
    getLinks() {
        return this.links;
    }
    /**
     * Get links for a specific file
     */
    getLinksFor(filePath) {
        return this.links.filter((link) => link.source === filePath);
    }
    /**
     * Get statistics
     */
    getStatistics() {
        const stats = {
            total: this.links.length,
            byType: {},
            bidirectional: 0,
        };
        for (const link of this.links) {
            stats.byType[link.type] = (stats.byType[link.type] || 0) + 1;
        }
        // Count bidirectional (simplified)
        stats.bidirectional = this.links.filter((link) => this.links.some((l) => l.source === link.target && l.target === link.source)).length / 2;
        return stats;
    }
}
/**
 * Factory function to create linker
 */
export function createCrossLinker(options) {
    return new CrossLinker(options);
}
//# sourceMappingURL=cross-linker.js.map