<?php
declare(strict_types=1);


/**
 * Module Manager
 *
 * @package ProRank\Core
 */

namespace ProRank\SEO\Core;

use ProRank\SEO\Contracts\ModuleInterface;

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}


/**
 * Manages plugin modules
 */


class ModuleManager {
    
    /**
     * Registered modules
     *
     * @var array
     */
    private array $modules = [];
    
    /**
     * Settings manager instance
     *
     * @var SettingsManager
     */
    private SettingsManager $settings;
    
    /**
     * License manager instance
     *
     * @var LicenseManager
     */
    private LicenseManager $license;
    
    /**
     * Constructor
     */
    public function __construct() {
        // Dependencies will be injected later
    }
    
    /**
     * Register a module
     *
     * @param ModuleInterface $module Module instance
     */
    public function register( ModuleInterface $module ): void {
        $id = (string) $module->id();
        if ($id === '') {
            return;
        }

        if (isset($this->modules[$id])) {
            $existing = $this->modules[$id];

            /**
             * Allow explicit override when duplicate module IDs exist.
             * Default behavior keeps the first registered module to avoid
             * non-deterministic runtime swaps.
             */
            $allow_override = (bool) apply_filters(
                'prorank_allow_module_id_override',
                false,
                $id,
                $existing,
                $module
            );

            if (!$allow_override) {
                if (function_exists('prorank_log')) {
                    prorank_log(
                        sprintf(
                            '[ProRank SEO] Duplicate module ID "%s" ignored (%s -> %s)',
                            $id,
                            is_object($existing) ? get_class($existing) : gettype($existing),
                            get_class($module)
                        )
                    );
                }
                return;
            }
        }

        $this->modules[$id] = $module;
    }
    
    /**
     * Register a module (alias for register)
     *
     * @param ModuleInterface $module Module instance
     */
    public function register_module( ModuleInterface $module ): void {
        $this->register( $module );
    }
    
    /**
     * Initialize active modules
     */
    public function init_active_modules(): void {
        // Hidden validation call
        if (class_exists('\ProRank\SEO\Core\SecurityHelper')) {
            \ProRank\SEO\Core\SecurityHelper::pr_validate_context();
        }
        
        // Initialize only modules that are active in settings and allowed by tier.
        foreach ( $this->modules as $module_id => $module ) {
            if ( ! $this->is_active( (string) $module_id ) ) {
                continue;
            }

            try {
                $module->init();
            } catch (\Throwable $e) {
                prorank_log(
                    sprintf(
                        '[ProRank SEO] Module init failed (%s, %s): %s',
                        $module_id,
                        is_object($module) ? get_class($module) : gettype($module),
                        $e->getMessage()
                    )
                );
            }
        }
    }
    
    /**
     * Get active modules
     *
     * @return array
     */
    public function get_active_modules(): array {
        $active = [];
        
        foreach ( $this->modules as $id => $module ) {
            if ( $this->is_active( $id ) ) {
                $active[ $id ] = $module;
            }
        }
        
        return $active;
    }
    
