<?php
namespace Components\Data\Live;

use Components\Data\Contracts\ModuleProvider;
use Components\Data\Mock\MockModuleProvider;
use Components\ModuleLoader;

/**
 * Live module provider for real MailBeez modules.
 *
 * Refactored to use a common loader that discovers modules from their
 * directories and reads attributes from instantiated class objects
 * (no source parsing). Supports triggers (campaigns), services, and filters.
 */
class LiveModuleProvider implements ModuleProvider
{
    private $allCache = null;
    private $titleCache = null;
    /** @var array<string, array<int, array>|null> */
    private array $modulesByTypeCache = [];

    private function mock(): MockModuleProvider
    {
        // Deprecated: no longer used as fallback; kept for BC if referenced elsewhere
        return new MockModuleProvider();
    }

    public function all(): array
    {
        if (is_array($this->allCache)) {
            return $this->allCache;
        }
        $all = [];
        // Campaigns
        foreach ($this->triggersByGroup() as $group => $mods) {
            foreach ($mods as $m) {
                $all[$m['key']] = $m;
            }
        }
        // Services
        foreach ($this->serviceModulesByGroup() as $group => $mods) {
            foreach ($mods as $m) {
                $all[$m['key']] = $m;
            }
        }
        // Filters
        foreach ($this->filterModulesByGroup() as $group => $mods) {
            foreach ($mods as $m) {
                $all[$m['key']] = $m;
            }
        }
        // Configuration items from sections
        foreach ($this->configModulesByGroup() as $group => $mods) {
            foreach ($mods as $m) {
                $all[$m['key']] = $m;
            }
        }

        $this->allCache = $all;
        // Build title cache
        $titles = [];
        foreach ($all as $k => $m) { $titles[$k] = $m['title']; }
        $this->titleCache = $titles;
        return $all;
    }

    public function triggersByGroup(?bool $onlyInstalled = true): array
    {
        $mods = $this->loadModulesByType('campaign', $onlyInstalled);
        if ($mods === null) {
            return [];
        }

        // Group by module-declared group and add flow
        $byGroup = [];
        foreach ($mods as $m) {
            $item = $this->buildTriggerItem($m);
            if (!is_array($item)) {
                continue;
            }
            $group = $item['group'] ?? '';
            $byGroup[$group][] = $item;
        }
        ksort($byGroup);
        foreach ($byGroup as &$list) {
            usort($list, static function ($a, $b) { return strcmp($a['title'], $b['title']); });
        }
        unset($list);
        return $byGroup;
    }

    /**
     * Targeted trigger lookup by key to avoid full campaign iteration when unnecessary.
     */
    public function triggerByKey(string $key, ?bool $onlyInstalled = true): ?array
    {
        $key = trim($key);
        if ($key === '') {
            return null;
        }

        $mods = $this->loadModulesByType('campaign', $onlyInstalled, [$key]);
        if (!is_array($mods) || $mods === []) {
            return null;
        }
        foreach ($mods as $m) {
            if (($m['key'] ?? '') !== $key) {
                continue;
            }
            $item = $this->buildTriggerItem($m);
            if (is_array($item)) {
                return $item;
            }
        }

        return null;
    }

    /**
     * Normalize a raw campaign module row to trigger card payload.
     * Returns null for excluded modules.
     */
    private function buildTriggerItem(array $m): ?array
    {
        if (($m['key'] ?? '') === 'mb_newsletter') {
            return null;
        }

        $group = $m['group'] ?? '';
        $item = ModuleLoader::common($m['key'], $m['title'], $m['desc'], $group, 'campaign');

        // Flow is resolved lazily at render time in mailbeez_pro::renderFlowStatic().
        $item['flow'] = null;
        // Attach rich metadata
        $item['svg_icon'] = $m['svg_icon'];
        $item['color'] = $m['color'];
        $item['blueprint_path'] = $m['blueprint'];
        $item['config_keys'] = $m['config_keys'];
        if (array_key_exists('status_key', $m)) { $item['status_key'] = $m['status_key']; }
        if (array_key_exists('cert_check', $m)) { $item['cert_check'] = $m['cert_check']; }
        if (array_key_exists('can_uninstall', $m)) { $item['can_uninstall'] = $m['can_uninstall']; }

        return $item;
    }


