<?php
// common/functions/modules_registry.php
// Centralized helpers to collect and synchronize legacy installed module registries

if (!function_exists('mh_installed_keys_for_area')) {
    /**
     * Map a logical area name to its legacy configuration keys
     * @return array{0:string,1:string} [installed_list_key, versions_list_key]
     */
    function mh_installed_keys_for_area(string $area): array
    {
        switch ($area) {
            case 'configbeez':
                return ['MAILBEEZ_CONFIG_INSTALLED', 'MAILBEEZ_CONFIG_INSTALLED_VERSIONS'];
            case 'filterbeez':
                return ['MAILBEEZ_FILTER_INSTALLED', 'MAILBEEZ_FILTER_INSTALLED_VERSIONS'];
            case 'reportbeez':
                return ['MAILBEEZ_REPORT_INSTALLED', 'MAILBEEZ_REPORT_INSTALLED_VERSIONS'];
            case 'dashboardbeez':
            case 'home':
                return ['MAILBEEZ_DASHBOARD_INSTALLED', 'MAILBEEZ_DASHBOARD_INSTALLED_VERSIONS'];
            case 'mailbeez':
            default:
                return ['MAILBEEZ_INSTALLED', 'MAILBEEZ_INSTALLED_VERSIONS'];
        }
    }
}

if (!function_exists('mh_parse_installed_list')) {
    /** Parse a semicolon-separated list into a unique, trimmed array */
    function mh_parse_installed_list(?string $list): array
    {
        $items = array_filter(array_map('trim', explode(';', (string) $list)));
        // De-duplicate while preserving order
        $seen = [];
        $out = [];
        foreach ($items as $it) {
            if (!isset($seen[$it])) { $seen[$it] = true; $out[] = $it; }
        }
        return $out;
    }
}

if (!function_exists('mh_format_installed_list')) {
    /** Format an installed list (array of strings) into the canonical semicolon-separated string */
    function mh_format_installed_list(array $items): string
    {
        $items = array_filter(array_map('trim', $items));
        $seen = [];
        $out = [];
        foreach ($items as $it) {
            if ($it === '') { continue; }
            if (!isset($seen[$it])) { $seen[$it] = true; $out[] = $it; }
        }
        return implode(';', $out);
    }
}

if (!function_exists('mh_format_installed_version_entry')) {
    /** Build a canonical version entry in the format code|version|check|enabled */
    function mh_format_installed_version_entry(string $module, $version, $check = '', $enabled = ''): string
    {
        return implode('|', [
            trim($module),
            (string) $version,
            (string) $check,
            (string) $enabled,
        ]);
    }
}

if (!function_exists('mh_get_installed_version_from_list')) {
    /**
     * Resolve installed version for one module from a versions list.
     * Accepts legacy entries (code|version) and extended entries (code|version|check|enabled).
     */
    function mh_get_installed_version_from_list(?string $versionInformation, string $module)
    {
        $installed_modules_list = mh_parse_installed_list($versionInformation);
        foreach ($installed_modules_list as $inst_modules) {
            if (!stristr($inst_modules, '|')) {
                continue;
            }

            $parts = explode('|', $inst_modules);
            $installed_module = isset($parts[0]) ? trim((string) $parts[0]) : '';
            $installed_module_version = isset($parts[1]) ? $parts[1] : '';
            $installed_module_check = isset($parts[2]) ? trim((string) $parts[2]) : null;

            if ($installed_module != $module) {
                continue;
            }

            // Legacy lists may not have an explicit check flag. Treat missing/empty check as installed.
            if ($installed_module_check === null || $installed_module_check === '' || (int) $installed_module_check > 0) {
                return str_replace(',', '.', $installed_module_version);
            }
        }
        return false;
    }
}

if (!function_exists('mh_update_installed_version_list')) {
    /**
     * Update only the version token for one module in a semicolon-separated versions list.
     * Preserves all remaining tokens and canonicalizes separators/duplicates.
     */
    function mh_update_installed_version_list(?string $versionInformation, string $module, $version): string
    {
        $installed_modules_list = mh_parse_installed_list($versionInformation);
        $updated_modules_list = [];

        foreach ($installed_modules_list as $inst_modules) {
            if (!stristr($inst_modules, '|')) {
                $updated_modules_list[] = $inst_modules;
                continue;
            }

            $parts = explode('|', $inst_modules);
            $installed_module = isset($parts[0]) ? trim((string) $parts[0]) : '';

            if ($installed_module == $module) {
                $parts[1] = (string) $version;
                $inst_modules = implode('|', $parts);
            }

            $updated_modules_list[] = $inst_modules;
        }

        return mh_format_installed_list($updated_modules_list);
    }
}