    /**
     * Check if module is active
     *
     * @param string $slug Module slug or ID
     * @return bool
     */
    public function is_active( string $slug ): bool {
        // Get the plugin instance for request-scoped frontend safe-mode checks.
        // May be null during very early activation; callers below null-guard it.
        $plugin = \ProRank\SEO\Plugin::get_instance();

        // Find the module - could be by ID or slug
        $module = null;
        if ( isset( $this->modules[ $slug ] ) ) {
            $module = $this->modules[ $slug ];
        } else {
            // Try to find by slug property
            foreach ( $this->modules as $module_id => $mod ) {
                if ( method_exists( $mod, 'get_slug' ) && $mod->get_slug() === $slug ) {
                    $module = $mod;
                    break;
                }
            }
        }
        
        if ( ! $module ) {
            return false;
        }
        
        $required_tier = $module->tier_required();
        if ($required_tier === 'core') {
            $required_tier = 'pro';
        }

        if ( ! $this->is_tier_available( $required_tier ) ) {
            return false;
        }

        // Then check settings for active modules
        $settings = get_option( 'prorank_settings', [] );
        if (!is_array($settings)) {
            $decoded = json_decode((string) $settings, true);
            $settings = is_array($decoded) ? $decoded : [];
        }
        $active_modules = $settings['active_modules'] ?? [];
        if (!is_array($active_modules)) {
            $active_modules = [];
        }

        $module_slug = method_exists( $module, 'get_slug' ) ? $module->get_slug() : null;
        $parent_slug = method_exists( $module, 'get_parent_slug' ) ? $module->get_parent_slug() : null;
        $group_states = get_option('prorank_seo_module_groups', []);
        if (!is_array($group_states)) {
            $group_states = [];
        }

        // Request-scoped frontend safe mode must not mutate saved module flags.
        // It only prevents risky performance modules from booting for this URL.
        if (
            $parent_slug === 'performance'
            && is_object($plugin)
            && method_exists($plugin, 'is_frontend_safe_mode_suppressed_module')
            && (
                $plugin->is_frontend_safe_mode_suppressed_module((string) $slug)
                || ($module_slug && $plugin->is_frontend_safe_mode_suppressed_module((string) $module_slug))
            )
        ) {
            return false;
        }

        // Group state is a hard gate when explicitly set.
        if (array_key_exists($slug, $group_states) && $this->is_disabled_setting($group_states[$slug])) {
            return false;
        }
        if ($module_slug && array_key_exists($module_slug, $group_states) && $this->is_disabled_setting($group_states[$module_slug])) {
            return false;
        }
        if ($parent_slug && array_key_exists($parent_slug, $group_states) && $this->is_disabled_setting($group_states[$parent_slug])) {
            return false;
        }

        // If no modules have been configured yet, keep performance modules OFF by default.
        // This prevents frontend breakage on first activation.
        if ( empty( $active_modules ) ) {
            if ( $parent_slug === 'performance' ) {
                return false;
            }
            return $this->is_tier_available( $required_tier );
        }

        // Check if explicitly set to false (disabled)
        if ( array_key_exists( $slug, $active_modules ) && $this->is_disabled_setting( $active_modules[ $slug ] ) ) {
            return false;
        }
        if ( $module_slug && array_key_exists( $module_slug, $active_modules ) && $this->is_disabled_setting( $active_modules[ $module_slug ] ) ) {
            return false;
        }
        if ( $parent_slug && array_key_exists( $parent_slug, $active_modules ) && $this->is_disabled_setting( $active_modules[ $parent_slug ] ) ) {
            // Legacy installs can carry a stale "performance=false" parent flag while
            // individual performance children are explicitly enabled. Respect explicit
            // child toggles unless the group itself is explicitly disabled.
            if ($parent_slug !== 'performance') {
                return false;
            }

            if (array_key_exists($parent_slug, $group_states) && $this->is_disabled_setting($group_states[$parent_slug])) {
                return false;
            }
        }

        // Check both by the provided slug and the module's slug property
        if ( isset( $active_modules[ $slug ] ) && $this->is_enabled_setting( $active_modules[ $slug ] ) ) {
            return true;
        }

        if ( $module_slug && isset( $active_modules[ $module_slug ] ) && $this->is_enabled_setting( $active_modules[ $module_slug ] ) ) {
            return true;
        }

        // Safety rule: a parent "performance" ON state is a gate, not an implicit
        // opt-in for every child module. Child modules must be explicitly enabled.
        if ( $parent_slug === 'performance' && isset( $active_modules[ $parent_slug ] ) && $this->is_enabled_setting( $active_modules[ $parent_slug ] ) ) {
            return false;
        }

        if ( $parent_slug && isset( $active_modules[ $parent_slug ] ) && $this->is_enabled_setting( $active_modules[ $parent_slug ] ) ) {
            return true;
        }

        // For performance modules, require explicit opt-in in active_modules.
        // This prevents unexpected frontend mutations when new modules are introduced.
        if ( $parent_slug === 'performance' ) {
            return false;
        }

        // Check group_states as an enabler — the wizard and module settings UI
        // save enabled groups here. Non-performance modules enabled via group_states
        // should be active even if not listed in active_modules.
        if (array_key_exists($slug, $group_states) && $this->is_enabled_setting($group_states[$slug])) {
            return true;
        }
        if ($module_slug && array_key_exists($module_slug, $group_states) && $this->is_enabled_setting($group_states[$module_slug])) {
            return true;
        }
        if ($parent_slug && array_key_exists($parent_slug, $group_states) && $this->is_enabled_setting($group_states[$parent_slug])) {
            return true;
        }

        // Non-performance modules default to enabled if tier is active and not explicitly disabled.
        // This ensures SEO features (sitemaps, schema, redirects, etc.) work out of the box.
        if ( $parent_slug && $parent_slug !== 'performance' ) {
            return $this->is_tier_available( $required_tier );
        }

        return false;
    }

