/**
 * Dependency Management System für Module
 * Verwaltet Dependencies und Initialisierungsreihenfolge
 */
import { Logger } from './logger.js';

/**
 * @typedef {Object} ModuleDependency
 * @property {string} name - Module name
 * @property {string} version - Required version (semver)
 * @property {boolean} optional - Whether dependency is optional
 */

/**
 * @typedef {Object} ModuleDefinition
 * @property {string} name - Module name
 * @property {string} version - Module version
 * @property {ModuleDependency[]} dependencies - Required dependencies
 * @property {string[]} provides - Services this module provides
 * @property {number} priority - Initialization priority (higher = earlier)
 */

export class DependencyManager {
    constructor() {
        /** @type {Map<string, ModuleDefinition>} */
        this.modules = new Map();
        
        /** @type {Map<string, string[]>} */
        this.dependents = new Map(); // who depends on this module
        
        /** @type {Set<string>} */
        this.initialized = new Set();
        
        /** @type {Set<string>} */
        this.initializing = new Set();
        
        /** @type {string[]} */
        this.initializationOrder = [];
    }

    /**
     * Register a module with its dependencies
     * @param {ModuleDefinition} definition - Module definition
     */
    register(definition) {
        if (this.modules.has(definition.name)) {
            Logger.warn(`[DependencyManager] Module '${definition.name}' already registered`);
            return;
        }

        // Validate definition
        if (!this.validateDefinition(definition)) {
            return;
        }

        this.modules.set(definition.name, definition);
        
        // Build dependents map
        definition.dependencies.forEach(dep => {
            if (!this.dependents.has(dep.name)) {
                this.dependents.set(dep.name, []);
            }
            this.dependents.get(dep.name).push(definition.name);
        });

        Logger.info(`[DependencyManager] Registered '${definition.name}' v${definition.version}`);
    }

    /**
     * Calculate initialization order based on dependencies
     * @returns {string[]} Ordered list of module names
     */
    calculateInitializationOrder() {
        const visited = new Set();
        const visiting = new Set();
        const order = [];

        /**
         * Depth-first search with cycle detection
         * @param {string} moduleName 
         */
        const visit = (moduleName) => {
            if (visiting.has(moduleName)) {
                throw new Error(`Circular dependency detected involving '${moduleName}'`);
            }
            
            if (visited.has(moduleName)) {
                return;
            }

            const module = this.modules.get(moduleName);
            if (!module) {
                return; // Module not registered, might be optional
            }

            visiting.add(moduleName);

            // Visit dependencies first
            module.dependencies.forEach(dep => {
                if (!dep.optional || this.modules.has(dep.name)) {
                    visit(dep.name);
                }
            });

            visiting.delete(moduleName);
            visited.add(moduleName);
            order.push(moduleName);
        };

        // Sort by priority first, then resolve dependencies
        const modulesByPriority = Array.from(this.modules.entries())
            .sort(([, a], [, b]) => (b.priority || 0) - (a.priority || 0))
            .map(([name]) => name);

        modulesByPriority.forEach(moduleName => {
            if (!visited.has(moduleName)) {
                visit(moduleName);
            }
        });

        this.initializationOrder = order;
        Logger.info(`[DependencyManager] Initialization order: ${order.join(' → ')}`);
        
        return order;
    }

    /**
     * Check if all dependencies for a module are satisfied
     * @param {string} moduleName - Module to check
     * @returns {Object} Dependency check result
     */
    checkDependencies(moduleName) {
        const module = this.modules.get(moduleName);
        if (!module) {
            return { satisfied: false, missing: [], reason: `Module '${moduleName}' not registered` };
        }

        const missing = [];
        const optional = [];

        module.dependencies.forEach(dep => {
            const depModule = this.modules.get(dep.name);
            
            if (!depModule) {
                if (dep.optional) {
                    optional.push(dep.name);
                } else {
                    missing.push(dep.name);
                }
            } else if (!this.initialized.has(dep.name)) {
                if (!dep.optional) {
                    missing.push(`${dep.name} (not initialized)`);
                }
            }
        });

        return {
            satisfied: missing.length === 0,
            missing,
            optional,
            reason: missing.length > 0 ? `Missing: ${missing.join(', ')}` : 'OK'
        };
    }