if (!function_exists('mh_area_root_dir')) {
    /**
     * Resolve filesystem root for an area.
     */
    function mh_area_root_dir(string $area): string
    {
        switch ($area) {
            case 'configbeez': return mh_cfg('MH_DIR_CONFIG');
            case 'filterbeez': return mh_cfg('MH_DIR_FILTER');
            case 'reportbeez': return mh_cfg('MH_DIR_REPORT');
            case 'dashboardbeez':
            case 'home': return mh_cfg('MH_DIR_DASHBOARD');
            case 'mailbeez':
            default: return mh_cfg('MH_DIR_MODULE');
        }
    }
}

if (!function_exists('mh_collect_area_registry')) {
    /**
     * Collect installed list and versions list for an area, preserving legacy behavior:
     * - installed list includes ONLY modules with check()>0 and not stealth
     * - versions list includes ALL non-stealth modules as code|version|check|enabled
     * - submodules are included only when parent is installed and has_submodules
     *
     * @return array{installed_list:string, versions_list:string}
     */
    function mh_collect_area_registry(string $area): array
    {
        $root = rtrim(mh_area_root_dir($area), '/\\') . '/';
        $installed = [];
        $versions = [];

        // Gather top-level module files
        $files = glob($root . '*.php');
        if (!is_array($files)) { $files = []; }

        foreach ($files as $file) {
            $rel = basename($file);
            $class = function_exists('mh_get_class_name') ? mh_get_class_name($rel) : '';
            @include_once $file;
            if (!class_exists($class)) { continue; }
            try {
                $obj = new $class();
            } catch (\Throwable $e) {
                continue; // skip broken modules
            }

            $check   = method_exists($obj, 'check') ? $obj->check() : 0;
            $enabled = property_exists($obj, 'enabled') ? $obj->enabled : false;
            $stealth = property_exists($obj, 'stealth') ? (bool) $obj->stealth : false;
            $version = property_exists($obj, 'version') ? (string) $obj->version : '';
            $code    = property_exists($obj, 'code') ? (string) $obj->code : substr($rel, 0, -4);

            // Versions: all non-stealth
            if (!$stealth) {
                $versions[] = mh_format_installed_version_entry($code, $version, $check, $enabled);
            }

            // Installed: installed AND non-stealth
            $isInstalled = ((int) $check > 0);
            if ($isInstalled && !$stealth) {
                $installed[] = $rel;

                // Submodules for modules that declare has_submodules true
                if (property_exists($obj, 'has_submodules') && $obj->has_submodules) {
                    $subRoot = $root . $obj->module . '/submodules/*.php';
                    $subs = glob($subRoot);
                    if (!is_array($subs)) { $subs = []; }
                    foreach ($subs as $sf) {
                        $installed[] = substr($sf, strlen($root)); // make path relative to area root
                    }
                }
            }
        }

        // Normalize/dedupe
        $installedList = mh_format_installed_list($installed);
        $versionsList  = mh_format_installed_list($versions);

        return [
            'installed_list' => $installedList,
            'versions_list'  => $versionsList,
        ];
    }
}

if (!function_exists('mh_sync_installed')) {
    /**
     * Synchronize the legacy config keys for an area based on current discovery, writing only on change.
     * Returns true when a write occurred.
     */
    function mh_sync_installed(string $area): bool
    {
        list($listKey, $verKey) = mh_installed_keys_for_area($area);

        $currentList = mh_format_installed_list(mh_parse_installed_list(mh_cfg_refresh($listKey)));
        $currentVer  = mh_format_installed_list(mh_parse_installed_list(mh_cfg_refresh($verKey)));

        $collected = mh_collect_area_registry($area);
        $newList = $collected['installed_list'];
        $newVer  = $collected['versions_list'];

        $changed = ($newList !== $currentList) || ($newVer !== $currentVer);

        if ($changed && function_exists('mh_update_config_value')) {
            // Write through legacy configuration bridge
            mh_update_config_value([
                'configuration_key'   => $listKey,
                'configuration_value' => $newList,
            ]);
            mh_update_config_value([
                'configuration_key'   => $verKey,
                'configuration_value' => $newVer,
            ]);
            if (function_exists('mh_reset_config_cache')) { mh_reset_config_cache(); }
            return true;
        }
        return false;
    }
}