    /**
     * Determine whether a tier is available in the free package.
     *
     * @param string $required_tier Minimum tier required.
     * @return bool
     */
    private function is_tier_available(string $required_tier): bool {
        if ($required_tier === 'core') {
            $required_tier = 'pro';
        }

        return $required_tier === 'free';
    }

    /**
     * Check if the premium plugin runtime is active.
     *
     * @return bool
     */
    private function is_premium_plugin_active(): bool {
        if (defined('PRORANK_PREMIUM_VERSION')) {
            return true;
        }

        if (function_exists('is_plugin_active')) {
            return is_plugin_active('prorank-seo-premium/prorank-seo.php')
                || is_plugin_active('prorank-seo-premium/prorank-seo-premium.php');
        }

        return false;
    }

    /**
     * Normalize mixed setting values into strict enabled/disabled booleans.
     *
     * Handles legacy string values ('true'/'false') correctly.
     *
     * @param mixed $value
     * @return bool
     */
    private function is_enabled_setting($value): bool {
        if (is_bool($value)) {
            return $value;
        }

        if (is_int($value) || is_float($value)) {
            return (int) $value === 1;
        }

        if (is_string($value)) {
            $normalized = strtolower(trim($value));
            if ($normalized === '') {
                return false;
            }

            if (in_array($normalized, ['1', 'true', 'yes', 'on', 'enabled'], true)) {
                return true;
            }

            if (in_array($normalized, ['0', 'false', 'no', 'off', 'disabled', 'null'], true)) {
                return false;
            }

            // Unknown non-empty strings should not auto-enable modules.
            return false;
        }

        return false;
    }

    /**
     * Inverse of is_enabled_setting().
     *
     * @param mixed $value
     * @return bool
     */
    private function is_disabled_setting($value): bool {
        return !$this->is_enabled_setting($value);
    }
    
    /**
     * Check if module is enabled (alias for is_active)
     *
     * @param string $slug Module slug or ID
     * @return bool
     */
    public function is_module_enabled( string $slug ): bool {
        return $this->is_active( $slug );
    }
    
    /**
     * Get all registered modules
     *
     * @return array
     */
    public function get_modules(): array {
        return $this->modules;
    }
    
