<?php

namespace AtlasAiDev\Awfah;


use AtlasAiDev\Awfah\Admin\Menu;
use AtlasAiDev\Awfah\Admin\Post_Meta_Box;
use AtlasAiDev\Awfah\Admin\MCP_Settings;
use AtlasAiDev\Awfah\Admin\Editor_Experiments;
use AtlasAiDev\Awfah\Admin\Classic_Experiments;
use AtlasAiDev\Awfah\Admin\Notices;
use AtlasAiDev\Awfah\Admin\Error_Dashboard_Widget;
use AtlasAiDev\Awfah\REST\Settings_Controller;
use AtlasAiDev\Awfah\REST\Content_Steward_Controller;
use AtlasAiDev\Awfah\REST\Providers_Controller;
use AtlasAiDev\Awfah\REST\Abilities_Controller;
use AtlasAiDev\Awfah\REST\Workflows_Controller;
use AtlasAiDev\Awfah\REST\Prompts_Controller;
use AtlasAiDev\Awfah\REST\Experiments_Controller;
use AtlasAiDev\Awfah\Abilities\Content_Steward_Abilities;
use AtlasAiDev\Awfah\Abilities\Posts_Abilities;
use AtlasAiDev\Awfah\Abilities\Pages_Abilities;
use AtlasAiDev\Awfah\Abilities\Media_Abilities;
use AtlasAiDev\Awfah\Abilities\Users_Abilities;
use AtlasAiDev\Awfah\Abilities\Custom_Post_Types_Abilities;
use AtlasAiDev\Awfah\Abilities\Rest_Api_Abilities;
use AtlasAiDev\Awfah\Abilities\Site_Info_Abilities;
use AtlasAiDev\Awfah\Abilities\Settings_Abilities;
use AtlasAiDev\Awfah\Abilities\Store_Manager_Abilities;
use AtlasAiDev\Awfah\Abilities\Debug_Log_Abilities;
use AtlasAiDev\Awfah\Service\Error_Email_Notifier;
use AtlasAiDev\Awfah\Auth\JwtAuth;
use AtlasAiDev\Awfah\Resources\Resource_Registry;
use AtlasAiDev\Awfah\Prompts\Prompt_Registry;

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

class Plugin {

    const OPTION_KEY     = 'awfah_settings';
    const MCP_OPTION_KEY = 'awfah_mcp_settings';


    /** @var self */
    private static $instance;

    /** @var Menu */
    protected $menu;

    /** @var Post_Meta_Box */
    protected $post_meta_box;

    /** @var MCP_Settings */
    protected $mcp_settings;

    /** @var Settings_Controller */
    protected $settings_controller;

    /** @var Content_Steward_Controller */
    protected $content_controller;

    /** @var Providers_Controller */
    protected $providers_controller;

    /** @var Abilities_Controller */
    protected $abilities_controller;

    /** @var Workflows_Controller */
    protected $workflows_controller;

    /** @var Prompts_Controller */
    protected $prompts_controller;

    /** @var Experiments_Controller */
    protected $experiments_controller;

    /** @var Editor_Experiments */
    protected $editor_experiments;

    /** @var Classic_Experiments */
    protected $classic_experiments;

    /** @var MCP_Controller */
    protected $mcp_controller;

    /** @var Mcp_Page */
    protected $mcp_page;