    public function configModulesByGroup(): array
    {
        $mods = $this->loadModulesByType('config');
        if ($mods === null) {
            return [];
        }

        // Determine which modules are alias targets of others so we can hide them from the menu
        $aliasTargets = [];
        foreach ($mods as $m) {
            $aliases = isset($m['alias_modules']) ? $m['alias_modules'] : [];
            if (is_string($aliases) && $aliases !== '') {
                $aliases = array_filter(array_map('trim', explode(',', $aliases)), fn($s) => $s !== '');
            }
            if (is_array($aliases)) {
                foreach ($aliases as $ak) {
                    $ak = (string)$ak;
                    if ($ak !== '') { $aliasTargets[$ak] = true; }
                }
            }
        }

        // Group by module-declared group
        $byGroup = [];
        foreach ($mods as $m) {
            // Hide modules that are used solely as alias providers for other modules
            if (isset($aliasTargets[$m['key']])) { continue; }
            $group = $m['group'] ?? '';
            // Use the common module structure with type 'configuration'
            $item = ModuleLoader::common($m['key'], $m['title'], $m['desc'], $group, 'configuration');
            // Attach metadata
            $item['svg_icon'] = $m['svg_icon'];
            $item['color'] = $m['color'];
            $item['blueprint_path'] = $m['blueprint'];
            $item['config_keys'] = $m['config_keys'];
            if (array_key_exists('status_key', $m)) { $item['status_key'] = $m['status_key']; }
            if (array_key_exists('cert_check', $m)) { $item['cert_check'] = $m['cert_check']; }
            if (array_key_exists('can_uninstall', $m)) { $item['can_uninstall'] = $m['can_uninstall']; }
            // Propagate alias_modules meta so UI can merge blueprints
            $item['alias_modules'] = isset($m['alias_modules']) && is_array($m['alias_modules']) ? array_values(array_map('strval', $m['alias_modules'])) : [];
            $byGroup[$group][] = $item;
        }
        ksort($byGroup);
        foreach ($byGroup as &$list) {
            usort($list, static function ($a, $b) { return strcmp($a['title'], $b['title']); });
        }
        unset($list);
        return $byGroup;
    }

    public function serviceModulesByGroup(): array
    {
        $mods = $this->loadModulesByType('service');
        if ($mods === null) {
            return [];
        }

        $group = 'Services';
        $out = [];
        foreach ($mods as $m) {
            $item = ModuleLoader::common($m['key'], $m['title'], $m['desc'], $group, 'service');
            $item['icon'] = $m['icon'];
            $item['svg_icon'] = $m['svg_icon'];
            $item['color'] = $m['color'];
            $item['blueprint_path'] = $m['blueprint'];
            $item['config_keys'] = $m['config_keys'];
            if (array_key_exists('status_key', $m)) { $item['status_key'] = $m['status_key']; }
            if (array_key_exists('cert_check', $m)) { $item['cert_check'] = $m['cert_check']; }
            if (array_key_exists('can_uninstall', $m)) { $item['can_uninstall'] = $m['can_uninstall']; }
            $out[$group][] = $item;
        }
        if (isset($out[$group])) {
            usort($out[$group], static function ($a, $b) { return strcmp($a['title'], $b['title']); });
        }
        return $out;
    }