    /**
     * Check if a module is registered
     *
     * @param string $slug Module slug or ID
     * @return bool
     */
    public function has_module( string $slug ): bool {
        // Check direct lookup
        if ( isset( $this->modules[ $slug ] ) ) {
            return true;
        }
        
        // Try to find by slug property
        foreach ( $this->modules as $module ) {
            if ( method_exists( $module, 'get_slug' ) && $module->get_slug() === $slug ) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Get a specific module
     *
     * @param string $slug Module slug
     * @return ModuleInterface|null
     */
    public function get_module( string $slug ): ?ModuleInterface {
        return $this->modules[ $slug ] ?? null;
    }
    
    /**
     * Enable a module
     *
     * @param string $slug Module slug or ID
     * @return bool
     */
    public function enable_module( string $slug ): bool {
        $settings = get_option( 'prorank_settings', [] );
        if (!is_array($settings)) {
            $decoded = json_decode((string) $settings, true);
            $settings = is_array($decoded) ? $decoded : [];
        }
        $active_modules = $settings['active_modules'] ?? [];
        if (!is_array($active_modules)) {
            $active_modules = [];
        }
        
        // Enable by the provided slug
        $active_modules[ $slug ] = true;
        
        // Also enable by module ID if we can find it
        foreach ( $this->modules as $module_id => $module ) {
            if ( method_exists( $module, 'get_slug' ) && $module->get_slug() === $slug ) {
                $active_modules[ $module_id ] = true;
                break;
            }
        }
        
        $settings['active_modules'] = $active_modules;
        return update_option( 'prorank_settings', $settings );
    }
    
    /**
     * Disable a module
     *
     * @param string $slug Module slug or ID
     * @return bool
     */
    public function disable_module( string $slug ): bool {
        $settings = get_option( 'prorank_settings', [] );
        if (!is_array($settings)) {
            $decoded = json_decode((string) $settings, true);
            $settings = is_array($decoded) ? $decoded : [];
        }
        $active_modules = $settings['active_modules'] ?? [];
        if (!is_array($active_modules)) {
            $active_modules = [];
        }

        // Set to false to explicitly mark as disabled (don't just unset)
        $active_modules[ $slug ] = false;

        // Also disable by module ID if we can find it
        foreach ( $this->modules as $module_id => $module ) {
            if ( method_exists( $module, 'get_slug' ) && $module->get_slug() === $slug ) {
                $active_modules[ $module_id ] = false;
                break;
            }
        }

        $settings['active_modules'] = $active_modules;
        return update_option( 'prorank_settings', $settings );
    }
    
    /**
     * Activate a module (alias for enable_module)
     *
     * @param string $slug Module slug
     * @return bool
     */
    public function activate_module( string $slug ): bool {
        return $this->enable_module( $slug );
    }
    
    /**
     * Deactivate a module (alias for disable_module)
     *
     * @param string $slug Module slug
     * @return bool
     */
    public function deactivate_module( string $slug ): bool {
        return $this->disable_module( $slug );
    }
    
    /**
     * Get top-level modules (modules without parent)
     *
     * @return array
     */
    public function get_top_level_modules(): array {
        $top_level = [];
        
        foreach ( $this->modules as $slug => $module ) {
            if ( $module->get_parent_slug() === null ) {
                $top_level[ $slug ] = $module;
            }
        }
        
        return $top_level;
    }
    
    /**
     * Get child modules of a parent module
     *
     * @param string $parent_slug Parent module slug
     * @return array
     */
    public function get_child_modules( string $parent_slug ): array {
        $children = [];
        
        foreach ( $this->modules as $slug => $module ) {
            if ( $module->get_parent_slug() === $parent_slug ) {
                $children[ $slug ] = $module;
            }
        }
        
        return $children;
    }

    /**
     * Register an external module from Premium plugin
     *
     * @param ModuleInterface $module Module instance
     * @param string $required_tier Minimum tier required (pro, business, agency)
     * @return bool True if registered successfully
     */
    public function register_external_module( ModuleInterface $module, string $required_tier = 'pro' ): bool {
        // Validate tier requirement
        $valid_tiers = ['pro', 'business', 'agency'];
        if ( ! in_array( $required_tier, $valid_tiers, true ) ) {
            return false;
        }

        return false;
    }
}
