<?php
namespace Components;

/**
 * Lightweight capability resolver used by route gating/fallback logic.
 *
 * Supported shapes:
 * - bool
 * - callable(): bool
 * - string (module key, e.g. "mb_newsletter" or typed "module:mb_newsletter")
 * - list => all items must pass
 * - assoc forms:
 *   - ['type' => 'module', 'module' => 'mb_newsletter']
 *   - ['type' => 'any', 'items' => [...]]
 *   - ['type' => 'all', 'items' => [...]]
 *   - ['type' => 'not', 'item' => ...]
 *   - ['module' => 'mb_newsletter']
 *   - ['any' => [...]] / ['all' => [...]] / ['not' => ...]
 */
class Capabilities
{
    /** @var array<string, callable(array):bool> */
    private static array $resolvers = [];

    public static function registerResolver(string $type, callable $resolver): void
    {
        $type = strtolower(trim($type));
        if ($type === '') {
            return;
        }
        self::$resolvers[$type] = $resolver;
    }

    /**
     * @param mixed $capability
     */
    public static function isSupported($capability): bool
    {
        if ($capability === null) {
            return true;
        }
        if (is_bool($capability)) {
            return $capability;
        }
        if (is_callable($capability)) {
            try {
                return (bool)call_user_func($capability);
            } catch (\Throwable $e) {
                return false;
            }
        }
        if (is_string($capability)) {
            $raw = trim($capability);
            if ($raw === '') {
                return true;
            }
            if (strpos($raw, ':') !== false) {
                [$type, $value] = explode(':', $raw, 2);
                return self::resolveTyped((string)$type, ['value' => trim((string)$value)]);
            }
            // Plain string defaults to module capability check.
            return self::moduleAvailable($raw);
        }
        if (!is_array($capability)) {
            return false;
        }

        if (self::isList($capability)) {
            foreach ($capability as $item) {
                if (!self::isSupported($item)) {
                    return false;
                }
            }
            return true;
        }

        if (array_key_exists('all', $capability)) {
            return self::isSupported($capability['all']);
        }
        if (array_key_exists('any', $capability)) {
            $items = $capability['any'];
            if (!is_array($items)) {
                return false;
            }
            foreach ($items as $item) {
                if (self::isSupported($item)) {
                    return true;
                }
            }
            return false;
        }
        if (array_key_exists('not', $capability)) {
            return !self::isSupported($capability['not']);
        }

        if (isset($capability['type'])) {
            return self::resolveTyped((string)$capability['type'], $capability);
        }

        if (isset($capability['module']) || isset($capability['key'])) {
            $module = (string)($capability['module'] ?? $capability['key']);
            return self::moduleAvailable($module);
        }

        if (isset($capability['callable']) && is_callable($capability['callable'])) {
            $args = $capability['args'] ?? [];
            if (!is_array($args)) {
                $args = [$args];
            }
            try {
                return (bool)call_user_func_array($capability['callable'], $args);
            } catch (\Throwable $e) {
                return false;
            }
        }

        return false;
    }

    /**
     * @param array<string,mixed> $definition
     */
    private static function resolveTyped(string $type, array $definition): bool
    {
        $type = strtolower(trim($type));
        if ($type === '') {
            return false;
        }

        if (isset(self::$resolvers[$type])) {
            try {
                return (bool)call_user_func(self::$resolvers[$type], $definition);
            } catch (\Throwable $e) {
                return false;
            }
        }

        switch ($type) {
            case 'license':
            case 'license_valid':
                $module = (string)($definition['module'] ?? $definition['key'] ?? $definition['value'] ?? '');
                return self::isModuleLicensed($module);
            case 'module':
            case 'module_available':
            case 'module_installed':
                $module = (string)($definition['module'] ?? $definition['key'] ?? $definition['value'] ?? '');
                return self::moduleAvailable($module);
            case 'all':
                return self::isSupported($definition['items'] ?? []);
            case 'any':
                return self::isSupported(['any' => $definition['items'] ?? []]);
            case 'not':
                return !self::isSupported($definition['item'] ?? null);
            default:
                return false;
        }
    }

    private static function moduleAvailable(string $module): bool
    {
        $module = strtolower(trim($module));
        if ($module === '') {
            return false;
        }

        // Most reliable in legacy runtime contexts.
        if (function_exists('mh_cfg')) {
            $installed = (string)mh_cfg('MAILBEEZ_INSTALLED', '');
            if ($installed !== '') {
                $entries = array_filter(array_map('trim', explode(';', strtolower($installed))), static function ($v) {
                    return $v !== '';
                });
                foreach ($entries as $entry) {
                    if ($entry === $module || $entry === $module . '.php') {
                        return true;
                    }
                    if (strpos($entry, $module . '/') === 0 || strpos($entry, $module . '.php/') === 0) {
                        return true;
                    }
                }
            }
        }

        // App-level fallback.
        try {
            return ModuleLoader::exists($module);
        } catch (\Throwable $e) {
            return false;
        }
    }

    /**
     * @param array<mixed> $arr
     */
    private static function isList(array $arr): bool
    {
        if (function_exists('array_is_list')) {
            return array_is_list($arr);
        }
        $i = 0;
        foreach ($arr as $k => $_) {
            if ($k !== $i++) {
                return false;
            }
        }
        return true;
    }

    private static function isModuleLicensed(string $module): bool
    {
        if ($module === '') {
            return false;
        }
        if (!class_exists('cloudBeezApiConsumer')) {
            $consumerFile = (defined('MH_DIR_CONFIG') ? MH_DIR_CONFIG : MH_DIR_FS_CATALOG . MH_ROOT_PATH . 'configbeez/') . 'config_cloudbeez/classes/api_cloudbeez_consumer.php';
            if (file_exists($consumerFile)) {
                require_once($consumerFile);
            }
        }
        if (!class_exists('cloudBeezApiConsumer')) {
            return false;
        }
        $subscriptions = \cloudBeezApiConsumer::getSubscriptionInfo();
        if (!is_array($subscriptions)) {
            return false;
        }

        foreach ($subscriptions as $subscr) {
            if (isset($subscr['modules'][$module])) {
                // Check if license is valid and not expired
                return !empty($subscr['valid_license']) && ((int)$subscr['expires_in'] !== -1);
            }
        }
        return false;
    }
}
