/**
 * Manifest Validator
 *
 * Validates SpecVerse manifests against registry metadata.
 * Checks that all instance factories referenced in the manifest exist
 * in the registry and validates capability mappings.
 *
 * @module registry/validators/manifest-validator
 * @version 2.0.0
 */
import { OfflineHandler } from '../offline/offline-handler.js';
import { createIssue, withSuggestions, withLearnMore, ErrorCodes, LearnMoreUrls } from '../types/index.js';
/**
 * Manifest Validator
 *
 * Validates manifests against registry metadata.
 */
export class ManifestValidator {
    offlineHandler;
    constructor(offlineHandler) {
        this.offlineHandler = offlineHandler || new OfflineHandler();
    }
    /**
     * Validate manifest
     *
     * @param manifest - Manifest to validate
     * @param options - Validation options
     * @returns Validation result with errors, warnings, and info
     */
    async validate(manifest, options = {}) {
        const errors = [];
        const warnings = [];
        const info = [];
        const filePath = options.filePath || 'manifests/implementation.yaml';
        // Enable offline mode if requested
        if (options.offline) {
            this.offlineHandler.enableOfflineMode();
        }
        try {
            // Fetch registry metadata
            const registryResult = await this.offlineHandler.fetchFactories();
            // Add any registry fetch warnings
            warnings.push(...registryResult.issues);
            // Build factory lookup map
            const factoryMap = new Map();
            for (const factory of registryResult.data.results) {
                factoryMap.set(factory.name, factory);
            }
            // Validate instance factories
            for (const instanceFactory of manifest.instanceFactories) {
                const registryFactory = factoryMap.get(instanceFactory.name);
                if (!registryFactory) {
                    // Factory not found in registry
                    const similar = this.findSimilarFactories(instanceFactory.name, Array.from(factoryMap.keys()));
                    const issue = createIssue(ErrorCodes.FACTORY_NOT_FOUND, 'error', 'manifest', 'Instance factory not found', `The instance factory "${instanceFactory.name}" was not found in the registry.`, filePath);
                    if (similar.length > 0) {
                        errors.push(withLearnMore(withSuggestions(issue, [
                            `Did you mean "${similar[0]}"?`,
                            ...similar.slice(1, 3).map(name => `Or try: "${name}"`),
                            'Run "specverse lib search <query>" to find factories'
                        ]), LearnMoreUrls.REGISTRY));
                    }
                    else {
                        errors.push(withLearnMore(withSuggestions(issue, [
                            'Check the factory name spelling',
                            'Run "specverse lib list" to see available factories',
                            'The factory may not be published yet'
                        ]), LearnMoreUrls.REGISTRY));
                    }
                    continue;
                }
                // Check if deprecated
                if (!options.skipDeprecated && registryFactory.deprecated) {
                    const issue = createIssue(ErrorCodes.FACTORY_DEPRECATED, options.strict ? 'error' : 'warning', 'manifest', 'Instance factory is deprecated', `The instance factory "${instanceFactory.name}" is deprecated${registryFactory.deprecationReason ? ': ' + registryFactory.deprecationReason : ''}`, filePath);
                    const suggestions = [];
                    if (registryFactory.replacedBy) {
                        suggestions.push(`Use "${registryFactory.replacedBy}" instead`);
                    }
                    if (registryFactory.alternatives && registryFactory.alternatives.length > 0) {
                        suggestions.push(`Alternatives: ${registryFactory.alternatives.slice(0, 3).join(', ')}`);
                    }
                    suggestions.push('Update your manifest to use a supported factory');
                    const deprecatedIssue = withLearnMore(withSuggestions(issue, suggestions), LearnMoreUrls.MIGRATION);
                    if (options.strict) {
                        errors.push(deprecatedIssue);
                    }
                    else {
                        warnings.push(deprecatedIssue);
                    }
                }
            }
            // Validate capability mappings
            if (manifest.capabilityMappings) {
                for (const mapping of manifest.capabilityMappings) {
                    const factory = factoryMap.get(mapping.instanceFactory);
                    if (!factory) {
                        // Factory not found (already reported above, skip duplicate)
                        continue;
                    }
                    // Check if factory provides the capability
                    if (!factory.capabilities.includes(mapping.capability)) {
                        const issue = createIssue(ErrorCodes.INVALID_CAPABILITY_MAPPING, 'error', 'manifest', 'Invalid capability mapping', `The instance factory "${mapping.instanceFactory}" does not provide capability "${mapping.capability}". ` +
                            `Available capabilities: ${factory.capabilities.join(', ')}`, filePath);
                        // Find factories that DO provide this capability
                        const providingFactories = registryResult.data.results
                            .filter(f => f.capabilities.includes(mapping.capability))
                            .map(f => f.name)
                            .slice(0, 5);
                        if (providingFactories.length > 0) {
                            errors.push(withSuggestions(issue, [
                                `Use one of these factories instead: ${providingFactories.join(', ')}`,
                                `Or remove the capability mapping for "${mapping.capability}"`
                            ]));
                        }
                        else {
                            errors.push(withSuggestions(issue, [
                                `No factories provide capability "${mapping.capability}"`,
                                'Check the capability name spelling',
                                'The capability may not be supported yet'
                            ]));
                        }
                    }
                }
            }
            // Check for unused factories (info)
            const usedFactoryNames = new Set((manifest.capabilityMappings || []).map(m => m.instanceFactory));
            for (const instanceFactory of manifest.instanceFactories) {
                if (!usedFactoryNames.has(instanceFactory.name)) {
                    info.push(createIssue('MANIFEST-003', 'info', 'manifest', 'Unused instance factory', `The instance factory "${instanceFactory.name}" is declared but not mapped to any capability.`, filePath));
                }
            }
        }
        catch (error) {
            // Handle offline errors
            if (error.name === 'OfflineError') {
                errors.push(withLearnMore(withSuggestions(createIssue(ErrorCodes.NETWORK_ERROR, 'error', 'manifest', 'Registry unavailable', 'Cannot validate manifest: registry is unavailable and no cache exists.', filePath), [
                    'Check your internet connection',
                    'Run with network access to populate cache',
                    'Use --offline flag with existing cache'
                ]), LearnMoreUrls.REGISTRY));
            }
            else {
                // Unexpected error
                errors.push(createIssue('MANIFEST-999', 'error', 'manifest', 'Validation error', `Unexpected error during validation: ${error.message}`, filePath));
            }
        }
        return {
            valid: errors.length === 0,
            errors,
            warnings,
            info
        };
    }
    /**
     * Find similar factory names using Levenshtein distance
     *
     * @param target - Target name
     * @param candidates - Candidate names
     * @returns Top 5 similar names
     */
    findSimilarFactories(target, candidates) {
        const scored = candidates.map(candidate => ({
            name: candidate,
            distance: this.levenshteinDistance(target.toLowerCase(), candidate.toLowerCase())
        }));
        // Sort by distance (lower is better)
        scored.sort((a, b) => a.distance - b.distance);
        // Return top 5 with distance <= 5
        return scored
            .filter(s => s.distance <= 5)
            .slice(0, 5)
            .map(s => s.name);
    }
    /**
     * Calculate Levenshtein distance between two strings
     *
     * @param a - First string
     * @param b - Second string
     * @returns Edit distance
     */
    levenshteinDistance(a, b) {
        const matrix = [];
        for (let i = 0; i <= b.length; i++) {
            matrix[i] = [i];
        }
        for (let j = 0; j <= a.length; j++) {
            matrix[0][j] = j;
        }
        for (let i = 1; i <= b.length; i++) {
            for (let j = 1; j <= a.length; j++) {
                if (b.charAt(i - 1) === a.charAt(j - 1)) {
                    matrix[i][j] = matrix[i - 1][j - 1];
                }
                else {
                    matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
                    matrix[i][j - 1] + 1, // insertion
                    matrix[i - 1][j] + 1 // deletion
                    );
                }
            }
        }
        return matrix[b.length][a.length];
    }
    /**
     * Get offline handler
     *
     * @returns Offline handler instance
     */
    getOfflineHandler() {
        return this.offlineHandler;
    }
}
//# sourceMappingURL=manifest-validator.js.map