    public static function instance(): self {
        if ( ! isset( self::$instance ) ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        $this->define_constants();
        $this->init_error_notifier();
        // Deferred to `init`: it enumerates ability get_abilities() (which use
        // __() for labels), and this constructor runs on `plugins_loaded` —
        // calling __() before `init` triggers the WP 6.7+ "translation loading
        // triggered too early" notice. It only seeds an option on first run, so
        // nothing before `init` depends on it.
        add_action( 'init', [ $this, 'maybe_init_role_abilities' ], 5 );
        $this->init_admin();
        $this->init_rest();
        $this->init_abilities();
        $this->init_mcp();
        $this->init_oauth();
        $this->init_wpai_bridge();
    }

    /**
     * Initialize the bridge to the official WordPress AI plugin (AI-10).
     * Self-gates: no-ops entirely when that plugin is inactive.
     */
    private function init_wpai_bridge(): void {
        new \AtlasAiDev\Awfah\Integrations\Wpai_Bridge();
    }

    /**
     * Initialize the OAuth 2.1 server (AI-9). Self-gates on the enabled flag;
     * instantiating only wires hooks, it does not expose anything until enabled.
     */
    private function init_oauth(): void {
        new \AtlasAiDev\Awfah\OAuth\OAuth_Server();
    }


    /**
     * Initialize error email notifier when error_debugger module is enabled.
     */
    private function init_error_notifier(): void {
        $settings = self::get_settings();
        if ( empty( $settings['modules']['error_debugger'] ) ) {
            return;
        }

        Error_Email_Notifier::instance()->register_handlers();

        // Monitor debug.log for errors that occurred before our plugin loaded
        // (e.g. parse errors during plugin activation of alphabetically earlier plugins).
        add_action( 'admin_init', [ Error_Email_Notifier::class, 'check_for_missed_errors' ], 5 );

        add_action( 'template_redirect', [ Error_Email_Notifier::class, 'handle_unsubscribe' ] );
        add_action( 'admin_init', [ Error_Email_Notifier::class, 'handle_unsubscribe' ] );
    }

    private function define_constants(): void {
        if ( ! defined( 'AWFAH_VERSION' ) ) {
            define( 'AWFAH_VERSION', '1.6.6' );
        }
        if ( ! defined( 'AWFAH_PLUGIN_FILE' ) ) {
            define( 'AWFAH_PLUGIN_FILE', dirname( __DIR__ ) . '/ai-workflow-automation-ai-agent-hub.php' );
        }
        if ( ! defined( 'AWFAH_PLUGIN_DIR' ) ) {
            define( 'AWFAH_PLUGIN_DIR', plugin_dir_path( AWFAH_PLUGIN_FILE ) );
        }
        if ( ! defined( 'AWFAH_PLUGIN_URL' ) ) {
            define( 'AWFAH_PLUGIN_URL', plugin_dir_url( AWFAH_PLUGIN_FILE ) );
        }
    }

    /**
     * Default settings for module toggles.
     */
    public static function get_default_settings(): array {
        return [
            'modules' => [
                'content_steward'  => true,
                'pages_manager'    => true,
                'media_manager'    => true,
                'users_manager'    => true,
                'cpt_manager'      => true,
                'rest_api'         => true,
                'site_info'        => true,
                'settings_manager' => true,
                'store_manager'       => true,
                'external_abilities' => true,
                'error_debugger'     => false,
                'support_desk'       => false,
                'automations'      => false,
            ],
            'role_abilities'  => [],
            'role_workflows'  => [],
        ];
    }

    public static function get_settings(): array {
        $stored   = get_option( self::OPTION_KEY, [] );
        $defaults = self::get_default_settings();
        $merged   = wp_parse_args( $stored, $defaults );

        // Deep-merge modules so new module keys pick up their defaults.
        if ( isset( $defaults['modules'] ) ) {
            $stored_modules = isset( $stored['modules'] ) && is_array( $stored['modules'] )
                ? $stored['modules']
                : [];
            $merged['modules'] = wp_parse_args( $stored_modules, $defaults['modules'] );
        }

        if ( ! isset( $merged['role_abilities'] ) || ! is_array( $merged['role_abilities'] ) ) {
            $merged['role_abilities'] = [];
        }

        if ( ! isset( $merged['role_workflows'] ) || ! is_array( $merged['role_workflows'] ) ) {
            $merged['role_workflows'] = [];
        }

        return $merged;
    }

    public static function update_settings( array $settings ): void {
        $defaults = self::get_default_settings();
        $merged   = wp_parse_args( $settings, $defaults );

        // Deep-merge modules so new module keys are preserved.
        if ( isset( $defaults['modules'] ) ) {
            $input_modules = isset( $settings['modules'] ) && is_array( $settings['modules'] )
                ? $settings['modules']
                : [];
            $merged['modules'] = wp_parse_args( $input_modules, $defaults['modules'] );
        }

        // Preserve role_abilities if provided.
        if ( isset( $settings['role_abilities'] ) && is_array( $settings['role_abilities'] ) ) {
            $merged['role_abilities'] = $settings['role_abilities'];
        }

        // Preserve role_workflows if provided.
        if ( isset( $settings['role_workflows'] ) && is_array( $settings['role_workflows'] ) ) {
            $merged['role_workflows'] = $settings['role_workflows'];
        }

        update_option( self::OPTION_KEY, $merged );
    }

    /**
     * Default MCP settings used when no option exists.
     */
    public static function get_default_mcp_settings(): array {
        return [
            'server_id'   => 'ai-agent-hub-mcp',
            'namespace'   => 'awfah_mcp',
            'route'       => 'mcp',
            'name'        => 'AtlasAI Connector Server',
            'description' => 'Expose AtlasAI Connector abilities, Resources, Prompts to MCP clients.',
            'version'     => AWFAH_VERSION,
            'abilities'   => self::get_default_mcp_abilities(),
            'oauth'       => [
                'client_id'     => '',
                'client_secret' => '',
                'auth_url'      => '',
                'token_url'     => '',
                'redirect_uri'  => '',
                'scopes'        => '',
                'access_token'  => '',
                'refresh_token' => '',
                'expires_at'    => 0,
            ],
        ];
    }

    /**
     * Retrieve MCP settings merged with defaults.
     */
    public static function get_mcp_settings(): array {

        $stored = get_option( self::MCP_OPTION_KEY, [] );
        $merged = wp_parse_args( $stored, self::get_default_mcp_settings() );

        if ( empty( $merged['abilities'] ) || ! is_array( $merged['abilities'] ) ) {
            $merged['abilities'] = [];
        }

        if ( empty( $merged['oauth'] ) || ! is_array( $merged['oauth'] ) ) {
            $merged['oauth'] = self::get_default_mcp_settings()['oauth'];
        } else {
            $merged['oauth'] = wp_parse_args( $merged['oauth'], self::get_default_mcp_settings()['oauth'] );
        }

        return $merged;
    }

    /**
     * Persist MCP settings.
     */
    public static function update_mcp_settings( array $settings ): void {
        $defaults = self::get_default_mcp_settings();
        $clean    = wp_parse_args( $settings, $defaults );

        $clean['abilities'] = array_values(
            array_unique(
                array_filter(
                    array_map( 'sanitize_text_field', $clean['abilities'] )
                )
            )
        );

        $oauth          = isset( $clean['oauth'] ) && is_array( $clean['oauth'] ) ? $clean['oauth'] : [];
        $clean['oauth'] = wp_parse_args( $oauth, $defaults['oauth'] );
        $clean['oauth'] = array_merge(
            $clean['oauth'],
            array_map(
                static function ( $value ) {
                    return is_string( $value ) ? trim( wp_kses_post( $value ) ) : '';
                },
                [
                    'client_id'     => $clean['oauth']['client_id'],
                    'client_secret' => $clean['oauth']['client_secret'],
                    'auth_url'      => $clean['oauth']['auth_url'],
                    'token_url'     => $clean['oauth']['token_url'],
                    'redirect_uri'  => $clean['oauth']['redirect_uri'],
                    'scopes'        => $clean['oauth']['scopes'],
                    'access_token'  => $clean['oauth']['access_token'],
                    'refresh_token' => $clean['oauth']['refresh_token'],
                ]
            )
        );

        $clean['oauth']['expires_at'] = isset( $clean['oauth']['expires_at'] ) ? (int) $clean['oauth']['expires_at'] : 0;

        update_option( self::MCP_OPTION_KEY, $clean );
    }

    /**
     * Static cache for external abilities (request-scoped).
     *
     * @var array[]|null
     */
    private static ?array $external_abilities_cache = null;

    /**
     * Discover external abilities registered by WordPress core and third-party plugins.
     *
     * Returns abilities NOT owned by this plugin (names that do NOT start with "awfah-").
     * Each ability is normalized to the same array shape used by the plugin's own ability classes.
     *
     * @return array[] List of ability arrays.
     */
    public static function get_external_abilities(): array {
        if ( null !== self::$external_abilities_cache ) {
            return self::$external_abilities_cache;
        }

        if ( ! function_exists( 'wp_get_abilities' ) ) {
            self::$external_abilities_cache = [];
            return self::$external_abilities_cache;
        }

        $all_abilities = wp_get_abilities();
        $external      = [];

        foreach ( $all_abilities as $ability ) {
            $name = $ability->get_name();

            // Skip abilities registered by this plugin.
            if ( str_starts_with( $name, 'awfah-' ) ) {
                continue;
            }

            // Derive the namespace prefix (e.g., "core" from "core/get-site-info").
            $parts     = explode( '/', $name, 2 );
            $namespace = $parts[0] ?? 'unknown';

            $external[] = [
                'id'            => $name,
                'module'        => 'external_abilities',
                'label'         => $ability->get_label(),
                'description'   => $ability->get_description(),
                'category'      => $ability->get_category(),
                'input_schema'  => $ability->get_input_schema(),
                'output_schema' => $ability->get_output_schema(),
                'meta'          => $ability->get_meta(),
                'namespace'     => $namespace,
            ];
        }

        self::$external_abilities_cache = $external;
        return self::$external_abilities_cache;
    }

    /**
     * Module-key to ability-class mapping.
     */
    public static function get_module_class_map(): array {
        $map = [
            'content_steward'  => [ Content_Steward_Abilities::class, Posts_Abilities::class ],
            'pages_manager'    => [ Pages_Abilities::class ],
            'media_manager'    => [ Media_Abilities::class ],
            'users_manager'    => [ Users_Abilities::class ],
            'cpt_manager'      => [ Custom_Post_Types_Abilities::class ],
            'rest_api'         => [ Rest_Api_Abilities::class ],
            'site_info'        => [ Site_Info_Abilities::class ],
            'settings_manager' => [ Settings_Abilities::class ],
            'store_manager'    => [ Store_Manager_Abilities::class ],
            'error_debugger'   => [ Debug_Log_Abilities::class ],
        ];

        /**
         * Allow Pro or third-party plugins to add ability classes to module mappings.
         *
         * @param array $map Module key => array of ability class names.
         */
        return apply_filters( 'awfah_module_class_map', $map );
    }

    /**
     * Helper to collect available ability IDs keyed by label for MCP exposure.
     */
    public static function get_available_mcp_abilities(): array {
        $abilities        = [];
        $settings         = self::get_settings();
        $module_class_map = self::get_module_class_map();

        foreach ( $module_class_map as $module_key => $classes ) {
            if ( empty( $settings['modules'][ $module_key ] ) ) {
                continue;
            }
            // Skip store_manager when WooCommerce is not active.
            if ( 'store_manager' === $module_key && ! class_exists( 'WooCommerce' ) ) {
                continue;
            }
            foreach ( $classes as $class ) {
                foreach ( $class::get_abilities() as $ability ) {
                    $abilities[] = [
                        'id'     => $ability['id'],
                        'label'  => $ability['label'] ?? $ability['id'],
                        'module' => $module_key,
                    ];
                }
            }
        }

        // Include external abilities if the module is enabled (Pro only).
        if ( ! empty( $settings['modules']['external_abilities'] ) && apply_filters( 'awfah_enable_external_abilities', false ) ) {
            foreach ( self::get_external_abilities() as $ext ) {
                $abilities[] = [
                    'id'     => $ext['id'],
                    'label'  => $ext['label'] ?? $ext['id'],
                    'module' => 'external_abilities',
                ];
            }
        }
//        error_log(print_r( $abilities, true ));
        return $abilities;
    }

    /**
     * Build a map of ability-ID => module-key for all known abilities.
     */
    public static function build_ability_module_map(): array {
        $map              = [];
        $module_class_map = self::get_module_class_map();

        foreach ( $module_class_map as $module_key => $classes ) {
            foreach ( $classes as $class ) {
                foreach ( $class::get_abilities() as $ability ) {
                    $map[ $ability['id'] ] = $module_key;
                }
            }
        }

        // Map external abilities to the 'external_abilities' module.
        foreach ( self::get_external_abilities() as $ext ) {
            $map[ $ext['id'] ] = 'external_abilities';
        }

        return $map;
    }

    /**
     * Get ability IDs that the current user is allowed to access based on role assignments.
     *
     * @return array List of ability IDs, or empty array if no access.
     */
    public static function get_current_user_allowed_abilities(): array {
        $user = wp_get_current_user();
        if ( ! $user || ! $user->exists() ) {
            return [];
        }

        $settings       = self::get_settings();
        $global_modules = $settings['modules'] ?? [];
        $role_config    = $settings['role_abilities'] ?? [];

        $allowed_abilities = [];

        foreach ( $user->roles as $role ) {
            if ( ! isset( $role_config[ $role ] ) ) {
                continue;
            }
            $role_data         = $role_config[ $role ];
            $allowed_abilities = array_merge( $allowed_abilities, $role_data['abilities'] ?? [] );
        }

        $allowed_abilities  = array_unique( $allowed_abilities );
        $ability_module_map = self::build_ability_module_map();

        // Filter out abilities whose module is globally disabled.
        $allowed_abilities = array_filter( $allowed_abilities, static function ( $ability_id ) use ( $global_modules, $ability_module_map ) {
            $module = $ability_module_map[ $ability_id ] ?? '';
            return ! empty( $global_modules[ $module ] );
        } );

        return array_values( $allowed_abilities );
    }

    /**
     * Get workflow IDs that the current user is allowed to access based on role assignments.
     *
     * Resolution logic:
     * 1. If role_workflows is configured in settings, use it (explicit override).
     * 2. Otherwise, fall back to each workflow's own assigned_roles field.
     * 3. In both cases, the user must also have access to the workflow's underlying abilities.
     *
     * @return array List of workflow IDs the current user can access.
     */
    public static function get_current_user_allowed_workflows(): array {
        $user = wp_get_current_user();
        if ( ! $user || ! $user->exists() ) {
            return [];
        }

        $settings        = self::get_settings();
        $role_workflows  = $settings['role_workflows'] ?? [];
        $allowed_abilities = self::get_current_user_allowed_abilities();

        $stored    = get_option( Workflows_Controller::OPTION_KEY, [] );
        $workflows = is_array( $stored ) ? $stored : [];

        if ( empty( $workflows ) ) {
            return [];
        }

        $user_roles = $user->roles;
        $allowed_workflow_ids = [];

        // Check if role_workflows has any actual assignments.
        $has_role_workflow_config = false;
        foreach ( $role_workflows as $role => $wf_ids ) {
            if ( ! empty( $wf_ids ) ) {
                $has_role_workflow_config = true;
                break;
            }
        }

        foreach ( $workflows as $workflow ) {
            $wf_id = $workflow['id'] ?? '';
            if ( empty( $wf_id ) ) {
                continue;
            }

            // Check that the user has access to the workflow's underlying abilities.
            $wf_abilities = $workflow['abilities'] ?? [];
            if ( empty( $wf_abilities ) ) {
                continue;
            }
            $has_ability_access = ! empty( array_intersect( $wf_abilities, $allowed_abilities ) );
            if ( ! $has_ability_access ) {
                continue;
            }

            if ( $has_role_workflow_config ) {
                // Use explicit role_workflows config.
                foreach ( $user_roles as $role ) {
                    $role_wf_ids = $role_workflows[ $role ] ?? [];
                    if ( in_array( $wf_id, $role_wf_ids, true ) ) {
                        $allowed_workflow_ids[] = $wf_id;
                        break;
                    }
                }
            } else {
                // Fall back to workflow's own assigned_roles field.
                $assigned_roles = $workflow['assigned_roles'] ?? [ 'all' ];
                if ( in_array( 'all', $assigned_roles, true ) ) {
                    $allowed_workflow_ids[] = $wf_id;
                } else {
                    foreach ( $user_roles as $role ) {
                        if ( in_array( $role, $assigned_roles, true ) ) {
                            $allowed_workflow_ids[] = $wf_id;
                            break;
                        }
                    }
                }
            }
        }

        return array_values( array_unique( $allowed_workflow_ids ) );
    }

    /**
     * Default abilities list to expose over MCP (based on enabled modules).
     */
    public static function get_default_mcp_abilities(): array {
        $abilities = self::get_available_mcp_abilities();

        return array_values(
            array_unique(
                array_map(
                    static function ( $item ) {
                        return $item['id'];
                    },
                    $abilities
                )
            )
        );
    }


    private function init_admin(): void {
        if ( is_admin() ) {
            $this->menu                = new Menu();
            $this->post_meta_box       = new Post_Meta_Box();
            $this->mcp_settings        = new MCP_Settings();
            $this->editor_experiments  = new Editor_Experiments();
            $this->classic_experiments = new Classic_Experiments();

            // Pro upsell notice (only shows when Pro is not active).
            Notices::instance();

            // Error dashboard widget (only when error_debugger module is enabled and not disabled by user).
            $settings = self::get_settings();
            if ( ! empty( $settings['modules']['error_debugger'] ) ) {
                $notif = $settings['error_notification'] ?? [];
                if ( ! isset( $notif['show_dashboard_widget'] ) || $notif['show_dashboard_widget'] ) {
                    new Error_Dashboard_Widget();
                }
            }
        }
    }

    private function init_rest(): void {
        $this->settings_controller  = new Settings_Controller();
        $this->content_controller   = new Content_Steward_Controller();
        $this->providers_controller = new Providers_Controller();
        $this->abilities_controller = new Abilities_Controller();
        $this->workflows_controller = new Workflows_Controller();
        $this->prompts_controller   = new Prompts_Controller();
        $this->experiments_controller = new Experiments_Controller();

        add_action(
            'rest_api_init',
            function () {
                $this->settings_controller->register_routes();
                $this->content_controller->register_routes();
                $this->providers_controller->register_routes();
                $this->abilities_controller->register_routes();
                $this->workflows_controller->register_routes();
                $this->prompts_controller->register_routes();
                $this->experiments_controller->register_routes();
            }
        );
    }

    private function init_abilities(): void {
        // Register ability categories.
        add_action(
            'wp_abilities_api_categories_init',
            [ $this, 'register_ability_categories' ]
        );

        // Register abilities depending on enabled modules.
        add_action(
            'wp_abilities_api_init',
            [ $this, 'register_abilities' ]
        );



    }

    /**
     * On first run, pre-populate administrator role with all enabled abilities.
     *
     * Hooked to `init` (not the constructor) so the ability label translations
     * it evaluates run at/after `init`. See __construct().
     */
    public function maybe_init_role_abilities(): void {
        $settings = self::get_settings();

        if ( ! empty( $settings['role_abilities'] ) ) {
            return;
        }

        if ( '1' === get_option( 'awfah_rbac_initialized' ) ) {
            return;
        }

        $all         = self::get_available_mcp_abilities();
        $modules     = array_values( array_unique( array_column( $all, 'module' ) ) );
        $ability_ids = array_values( array_column( $all, 'id' ) );

        $settings['role_abilities'] = [
            'administrator' => [
                'modules'   => $modules,
                'abilities' => $ability_ids,
            ],
        ];

        self::update_settings( $settings );
        update_option( 'awfah_rbac_initialized', '1' );
    }

    private function init_mcp(): void {
        if ( ! class_exists( '\WP\MCP\Core\McpAdapter' ) ) {
            return;
        }

        // Disable the mcp-adapter's built-in "default server". It auto-registers
        // helper abilities (mcp-adapter/discover-abilities, /get-ability-info,
        // /execute-ability) on wp_abilities_api_init, but only adds that hook
        // during rest_api_init — after the action has already fired — so the
        // abilities are never registered and the default server logs
        // "ability does not exist" on every request. We register our own MCP
        // server with our own abilities, so the default server is redundant.
        add_filter( 'mcp_adapter_create_default_server', '__return_false' );

        \WP\MCP\Core\McpAdapter::instance();

        add_action(
            'mcp_adapter_init',
            [ $this, 'register_mcp_server' ],
            20
        );

        // Role-based tool filtering for MCP.
        add_filter( 'mcp_adapter_list_tools', [ $this, 'filter_mcp_tools_by_role' ], 10, 2 );
        add_filter( 'mcp_adapter_can_call_tool', [ $this, 'check_mcp_tool_call_access' ], 10, 3 );

        // Initialize the JWT authentication.
        new JwtAuth();
    }

    /**
     * Register the MCP server exposed by this plugin.
     *
     * @param mixed $adapter Adapter instance from the MCP Adapter plugin.
     */
    public function register_mcp_server( $adapter ): void {
        if ( ! class_exists( '\WP\MCP\Transport\HttpTransport' ) || ! is_object( $adapter ) ) {
            return;
        }

        // The MCP server MUST be created during this (mcp_adapter_init) action — creating it on
        // any other hook triggers a "called incorrectly" notice and the server is never
        // registered (no REST route → 404). Our abilities register on wp_abilities_api_init,
        // which the Abilities API fires LAZILY the first time its registry is accessed; on WP 7.0
        // that can happen after mcp_adapter_init. So if it hasn't fired yet, force the registry to
        // initialise now (which fires wp_abilities_api_init and registers our abilities via the
        // hook), then build the server here, in-context.
        if ( ! did_action( 'wp_abilities_api_init' ) && function_exists( 'wp_get_abilities' ) ) {
            wp_get_abilities();
        }

        $this->build_mcp_server( $adapter );
    }

    /**
     * Build and register the MCP server once abilities are available.
     *
     * @param mixed $adapter Adapter instance from the MCP Adapter plugin.
     */
    private function build_mcp_server( $adapter ): void {
        $settings = self::get_mcp_settings();

        // Only expose abilities from modules that are currently enabled.
        $available_ids = array_map(
            static function ( $item ) {
                return $item['id'];
            },
            self::get_available_mcp_abilities()
        );

        // Intersect with the saved MCP abilities list (user's selection).
        $mcp_ability_ids = $settings['abilities'] ?? [];
        if ( ! empty( $mcp_ability_ids ) ) {
            $abilities = array_values( array_intersect( $mcp_ability_ids, $available_ids ) );
        } else {
            $abilities = $available_ids;
        }

        // Verify each ability is actually registered in the Abilities API.
        if ( function_exists( 'wp_get_ability' ) ) {
            $abilities = array_values(
                array_filter(
                    $abilities,
                    static function ( $ability ) {
                        return (bool) wp_get_ability( (string) $ability );
                    }
                )
            );
        }


        $permission_callback = null;
        $oauth_settings      = $settings['oauth'] ?? [];

        if ( ! empty( $oauth_settings['access_token'] ) && is_string( $oauth_settings['access_token'] ) ) {
            $expected_token      = $oauth_settings['access_token'];
            $permission_callback = static function ( $request ) use ( $expected_token ) {
                $header = '';
                if ( method_exists( $request, 'get_header' ) ) {
                    $header = (string) $request->get_header( 'authorization' );
                }

                if ( '' !== $header && preg_match( '/Bearer\\s+(.*)$/i', $header, $matches ) ) {
                    $provided = trim( (string) ( $matches[1] ?? '' ) );
                    if ( $provided && hash_equals( $expected_token, $provided ) ) {
                        return true;
                    }
                }

                return current_user_can( apply_filters( 'mcp_adapter_default_transport_permission_user_capability', 'read' ) );
            };
        }

        $adapter->create_server(
            sanitize_key( $settings['server_id'] ),
            sanitize_key( $settings['namespace'] ),
            sanitize_key( $settings['route'] ),
            $settings['name'],
            $settings['description'],
            $settings['version'],
            [
                \WP\MCP\Transport\HttpTransport::class,
            ],
            \WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler::class,
            \WP\MCP\Infrastructure\Observability\NullMcpObservabilityHandler::class,
            $abilities,
            apply_filters( 'awfah_mcp_resource_ids', Resource_Registry::get_resource_ability_ids() ),
            apply_filters( 'awfah_mcp_prompt_ids', Prompt_Registry::get_prompt_identifiers() ),
            $permission_callback
        );
    }

    /**
     * Get the MCP tool names the current user is allowed to access.
     *
     * Returns null when RBAC does not apply (no config, or user's roles not in config),
     * meaning all tools should be shown. Returns an array of tool names when filtering applies.
     *
     * @return string[]|null Allowed tool names, or null if no restriction.
     */
    private static function get_allowed_mcp_tool_names(): ?array {
        $settings    = self::get_settings();
        $role_config = $settings['role_abilities'] ?? [];

        // No RBAC configured — allow all.
        if ( empty( $role_config ) ) {
            return null;
        }

        $user = wp_get_current_user();
        if ( ! $user || ! $user->exists() ) {
            return null;
        }

        // Check if any of the user's roles have RBAC entries.
        $user_has_rbac = false;
        foreach ( $user->roles as $role ) {
            if ( isset( $role_config[ $role ] ) ) {
                $user_has_rbac = true;
                break;
            }
        }

        // User's roles aren't configured in RBAC — allow all.
        if ( ! $user_has_rbac ) {
            return null;
        }

        $allowed = self::get_current_user_allowed_abilities();

        // Convert ability IDs (vendor/name) to MCP tool names (vendor-name).
        return array_map( static function ( $ability_id ) {
            return str_replace( '/', '-', $ability_id );
        }, $allowed );
    }

    /**
     * Filter MCP tools/list response based on the current user's role abilities.
     *
     * @param array  $tools     Sanitized tool data array.
     * @param string $server_id MCP server ID.
     * @return array Filtered tools.
     */
    public function filter_mcp_tools_by_role( array $tools, string $server_id ): array {
        $allowed = self::get_allowed_mcp_tool_names();

        // null means no restriction.
        if ( null === $allowed ) {
            return $tools;
        }

        return array_values( array_filter( $tools, static function ( $tool ) use ( $allowed ) {
            return in_array( $tool['name'] ?? '', $allowed, true );
        } ) );
    }

    /**
     * Check if the current user can call a specific MCP tool based on role abilities.
     *
     * @param bool   $can_call  Current access status.
     * @param string $tool_name MCP tool name.
     * @param string $server_id MCP server ID.
     * @return bool
     */
    public function check_mcp_tool_call_access( bool $can_call, string $tool_name, string $server_id ): bool {
        if ( ! $can_call ) {
            return false;
        }

        $allowed = self::get_allowed_mcp_tool_names();

        // null means no restriction.
        if ( null === $allowed ) {
            return true;
        }

        return in_array( $tool_name, $allowed, true );
    }

    /**
     * Check if the current user has role-based access to a specific ability.
     *
     * Returns true if access is allowed, or a WP_Error with a user-friendly
     * message if the user's role does not have permission for this ability.
     *
     * @param string          $ability_id   The ability ID (e.g. 'awfah-posts/wp-add-post').
     * @param string|string[] $capabilities One or more WP capabilities (any must match).
     * @return true|\WP_Error True if allowed, WP_Error otherwise.
     */
    public static function check_ability_access( string $ability_id, $capabilities ) {
        $caps    = (array) $capabilities;
        $has_cap = false;
        foreach ( $caps as $cap ) {
            if ( current_user_can( $cap ) ) {
                $has_cap = true;
                break;
            }
        }
        if ( ! $has_cap ) {
            return new \WP_Error(
                'insufficient_capability',
                sprintf(
                    'You do not have permission to perform this action. Your user account lacks the required "%s" capability.',
                    implode( '" or "', $caps )
                )
            );
        }

        $settings    = self::get_settings();
        $role_config = $settings['role_abilities'] ?? [];

        // No RBAC configured — allow based on capability alone.
        if ( empty( $role_config ) ) {
            return true;
        }

        $user = wp_get_current_user();
        if ( ! $user || ! $user->exists() ) {
            return new \WP_Error(
                'not_authenticated',
                'You must be logged in to use this tool. Please check your authentication credentials.'
            );
        }

        // Check if any of the user's roles have RBAC entries.
        $user_has_rbac = false;
        foreach ( $user->roles as $role ) {
            if ( isset( $role_config[ $role ] ) ) {
                $user_has_rbac = true;
                break;
            }
        }

        // User's roles aren't configured in RBAC — allow based on capability.
        if ( ! $user_has_rbac ) {
            return true;
        }

        $allowed = self::get_current_user_allowed_abilities();

        if ( in_array( $ability_id, $allowed, true ) ) {
            return true;
        }

        $user_roles = implode( ', ', $user->roles );
        $tool_label = str_replace( [ 'awfah-', '/' ], [ '', ' > ' ], $ability_id );

        return new \WP_Error(
            'role_access_denied',
            sprintf(
                'Access denied: Your role (%s) does not have permission to use the "%s" tool. Please contact your site administrator to request access via the Access Control settings.',
                $user_roles,
                $tool_label
            )
        );
    }

    public function register_ability_categories(): void {
        if ( ! function_exists( 'wp_register_ability_category' ) ) {
            return;
        }

        wp_register_ability_category(
            'awfah-content',
            [
                'label'       => 'AtlasAI Connector – ' . __( 'Content', 'ai-workflow-automation-ai-agent-hub' ),
                'description' => sprintf( /* translators: %s: plugin brand name (do not translate) */ __( 'Content abilities provided by %s.', 'ai-workflow-automation-ai-agent-hub' ), 'AtlasAI Connector' ),
            ]
        );

        wp_register_ability_category(
            'awfah-posts',
            [
                'label'       => 'AtlasAI Connector – ' . __( 'Posts', 'ai-workflow-automation-ai-agent-hub' ),
                'description' => __( 'WordPress post, category, and tag management abilities.', 'ai-workflow-automation-ai-agent-hub' ),
            ]
        );

        wp_register_ability_category(
            'awfah-pages',
            [
                'label'       => 'AtlasAI Connector – ' . __( 'Pages', 'ai-workflow-automation-ai-agent-hub' ),
                'description' => __( 'WordPress page management abilities.', 'ai-workflow-automation-ai-agent-hub' ),
            ]
        );

        wp_register_ability_category(
            'awfah-media',
            [
                'label'       => 'AtlasAI Connector – ' . __( 'Media', 'ai-workflow-automation-ai-agent-hub' ),
                'description' => __( 'WordPress media management abilities.', 'ai-workflow-automation-ai-agent-hub' ),
            ]
        );

        wp_register_ability_category(
            'awfah-users',
            [
                'label'       => 'AtlasAI Connector – ' . __( 'Users', 'ai-workflow-automation-ai-agent-hub' ),
                'description' => __( 'WordPress user management abilities.', 'ai-workflow-automation-ai-agent-hub' ),
            ]
        );

        wp_register_ability_category(
            'awfah-cpt',
            [
                'label'       => 'AtlasAI Connector – ' . __( 'Custom Post Types', 'ai-workflow-automation-ai-agent-hub' ),
                'description' => __( 'Custom post type management abilities.', 'ai-workflow-automation-ai-agent-hub' ),
            ]
        );

        wp_register_ability_category(
            'awfah-rest-api',
            [
                'label'       => 'AtlasAI Connector – ' . __( 'REST API', 'ai-workflow-automation-ai-agent-hub' ),
                'description' => __( 'Generic WordPress REST API abilities.', 'ai-workflow-automation-ai-agent-hub' ),
            ]
        );

        wp_register_ability_category(
            'awfah-site',
            [
                'label'       => 'AtlasAI Connector – ' . __( 'Site Info', 'ai-workflow-automation-ai-agent-hub' ),
                'description' => __( 'Site information and diagnostics abilities.', 'ai-workflow-automation-ai-agent-hub' ),
            ]
        );

        wp_register_ability_category(
            'awfah-settings',
            [
                'label'       => 'AtlasAI Connector – ' . __( 'Settings', 'ai-workflow-automation-ai-agent-hub' ),
                'description' => __( 'WordPress settings management abilities.', 'ai-workflow-automation-ai-agent-hub' ),
            ]
        );

        wp_register_ability_category(
            'awfah-store',
            [
                'label'       => 'AtlasAI Connector – ' . __( 'Store', 'ai-workflow-automation-ai-agent-hub' ),
                'description' => sprintf( /* translators: %s: plugin brand name (do not translate) */ __( 'WooCommerce abilities provided by %s.', 'ai-workflow-automation-ai-agent-hub' ), 'AtlasAI Connector' ),
            ]
        );

        wp_register_ability_category(
            'awfah-debug',
            [
                'label'       => 'AtlasAI Connector – ' . __( 'Debug', 'ai-workflow-automation-ai-agent-hub' ),
                'description' => __( 'Error debugging and log analysis abilities.', 'ai-workflow-automation-ai-agent-hub' ),
            ]
        );

        wp_register_ability_category(
            'awfah-support',
            [
                'label'       => 'AtlasAI Connector – ' . __( 'Support', 'ai-workflow-automation-ai-agent-hub' ),
                'description' => sprintf( /* translators: %s: plugin brand name (do not translate) */ __( 'Support and helpdesk abilities provided by %s.', 'ai-workflow-automation-ai-agent-hub' ), 'AtlasAI Connector' ),
            ]
        );

        wp_register_ability_category(
            'awfah-automations',
            [
                'label'       => 'AtlasAI Connector – ' . __( 'Automations', 'ai-workflow-automation-ai-agent-hub' ),
                'description' => sprintf( /* translators: %s: plugin brand name (do not translate) */ __( 'Automation and workflow abilities provided by %s.', 'ai-workflow-automation-ai-agent-hub' ), 'AtlasAI Connector' ),
            ]
        );
    }

    public function register_abilities(): void {
        if ( ! function_exists( 'wp_register_ability' ) ) {
            return;
        }

        $settings = self::get_settings();

        if ( ! empty( $settings['modules']['content_steward'] ) ) {
            Content_Steward_Abilities::register();
            Posts_Abilities::register();
        }

        if ( ! empty( $settings['modules']['pages_manager'] ) ) {
            Pages_Abilities::register();
        }

        if ( ! empty( $settings['modules']['media_manager'] ) ) {
            Media_Abilities::register();
        }

        if ( ! empty( $settings['modules']['users_manager'] ) ) {
            Users_Abilities::register();
        }

        if ( ! empty( $settings['modules']['cpt_manager'] ) ) {
            Custom_Post_Types_Abilities::register();
        }

        if ( ! empty( $settings['modules']['rest_api'] ) ) {
            Rest_Api_Abilities::register();
        }

        if ( ! empty( $settings['modules']['site_info'] ) ) {
            Site_Info_Abilities::register();
        }

        if ( ! empty( $settings['modules']['settings_manager'] ) ) {
            Settings_Abilities::register();
        }

        if ( ! empty( $settings['modules']['store_manager'] ) && class_exists( 'WooCommerce' ) ) {
            Store_Manager_Abilities::register();
        }

        if ( ! empty( $settings['modules']['error_debugger'] ) ) {
            Debug_Log_Abilities::register();
        }
    }
}