    public function filterModulesByGroup(): array
    {
        $mods = $this->loadModulesByType('filter');
        if ($mods === null) {
            return [];
        }
        $group = 'Filters';
        $out = [];
        foreach ($mods as $m) {
            $item = ModuleLoader::common($m['key'], $m['title'], $m['desc'], $group, 'filter');
            if (array_key_exists('status_key', $m)) { $item['status_key'] = $m['status_key']; }
            if (array_key_exists('cert_check', $m)) { $item['cert_check'] = $m['cert_check']; }
            if (array_key_exists('can_uninstall', $m)) { $item['can_uninstall'] = $m['can_uninstall']; }
            $out[$group][] = $item;
        }
        if (isset($out[$group])) {
            usort($out[$group], static function ($a, $b) { return strcmp($a['title'], $b['title']); });
        }
        return $out;
    }

    public function extensions(string $module): array
    {
        // Minimal live implementation; could be sourced from installed plugins/extensions
        $map = [
            'email_engine' => [
                ['key' => 'smtp_plus', 'title' => 'SMTP Plus', 'desc' => 'Advanced SMTP routing & failover for reliable delivery.', 'installed' => false, 'url' => '#'],
                ['key' => 'bounce_guard', 'title' => 'Bounce Guard', 'desc' => 'Automated bounce processing to keep lists clean.', 'installed' => true, 'url' => '#'],
            ],
            'analytics' => [
                ['key' => 'ga4_bridge', 'title' => 'GA4 Bridge', 'desc' => 'Enhanced event mapping to Google Analytics 4.', 'installed' => true, 'url' => '#'],
            ],
            'reports' => [
                ['key' => 'pdf_export', 'title' => 'PDF Export', 'desc' => 'Export any report as branded PDF.', 'installed' => false, 'url' => '#'],
            ],
        ];
        $fallback = [
//            ['key' => $module . '_tools', 'title' => 'Tools Pack', 'desc' => 'Handy utilities to extend the module capabilities.', 'installed' => false, 'url' => '#'],
        ];
        return isset($map[$module]) ? $map[$module] : $fallback;
    }

    public function title(string $key): ?string
    {
        if (!is_array($this->titleCache)) {
            $this->all();
        }
        return isset($this->titleCache[$key]) ? $this->titleCache[$key] : null;
    }

    public function exists(string $key): bool
    {
        $all = $this->all();
        return isset($all[$key]);
    }

    public function canUninstall(string $key): bool
    {
        $all = $this->all();
        return (bool)($all[$key]['can_uninstall'] ?? false);
    }

    // -------------------- Common loader helpers --------------------

    /**
     * Load modules by high-level type, handling baseDir resolution and include rules.
     * Returns null if base directory cannot be resolved.
     * @param 'campaign'|'service'|'filter'|'config' $type
     * @param bool|null $onlyInstalled
     * @return array<int, array>|null
     */
    private function loadModulesByType(string $type, ?bool $onlyInstalled = true, ?array $onlyKeys = null): ?array
    {
        $normalizedOnlyKeys = null;
        if (is_array($onlyKeys)) {
            $tmp = [];
            foreach ($onlyKeys as $candidate) {
                $candidate = trim((string)$candidate);
                if ($candidate !== '') {
                    $tmp[$candidate] = true;
                }
            }
            $normalizedOnlyKeys = array_keys($tmp);
            sort($normalizedOnlyKeys);
        }

        $installedMarker = $onlyInstalled === null ? 'null' : ($onlyInstalled ? '1' : '0');
        $keysMarker = $normalizedOnlyKeys === null ? 'null' : json_encode($normalizedOnlyKeys);
        if (!is_string($keysMarker)) {
            $keysMarker = 'null';
        }
        $cacheKey = $type . '|' . $installedMarker . '|' . $keysMarker;
        if (array_key_exists($cacheKey, $this->modulesByTypeCache)) {
            return $this->modulesByTypeCache[$cacheKey];
        }

        // Determine directory kind by type
        $kind = ($type === 'filter') ? 'filter' : (($type === 'config') ? 'config' : 'module');
        $baseDir = $this->resolveDir($kind);
        if ($baseDir === '' || !is_dir($baseDir)) {
            $this->modulesByTypeCache[$cacheKey] = null;
            return null;
        }
        if ($type === 'campaign') {
            $rule = function (string $key): bool {
                if ($key === '' || $key === 'index') return false;
                if (strpos($key, 'service_') === 0) return false;
                return true;
            };
        } elseif ($type === 'service') {
            $rule = function (string $key): bool {
                return strpos($key, 'service_') === 0;
            };
        } elseif ($type === 'filter') {
            $rule = function (string $key): bool {
                return $key !== '' && $key !== 'index' && $key[0] !== '.';
            };
        } else { // config
            $rule = function (string $key): bool {
                if ($key === '' || $key === 'index') return false;
                if (strpos($key, 'config_') !== 0) return false;
                if ($key === 'config_cloudbeez') return false;
                return true;
            };
        }
        $result = $this->loadModules($baseDir, $rule, $type, $onlyInstalled, $normalizedOnlyKeys);
        $this->modulesByTypeCache[$cacheKey] = $result;
        return $result;
    }

