<?php

class PluginManager
{
    private array $plugins = [];
    private string $pluginsPath;
    private $api = null;
    private array $enabledPlugins = [];
    private array $actions = [];
    private array $filters = [];

    public function __construct(string $pluginsPath, array $enabledPlugins = [])
    {
        $this->pluginsPath = $pluginsPath;
        $this->enabledPlugins = $enabledPlugins;
        $this->loadPlugins();
    }
    
    public function setAPI($api): void
    {
        $this->api = $api;
        
        foreach ($this->plugins as $plugin) {
            if (method_exists($plugin, 'setAPI')) {
                $plugin->setAPI($api);
            }
        }
    }

    private function loadPlugins(): void
    {
        if (!is_dir($this->pluginsPath)) {
            return;
        }

        $pluginDirs = glob($this->pluginsPath . '/*', GLOB_ONLYDIR);
        
        foreach ($pluginDirs as $pluginDir) {
            $pluginName = basename($pluginDir);
            
            if (!in_array($pluginName, $this->enabledPlugins, true)) {
                continue;
            }
            
            $pluginFile = $pluginDir . '/' . $pluginName . '.php';
            
            if (file_exists($pluginFile)) {
                require_once $pluginFile;
                
                $className = $pluginName;
                if (class_exists($className)) {
                    $this->plugins[$pluginName] = new $className();

                    // Auto-register hooks from plugin methods
                    $hookMethods = ['onPageLoad', 'onBeforeRender', 'onAfterRender', 'onSearch', 'onMenuBuild'];
                    foreach ($hookMethods as $hook) {
                        if (method_exists($this->plugins[$pluginName], $hook)) {
                            $this->addAction($hook, [$this->plugins[$pluginName], $hook]);
                        }
                    }

                    // Register filter methods
                    $filterMethods = ['onContentFilter', 'onTitleFilter', 'onMenuFilter'];
                    foreach ($filterMethods as $filter) {
                        if (method_exists($this->plugins[$pluginName], $filter)) {
                            $this->addFilter($filter, [$this->plugins[$pluginName], $filter]);
                        }
                    }
                }
            }
        }
    }

    public function addAction(string $hook, callable $callback, int $priority = 10): void
    {
        $this->actions[$hook][$priority][] = $callback;
    }

    public function addFilter(string $hook, callable $callback, int $priority = 10): void
    {
        $this->filters[$hook][$priority][] = $callback;
    }

    public function doAction(string $hook, ...$args): void
    {
        if (!isset($this->actions[$hook])) return;
        ksort($this->actions[$hook]);
        foreach ($this->actions[$hook] as $callbacks) {
            foreach ($callbacks as $callback) {
                $callback(...$args);
            }
        }
    }

    public function applyFilters(string $hook, $value, ...$args)
    {
        if (!isset($this->filters[$hook])) return $value;
        ksort($this->filters[$hook]);
        foreach ($this->filters[$hook] as $callbacks) {
            foreach ($callbacks as $callback) {
                $value = $callback($value, ...$args);
            }
        }
        return $value;
    }

    public function getPlugin(string $name): ?object
    {
        return $this->plugins[$name] ?? null;
    }

    public function getAllPlugins(): array
    {
        return $this->plugins;
    }

    public function getEnabledPlugins(): array
    {
        return $this->enabledPlugins;
    }

    public function isEnabled(string $pluginName): bool
    {
        return in_array($pluginName, $this->enabledPlugins, true);
    }

    public function isPluginViewable(object $plugin): bool
    {
        if (method_exists($plugin, 'getConfig')) {
            $config = $plugin->getConfig();
            return !isset($config['viewable']) || $config['viewable'] !== false;
        }
        return true;
    }

    public function getSidebarContent(?array $allowedPlugins = null): string
    {
        $sidebarContent = '';
        
        foreach ($this->plugins as $pluginName => $plugin) {
            if (!$this->isPluginViewable($plugin) || !method_exists($plugin, 'getSidebarContent')) {
                continue;
            }
            
            if ($allowedPlugins !== null && !in_array($pluginName, $allowedPlugins, true)) {
                continue;
            }
            
            $content = $plugin->getSidebarContent();
            if (trim($content) === '') {
                continue;
            }
            
            $title = 'Plugin';
            if (method_exists($plugin, 'getConfig')) {
                $config = $plugin->getConfig();
                $title = $config['title'] ?? 'Plugin';
            }
            
            $sidebarContent .= '
                <div class="card mb-3">
                    <div class="card-header">
                        <h5 class="mb-0">' . htmlspecialchars($title) . '</h5>
                    </div>
                    <div class="card-body">
                        ' . $content . '
                    </div>
                </div>';
        }
        
        return $sidebarContent;
    }

    /**
     * Get CSS URLs from all enabled plugins that provide getCssUrl().
     *
     * @return array List of CSS URLs
     */
    public function getPluginCssUrls(): array
    {
        $urls = [];
        foreach ($this->plugins as $pluginName => $plugin) {
            if (method_exists($plugin, 'getCssUrl')) {
                $url = $plugin->getCssUrl();
                if (!empty($url)) {
                    $urls[] = $url;
                }
            }
        }
        return $urls;
    }

    /**
     * Collect admin menu items from all enabled plugins that implement getAdminMenu().
     * Each plugin returns [['route' => 'plugin-name/action', 'label' => 'Label', 'icon' => 'bi-icon']].
     *
     * @return array Admin menu items from all plugins
     */
    public function getAdminMenuItems(): array
    {
        $items = [];
        foreach ($this->plugins as $pluginName => $plugin) {
            if (method_exists($plugin, 'getAdminMenu')) {
                $pluginItems = $plugin->getAdminMenu();
                if (is_array($pluginItems)) {
                    foreach ($pluginItems as $item) {
                        $items[] = $item;
                    }
                }
            }
        }
        return $items;
    }

    /**
     * Dispatch an admin route to a plugin that handles it.
     * Plugins implement handleAdminRoute(string $action): ?string (returns rendered HTML or null).
     *
     * @param string $pluginName Plugin name
     * @param string $action Action/sub-route within the plugin
     * @return string|null Rendered HTML, or null if plugin doesn't handle it
     */
    public function dispatchAdminRoute(string $pluginName, string $action): ?string
    {
        $plugin = $this->getPlugin($pluginName);
        if ($plugin === null) {
            return null;
        }
        if (method_exists($plugin, 'handleAdminRoute')) {
            return $plugin->handleAdminRoute($action);
        }
        return null;
    }

    /**
     * Find which plugin handles a given admin route.
     *
     * @param string $route The admin route (e.g. 'statistics' or 'statistics/details')
     * @return array|null ['plugin' => name, 'action' => action] or null
     */
    public function resolveAdminRoute(string $route): ?array
    {
        foreach ($this->getAdminMenuItems() as $item) {
            $itemRoute = $item['route'] ?? '';
            // Check if the requested route starts with this plugin's route
            if ($route === $itemRoute || str_starts_with($route, $itemRoute . '/')) {
                $action = substr($route, strlen($itemRoute) + 1);
                return [
                    'plugin' => $item['plugin'] ?? '',
                    'action' => $action,
                ];
            }
        }
        return null;
    }
}
