<?php

namespace PloverCRM\Core;

use Exception;

/**
 * Module Manager
 * 
 * Handles module registration, dependency resolution, and lifecycle management
 */
class ModuleManager
{
    /**
     * @var Container
     */
    private $container;

    /**
     * @var array Registered modules
     */
    private $modules = [];

    /**
     * @var array Booted modules
     */
    private $bootedModules = [];

    /**
     * @var bool Whether modules have been registered
     */
    private $registered = false;

    /**
     * @var bool Whether modules have been booted
     */
    private $booted = false;

    /**
     * Constructor
     * 
     * @param Container $container
     */
    public function __construct(Container $container)
    {
        $this->container = $container;
    }

    /**
     * Get all registered module instances
     * 
     * @return array
     */
    public function getModules(): array
    {
        return $this->modules;
    }

    /**
     * Register a module
     * 
     * @param string $moduleClass Fully qualified module class name
     * @return void
     * @throws Exception
     */
    public function registerModule(string $moduleClass): void
    {
        if ($this->registered) {
            throw new Exception("Cannot register modules after registration phase.");
        }

        if (!class_exists($moduleClass)) {
            throw new Exception("Module class '{$moduleClass}' does not exist.");
        }

        $module = new $moduleClass($this->container);

        if (!$module instanceof ModuleInterface) {
            throw new Exception("Module '{$moduleClass}' must implement ModuleInterface.");
        }

        $moduleId = $module->getId();

        if (isset($this->modules[$moduleId])) {
            throw new Exception("Module '{$moduleId}' is already registered.");
        }

        // Only register if module is enabled
        if (!$module->isEnabled()) {
            return;
        }

        $this->modules[$moduleId] = $module;
    }

    /**
     * Register all modules
     * 
     * @return void
     */
    public function registerAll(): void
    {
        if ($this->registered) {
            return;
        }

        // Sort modules by dependencies
        $sortedModules = $this->sortModulesByDependencies();

        // Register each module
        foreach ($sortedModules as $module) {
            $module->register();
        }

        $this->registered = true;
    }

    /**
     * Boot all registered modules
     * 
     * @return void
     */
    public function bootAll(): void
    {
        if ($this->booted) {
            return;
        }

        if (!$this->registered) {
            throw new Exception("Modules must be registered before booting.");
        }

        // Boot each module in dependency order
        foreach ($this->modules as $moduleId => $module) {
            $module->boot();
            $this->bootedModules[$moduleId] = true;
        }

        $this->booted = true;
    }

    /**
     * Get a registered module by ID
     * 
     * @param string $moduleId
     * @return ModuleInterface|null
     */
    public function getModule(string $moduleId): ?ModuleInterface
    {
        return $this->modules[$moduleId] ?? null;
    }

    /**
     * Check if a module is registered
     * 
     * @param string $moduleId
     * @return bool
     */
    public function hasModule(string $moduleId): bool
    {
        return isset($this->modules[$moduleId]);
    }

    /**
     * Sort modules by dependencies using topological sort
     * 
     * @return array
     * @throws Exception
     */
    private function sortModulesByDependencies(): array
    {
        $sorted = [];
        $visited = [];
        $visiting = [];

        foreach ($this->modules as $moduleId => $module) {
            $this->visitModule($moduleId, $visited, $visiting, $sorted);
        }

        return $sorted;
    }

    /**
     * Visit a module during dependency sorting (DFS)
     * 
     * @param string $moduleId
     * @param array $visited
     * @param array $visiting
     * @param array $sorted
     * @return void
     * @throws Exception
     */
    private function visitModule(string $moduleId, array &$visited, array &$visiting, array &$sorted): void
    {
        // Already processed
        if (isset($visited[$moduleId])) {
            return;
        }

        // Circular dependency detected
        if (isset($visiting[$moduleId])) {
            throw new Exception("Circular dependency detected for module '{$moduleId}'.");
        }

        $visiting[$moduleId] = true;

        $module = $this->modules[$moduleId];
        $dependencies = $module->getDependencies();

        // Visit dependencies first
        foreach ($dependencies as $dependencyId) {
            if (!isset($this->modules[$dependencyId])) {
                throw new Exception("Module '{$moduleId}' depends on '{$dependencyId}', which is not registered or disabled.");
            }

            $this->visitModule($dependencyId, $visited, $visiting, $sorted);
        }

        unset($visiting[$moduleId]);
        $visited[$moduleId] = true;
        $sorted[] = $module;
    }
}