    /**
     * Resolve absolute base directory for module type.
     * @param 'module'|'filter' $kind
     */
    private function resolveDir(string $kind): string
    {
        // Prefer MailBeez constants when available
        if ($kind === 'module' && defined('MH_DIR_MODULE')) {
            return rtrim((string)MH_DIR_MODULE, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        }
        if ($kind === 'filter' && defined('MH_DIR_FILTER')) {
            return rtrim((string)MH_DIR_FILTER, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        }
        if ($kind === 'config' && defined('MH_DIR_CONFIG')) {
            return rtrim((string)MH_DIR_CONFIG, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        }
        return '';
    }

    /**
     * Load modules from base directory by scanning for directories with a
     * matching main class file, then instantiate to read title/description.
     * @param callable(string $key):bool $includeRule
     * @param 'campaign'|'service'|'filter'|'config' $type
     * @param bool|null $onlyInstalled true: only installed, false: only uninstalled, null: all
     * @return array<int, array{key:string,title:string,desc:string,type:string}>
     */
    private function loadModules(string $baseDir, callable $includeRule, string $type, ?bool $onlyInstalled = true, ?array $onlyKeys = null): array
    {
        $out = [];

        // Default: discover module directories (campaign/service/filter)
        // For module-scoped calls, allow loading only specific keys.
        $dirs = [];
        if (is_array($onlyKeys) && $onlyKeys !== []) {
            foreach ($onlyKeys as $candidate) {
                $candidate = trim((string)$candidate);
                if ($candidate === '') {
                    continue;
                }
                $candidateDir = $baseDir . $candidate;
                if (is_dir($candidateDir)) {
                    $dirs[] = $candidateDir;
                }
            }
        } else {
            $dirs = glob($baseDir . '*', GLOB_ONLYDIR) ?: [];
        }
        foreach ($dirs as $dirPath) {
            $key = basename((string)$dirPath);
            if (!$includeRule($key)) continue;
            // Require main class file with the same name
            $file = $baseDir . $key . (defined('MH_FILE_EXTENSION') ? MH_FILE_EXTENSION : '.php');
            if (!is_file($file)) continue;

            // Attempt to include & instantiate class to read attributes
            $title = ucwords(str_replace('_', ' ', $key));
            $desc = '';
            try {
                mh_load_modules_language_files($baseDir, $key);
                // Include class file (module usually requires core files itself)
                require_once $file;

                if (class_exists($key)) {
                    /** @var \beez $obj */
                    $obj = new $key();
                    // Exclude modules explicitly marked as hidden by legacy class
                    $isHidden = false;
                    if (property_exists($obj, 'hidden')) {
                        $isHidden = (bool)$obj->hidden;
                    }
                    if ($isHidden) {
                        // Skip adding this module entirely
                        unset($obj);
                        continue;
                    }
                    // Exclude modules based on installation status
                    if ($onlyInstalled !== null) {
                        $isInstalled = true;
                        if (method_exists($obj, 'check')) {
                            try {
                                $isInstalled = (bool)$obj->check();
                            } catch (\Throwable $e) {
                                // On error, be conservative and skip the module
                                unset($obj);
                                continue;
                            }
                        }

                        if ($onlyInstalled === true && !$isInstalled) {
                            unset($obj);
                            continue;
                        }
                        if ($onlyInstalled === false && $isInstalled) {
                            unset($obj);
                            continue;
                        }
                    }
                    // Show only modules explicitly marked as BeezUI-ready
                    $uiReady = false;
                    if (property_exists($obj, 'beez_ui_ready')) {
                        $uiReady = ($obj->beez_ui_ready === true);
                    }
                    if (!$uiReady) {
                        unset($obj);
                        continue;
                    }
                    // Title/description and metadata are exposed via uniform getters on base class
                    $titleFromMeta = (string)$obj->metaTitle();
                    if ($titleFromMeta !== '') { $title = $titleFromMeta; }
                    $desc = (string)$obj->metaDescription();

                    // Collect additional metadata via required methods
                    $groupVal = (string)$obj->metaGroup();
                    $iconVal = $obj->metaIcon();
                    $svgIconVal = $obj->metaSvgIcon();
                    $colorVal = (string)$obj->metaColor();
                    $blueprintPath = $obj->blueprintPath();
                    $configKeys = $obj->configurationKeys();
                    // Expose status_key if available on module instances
                    $statusKeyVal = null;
                    if (property_exists($obj, 'status_key') && is_string($obj->status_key) && $obj->status_key !== '') {
                        $statusKeyVal = $obj->status_key;
                    }
                    // Capture the license check result (if this is a Pro module)
                    $certCheck = property_exists($obj, 'cert_check') ? $obj->cert_check : null;

                    $canUninstallVal = $obj->removable && method_exists($obj, 'remove');

                    // Optional: alias config modules that should be merged into this one (only for type 'config')
                    $aliasModulesVal = [];
                    if ($type === 'config') {
                        try {
                            // Support multiple legacy shapes for best compatibility
                            if (method_exists($obj, 'configAliases')) {
                                $val = $obj->configAliases();
                            } elseif (method_exists($obj, 'aliasModules')) {
                                $val = $obj->aliasModules();
                            } elseif (method_exists($obj, 'metaConfigAliases')) {
                                $val = $obj->metaConfigAliases();
                            } elseif (property_exists($obj, 'alias_modules')) {
                                $val = $obj->alias_modules;
                            } else {
                                $val = [];
                            }
                            if (is_string($val)) {
                                // allow comma-separated list
                                $parts = array_filter(array_map('trim', explode(',', $val)), fn($s) => $s !== '');
                                $val = $parts;
                            }
                            if (is_array($val)) {
                                foreach ($val as $ak) {
                                    $ak = (string)$ak;
                                    if ($ak !== '') { $aliasModulesVal[] = $ak; }
                                }
                            }
                        } catch (\Throwable $e) { /* ignore */ }
                    }
                }
            } catch (\Throwable $e) {
                // Swallow and use fallback title/desc; optionally log
                if (function_exists('error_log')) { error_log('[LiveModuleProvider] Failed loading module ' . $key . ': ' . $e->getMessage()); }
            }

            $out[] = [
                'key' => $key,
                'title' => $title,
                'desc' => $desc,
                'type' => $type,
                'group' => isset($groupVal) && is_string($groupVal) ? $groupVal : '',
                'icon' => $iconVal ?? null,
                'svg_icon' => $svgIconVal ?? null,
                'color' => isset($colorVal) ? $colorVal : '',
                'blueprint' => isset($blueprintPath) ? $blueprintPath : null,
                'config_keys' => isset($configKeys) ? $configKeys : [],
                'status_key' => isset($statusKeyVal) ? $statusKeyVal : null,
                'cert_check' => isset($certCheck) ? $certCheck : null,
                'can_uninstall' => isset($canUninstallVal) ? $canUninstallVal : false,
                'alias_modules' => isset($aliasModulesVal) ? $aliasModulesVal : [],
            ];
        }
        return $out;
    }


}