    /**
     * Mark module as initialized
     * @param {string} moduleName - Module name
     */
    markInitialized(moduleName) {
        this.initialized.add(moduleName);
        this.initializing.delete(moduleName);
        Logger.info(`[DependencyManager] '${moduleName}' initialized`);
    }

    /**
     * Mark module as initializing
     * @param {string} moduleName - Module name
     */
    markInitializing(moduleName) {
        this.initializing.add(moduleName);
    }

    /**
     * Get modules that depend on the given module
     * @param {string} moduleName - Module name
     * @returns {string[]} Dependent module names
     */
    getDependents(moduleName) {
        return this.dependents.get(moduleName) || [];
    }

    /**
     * Get module definition
     * @param {string} moduleName - Module name
     * @returns {ModuleDefinition|undefined} Module definition
     */
    getModule(moduleName) {
        return this.modules.get(moduleName);
    }

    /**
     * Check if module is ready to initialize
     * @param {string} moduleName - Module name
     * @returns {boolean} Ready status
     */
    isReadyToInitialize(moduleName) {
        if (this.initialized.has(moduleName) || this.initializing.has(moduleName)) {
            return false;
        }

        const check = this.checkDependencies(moduleName);
        return check.satisfied;
    }

    /**
     * Get initialization status
     * @returns {Object} Status report
     */
    getStatus() {
        const total = this.modules.size;
        const initialized = this.initialized.size;
        const initializing = this.initializing.size;
        const pending = total - initialized - initializing;

        return {
            total,
            initialized,
            initializing,
            pending,
            modules: {
                initialized: Array.from(this.initialized),
                initializing: Array.from(this.initializing),
                pending: Array.from(this.modules.keys()).filter(name => 
                    !this.initialized.has(name) && !this.initializing.has(name)
                )
            }
        };
    }

    /**
     * Validate module definition
     * @private
     * @param {ModuleDefinition} definition - Module definition to validate
     * @returns {boolean} Validation result
     */
    validateDefinition(definition) {
        if (!definition.name || typeof definition.name !== 'string') {
            Logger.error('[DependencyManager] Module name is required and must be string');
            return false;
        }

        if (!definition.version || typeof definition.version !== 'string') {
            Logger.error(`[DependencyManager] Version is required for module '${definition.name}'`);
            return false;
        }

        if (!Array.isArray(definition.dependencies)) {
            Logger.error(`[DependencyManager] Dependencies must be array for module '${definition.name}'`);
            return false;
        }

        // Validate dependencies
        for (const dep of definition.dependencies) {
            if (!dep.name || typeof dep.name !== 'string') {
                Logger.error(`[DependencyManager] Invalid dependency in module '${definition.name}': missing name`);
                return false;
            }
        }

        return true;
    }

    /**
     * Reset dependency manager
     */
    reset() {
        this.modules.clear();
        this.dependents.clear();
        this.initialized.clear();
        this.initializing.clear();
        this.initializationOrder = [];
        Logger.info('[DependencyManager] Reset complete');
    }

    /**
     * Create module definition helper
     * @param {string} name - Module name
     * @param {string} version - Module version
     * @returns {Object} Module definition builder
     */
    static createDefinition(name, version) {
        const definition = {
            name,
            version,
            dependencies: [],
            provides: [],
            priority: 0
        };
        
        // Create a chainable API
        const builder = {
            depends(moduleName, moduleVersion = '*', optional = false) {
                definition.dependencies.push({
                    name: moduleName,
                    version: moduleVersion,
                    optional
                });
                return this;
            },
            
            provides(...services) {
                definition.provides.push(...services);
                return this;
            },
            
            priority(level) {
                definition.priority = level;
                return this;
            },
            
            // Return the final definition
            build() {
                return definition;
            }
        };
        
        // Make the builder return the definition when accessed as an object
        Object.setPrototypeOf(builder, definition);
        
        // Allow direct access to the definition properties
        Object.keys(definition).forEach(key => {
            if (!(key in builder)) {
                Object.defineProperty(builder, key, {
                    get() { return definition[key]; },
                    set(value) { definition[key] = value; },
                    enumerable: true,
                    configurable: true
                });
            }
        });
        
        return builder;
    }
}

// Global instance
export const dependencyManager = new DependencyManager();

// Debug access
if (typeof window !== 'undefined') {
    window.dependencyManager = dependencyManager;
    window.depStatus = () => dependencyManager.getStatus();
}