<?php

/**
 * Main SlideMind plugin class
 *
 * @package SlideMind
 * @since 2.0.0
 */
namespace SlideMind;

if ( !defined( 'ABSPATH' ) ) {
    exit;
}
class Plugin {
    /**
     * Singleton instance
     * @var Plugin|null
     */
    private static $instance = null;

    /**
     * Plugin version
     * @var string
     */
    private $version;

    /**
     * Plugin path
     * @var string
     */
    private $plugin_path;

    /**
     * Plugin URL
     * @var string
     */
    private $plugin_url;

    /**
     * Core component instances
     */
    private $api_key_manager;

    private $config_repository;

    private $prompt_engine;

    private $llm_proxy;

    /**
     * Core component instances (templates + memory + export)
     */
    private $template_manager;

    private $learner_memory;

    private $config_exporter;

    private $full_exporter;

    private $import_parser;

    private $full_importer;

    /**
     * Admin component instances
     */
    private $admin_menu;

    private $settings_page;

    private $configurations_page;

    private $templates_page;

    private $sandbox_controller;

    private $export_tool_page;

    private $import_tool_page;

    private $downgrade_notice;

    private $memory_admin_page;

    /**
     * Premium component instances (null in free zip)
     */
    private $license_state_watcher;

    private $learner_detail_page;

    private $analytics_dashboard;

    /**
     * Frontend instances
     */
    private $embed_shortcode;

    private $chat_widget;

    /**
     * Privacy management (GDPR)
     */
    private $privacy;

    /**
     * Get the singleton plugin instance
     *
     * @return Plugin
     */
    public static function instance() {
        if ( is_null( self::$instance ) ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Private constructor (singleton)
     */
    private function __construct() {
        $this->version = SLIDEMIND_VERSION;
        $this->plugin_path = SLIDEMIND_PLUGIN_PATH;
        $this->plugin_url = SLIDEMIND_PLUGIN_URL;
        $this->maybe_upgrade_db();
        $this->load_dependencies();
        $this->init_components();
        $this->init_hooks();
    }

    /**
     * Check if the database needs updating and run migration
     */
    private function maybe_upgrade_db() {
        require_once $this->plugin_path . 'includes/class-activator.php';
        Activator::maybe_upgrade();
    }

    /**
     * Load dependencies
     */
    private function load_dependencies() {
        // Core
        require_once $this->plugin_path . 'includes/Core/class-api-key-manager.php';
        require_once $this->plugin_path . 'includes/Core/class-config-repository.php';
        require_once $this->plugin_path . 'includes/Core/class-prompt-engine.php';
        require_once $this->plugin_path . 'includes/Core/class-llm-proxy.php';
        require_once $this->plugin_path . 'includes/Core/class-template-manager.php';
        require_once $this->plugin_path . 'includes/Core/class-learner-memory.php';
        require_once $this->plugin_path . 'includes/Core/class-learner-identity.php';
        require_once $this->plugin_path . 'includes/Core/class-config-exporter.php';
        require_once $this->plugin_path . 'includes/Core/class-bulk-data-manager.php';
        require_once $this->plugin_path . 'includes/Core/class-full-exporter.php';
        require_once $this->plugin_path . 'includes/Core/class-import-manifest.php';
        require_once $this->plugin_path . 'includes/Core/class-import-parser.php';
        require_once $this->plugin_path . 'includes/Core/class-full-importer.php';
        require_once $this->plugin_path . 'includes/Core/class-privacy.php';
        require_once $this->plugin_path . 'includes/Core/class-feature-gate.php';
        // API
        require_once $this->plugin_path . 'includes/API/class-chat-endpoint.php';
        require_once $this->plugin_path . 'includes/API/class-memory-endpoint.php';
        require_once $this->plugin_path . 'includes/API/class-sandbox-endpoint.php';
        require_once $this->plugin_path . 'includes/API/class-whoami-endpoint.php';
        require_once $this->plugin_path . 'includes/API/class-bootstrap-endpoint.php';
        // Frontend
        require_once $this->plugin_path . 'includes/Frontend/class-embed-shortcode.php';
        require_once $this->plugin_path . 'includes/Frontend/class-chat-widget.php';
        // Admin (loaded only in admin context)
        if ( is_admin() ) {
            require_once $this->plugin_path . 'includes/Admin/class-admin-menu.php';
            require_once $this->plugin_path . 'includes/Admin/class-settings-page.php';
            require_once $this->plugin_path . 'includes/Admin/class-configurations-page.php';
            require_once $this->plugin_path . 'includes/Admin/class-templates-page.php';
            require_once $this->plugin_path . 'includes/Admin/class-sandbox-controller.php';
            require_once $this->plugin_path . 'includes/Admin/class-export-tool-page.php';
            require_once $this->plugin_path . 'includes/Admin/class-import-tool-page.php';
            require_once $this->plugin_path . 'includes/Admin/class-downgrade-notice.php';
            require_once $this->plugin_path . 'includes/Admin/class-memory-admin-page.php';
        }
    }

    /**
     * Initialize components
     */
    private function init_components() {
        // Core
        $this->api_key_manager = new Core\ApiKeyManager();
        $this->config_repository = new Core\ConfigRepository($this->api_key_manager);
        $this->prompt_engine = new Core\PromptEngine();
        $this->learner_memory = new Core\LearnerMemory();
        $this->llm_proxy = new Core\LLMProxy($this->api_key_manager, $this->prompt_engine, $this->learner_memory);
        $this->template_manager = new Core\TemplateManager($this->config_repository);
        $this->config_exporter = new Core\ConfigExporter($this->config_repository);
        $this->full_exporter = new Core\FullExporter($this->config_repository, $this->template_manager);
        $this->import_parser = new Core\ImportParser();
        $this->full_importer = new Core\FullImporter($this->config_repository, $this->template_manager);
        $this->privacy = new Core\Privacy($this->learner_memory);
        // Admin
        if ( is_admin() ) {
            $this->admin_menu = new Admin\AdminMenu();
            $this->settings_page = new Admin\SettingsPage($this->api_key_manager);
            $this->configurations_page = new Admin\ConfigurationsPage(
                $this->config_repository,
                $this->llm_proxy,
                $this->learner_memory,
                $this->template_manager,
                $this->config_exporter
            );
            $this->templates_page = new Admin\TemplatesPage($this->template_manager);
            $this->sandbox_controller = new Admin\SandboxController($this->prompt_engine, $this->config_repository);
            $this->export_tool_page = new Admin\ExportToolPage($this->full_exporter);
            $this->import_tool_page = new Admin\ImportToolPage($this->import_parser, $this->full_importer);
            $this->downgrade_notice = new Admin\DowngradeNotice();
            $this->memory_admin_page = new Admin\MemoryAdminPage();
        }
        // Frontend
        $this->embed_shortcode = new Frontend\EmbedShortcode();
        $this->chat_widget = new Frontend\ChatWidget($this->config_repository, $this->prompt_engine, $this->learner_memory);
    }

    /**
     * Initialize WordPress hooks
     */
    private function init_hooks() {
        // Register CPT
        add_action( 'init', [$this, 'register_post_type'] );
        // Register REST endpoints
        add_action( 'rest_api_init', [$this, 'register_rest_routes'] );
        // CORS preflight: must fire on init (not rest_api_init) because
        // Nginx may not route OPTIONS through the REST API lifecycle.
        add_action( 'init', [$this, 'handle_cors_preflight'] );
        add_filter(
            'rest_pre_serve_request',
            [$this, 'handle_cors'],
            10,
            4
        );
        // Capabilities
        add_filter(
            'map_meta_cap',
            [$this, 'map_meta_capabilities'],
            10,
            4
        );
        // Memory hooks (cache invalidation, user deletion, cron purge)
        add_action( 'user_register', [$this->learner_memory, 'invalidate_user_cache'] );
        add_action( 'profile_update', [$this->learner_memory, 'invalidate_user_cache'] );
        add_action( 'delete_user', [$this, 'on_delete_user'] );
        add_action( 'slidemind_purge_memory_cron', [$this, 'purge_old_memory'] );
        // Privacy (GDPR / WP Privacy Tools)
        $this->privacy->register_hooks();
        // Log purge cron
        add_action( 'slidemind_purge_logs_cron', [$this, 'purge_old_logs'] );
        // One-time migration: memory mode slides → QA + draft
        if ( is_admin() ) {
            add_action( 'admin_init', [$this, 'migrate_memory_mode_slides'] );
            add_action( 'admin_notices', [$this, 'show_memory_migration_notice'] );
            add_action( 'admin_notices', [$this, 'show_cors_test_mode_notice'] );
        }
    }

    /**
     * One-time migration: converts slides in memory mode to QA + draft.
     * Memory variables meta are preserved.
     */
    public function migrate_memory_mode_slides() : void {
        if ( get_option( 'slidemind_memory_mode_migrated' ) ) {
            return;
        }
        $posts = get_posts( [
            'post_type'      => 'slidemind_config',
            'meta_query'     => [[
                'key'   => '_slidemind_config_type',
                'value' => 'slide',
            ], [
                'key'   => '_slidemind_widget_mode',
                'value' => 'memory',
            ]],
            'posts_per_page' => -1,
            'post_status'    => 'any',
            'fields'         => 'ids',
        ] );
        $count = count( $posts );
        foreach ( $posts as $post_id ) {
            update_post_meta( $post_id, '_slidemind_widget_mode', 'qa' );
            wp_update_post( [
                'ID'          => $post_id,
                'post_status' => 'draft',
            ] );
        }
        update_option( 'slidemind_memory_mode_migrated', true );
        if ( $count > 0 ) {
            set_transient( 'slidemind_memory_migration_notice', $count, 60 );
        }
    }

    /**
     * Displays a one-time admin notice after memory mode migration.
     */
    public function show_memory_migration_notice() : void {
        $count = get_transient( 'slidemind_memory_migration_notice' );
        if ( !$count ) {
            return;
        }
        delete_transient( 'slidemind_memory_migration_notice' );
        printf( '<div class="notice notice-info is-dismissible"><p><strong>SlideMind:</strong> %s</p></div>', esc_html( sprintf( 
            /* translators: %d: number of migrated slides */
            _n(
                '%d slide in Memory mode has been converted to Q&A (draft). Memory variables are preserved. Please review and republish.',
                '%d slides in Memory mode have been converted to Q&A (draft). Memory variables are preserved. Please review and republish.',
                $count,
                'slidemind'
            ),
            $count
         ) ) );
    }

    /**
     * Checks whether CORS test mode is currently active (enabled and not expired).
     *
     * @param array $settings slidemind_settings option array
     * @return bool
     */
    public static function is_cors_test_mode_active( array $settings ) : bool {
        if ( empty( $settings['cors_test_mode'] ) ) {
            return false;
        }
        $until = (int) ($settings['cors_test_mode_until'] ?? 0);
        return $until <= 0 || time() <= $until;
    }

    /**
     * Displays a persistent admin warning when CORS test mode is active.
     */
    public function show_cors_test_mode_notice() : void {
        $settings = get_option( 'slidemind_settings', [] );
        if ( !self::is_cors_test_mode_active( $settings ) ) {
            return;
        }
        $until = (int) ($settings['cors_test_mode_until'] ?? 0);
        $remaining = ( $until > 0 ? human_time_diff( time(), $until ) : '' );
        $settings_url = admin_url( 'admin.php?page=slidemind-settings' );
        printf( '<div class="notice notice-warning is-dismissible"><p><strong>SlideMind:</strong> %s %s</p></div>', esc_html__( 'CORS test mode is active — API requests are accepted from any origin.', 'slidemind' ), ( $remaining ? wp_kses( sprintf(
            /* translators: 1: remaining time (e.g. "23 hours"), 2: opening <a> tag, 3: closing </a> tag */
            __( 'Auto-expires in %1$s. %2$sDisable now%3$s', 'slidemind' ),
            esc_html( $remaining ),
            '<a href="' . esc_url( $settings_url ) . '">',
            '</a>'
        ), [
            'a' => [
                'href' => [],
            ],
        ] ) : '' ) );
    }

    /**
     * Purge learner memory when a WP user is deleted
     *
     * @param int $user_id
     */
    public function on_delete_user( int $user_id ) : void {
        $this->learner_memory->purge_user( $user_id );
    }

    /**
     * Delete logs older than the configured retention period
     */
    public function purge_old_logs() : void {
        global $wpdb;
        $settings = get_option( 'slidemind_settings', [] );
        $days = max( 1, (int) ($settings['log_retention_days'] ?? 90) );
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}slidemind_logs WHERE created_at < DATE_SUB(NOW(), INTERVAL %d DAY)", $days ) );
    }

    /**
     * Delete learner memory entries exceeding the configured retention period
     */
    public function purge_old_memory() : void {
        $settings = get_option( 'slidemind_settings', [] );
        $days = (int) ($settings['memory_retention_days'] ?? 0);
        // Purge authenticated learner memory (0 = keep forever)
        if ( $days > 0 ) {
            global $wpdb;
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}slidemind_learner_memory WHERE wp_user_id > 0 AND updated_at < DATE_SUB(NOW(), INTERVAL %d DAY)", $days ) );
        }
        // Purge expired anonymous sessions
        $anon_ttl = (int) ($settings['anonymous_session_ttl'] ?? 24);
        if ( $anon_ttl > 0 ) {
            $this->learner_memory->purge_expired_sessions( $anon_ttl );
        }
    }

    /**
     * Register the Custom Post Type
     */
    public function register_post_type() {
        $labels = [
            'name'               => __( 'AI Configurations', 'slidemind' ),
            'singular_name'      => __( 'AI Configuration', 'slidemind' ),
            'menu_name'          => 'SlideMind',
            'all_items'          => __( 'All configurations', 'slidemind' ),
            'add_new_item'       => __( 'Add a configuration', 'slidemind' ),
            'add_new'            => __( 'Add', 'slidemind' ),
            'edit_item'          => __( 'Edit configuration', 'slidemind' ),
            'update_item'        => __( 'Update', 'slidemind' ),
            'search_items'       => __( 'Search', 'slidemind' ),
            'not_found'          => __( 'No configuration found', 'slidemind' ),
            'not_found_in_trash' => __( 'No configuration in trash', 'slidemind' ),
        ];
        $args = [
            'label'           => __( 'AI Configuration', 'slidemind' ),
            'description'     => __( 'AI configurations for e-learning modules', 'slidemind' ),
            'labels'          => $labels,
            'supports'        => ['title', 'author', 'revisions'],
            'public'          => false,
            'show_ui'         => true,
            'show_in_menu'    => false,
            'menu_position'   => 30,
            'menu_icon'       => 'dashicons-format-chat',
            'capability_type' => 'slidemind_config',
            'map_meta_cap'    => true,
            'show_in_rest'    => false,
            'has_archive'     => false,
            'rewrite'         => false,
        ];
        register_post_type( 'slidemind_config', $args );
    }

    /**
     * Register REST API routes
     */
    public function register_rest_routes() {
        // Main chat endpoint
        $chat_endpoint = new API\ChatEndpoint($this->config_repository, $this->llm_proxy, $this->learner_memory);
        $chat_endpoint->register_routes();
        // Learner memory endpoint
        $memory_endpoint = new API\MemoryEndpoint($this->config_repository, $this->learner_memory);
        $memory_endpoint->register_routes();
        // Sandbox endpoint (manual mode)
        // Note: SandboxController may not be instantiated outside admin context
        $sandbox_controller = $this->sandbox_controller;
        if ( !$sandbox_controller ) {
            // Create a temporary instance if not in admin context
            require_once $this->plugin_path . 'includes/Admin/class-sandbox-controller.php';
            $sandbox_controller = new Admin\SandboxController($this->prompt_engine, $this->config_repository);
        }
        $sandbox_endpoint = new API\SandboxEndpoint(
            $this->llm_proxy,
            $this->prompt_engine,
            $this->api_key_manager,
            $sandbox_controller
        );
        $sandbox_endpoint->register_routes();
        // Whoami endpoint (learner identification)
        $whoami_endpoint = new API\WhoamiEndpoint();
        $whoami_endpoint->register_routes();
        // Bootstrap endpoint (nonce + identity issuance for chat/memory clients)
        $bootstrap_endpoint = new API\BootstrapEndpoint($this->config_repository);
        $bootstrap_endpoint->register_routes();
    }

    /**
     * Map custom capabilities
     */
    public function map_meta_capabilities(
        $caps,
        $cap,
        $user_id,
        $args
    ) {
        // Map slidemind_config capabilities to post capabilities
        $capability_map = [
            'edit_slidemind_config'          => 'edit_post',
            'read_slidemind_config'          => 'read_post',
            'delete_slidemind_config'        => 'delete_post',
            'edit_slidemind_configs'         => 'edit_posts',
            'edit_others_slidemind_configs'  => 'edit_others_posts',
            'publish_slidemind_configs'      => 'publish_posts',
            'read_private_slidemind_configs' => 'read_private_posts',
        ];
        if ( isset( $capability_map[$cap] ) ) {
            $caps = [$capability_map[$cap]];
        }
        return $caps;
    }

    /**
     * Handles OPTIONS preflight early, before REST route matching.
     *
     * Browsers may send a preflight OPTIONS that never reaches
     * rest_pre_serve_request. This ensures CORS headers are sent.
     * Calls exit — downstream rest_api_init hooks will not run for OPTIONS.
     */
    public function handle_cors_preflight() : void {
        if ( !isset( $_SERVER['REQUEST_METHOD'] ) || $_SERVER['REQUEST_METHOD'] !== 'OPTIONS' ) {
            return;
        }
        // Check that the request targets a slidemind route
        // phpcs:disable WordPress.Security.NonceVerification.Recommended -- REST route detection, no form processing
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
        $rest_route = ( isset( $_GET['rest_route'] ) ? wp_unslash( $_GET['rest_route'] ) : '' );
        if ( empty( $rest_route ) ) {
            // Pretty permalinks — check REQUEST_URI
            $rest_prefix = rest_get_url_prefix();
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
            $request_uri = ( isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '' );
            if ( strpos( $request_uri, '/' . $rest_prefix . '/slidemind/' ) === false ) {
                return;
            }
        } elseif ( strpos( $rest_route, '/slidemind/' ) === false ) {
            return;
        }
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
        $origin = $this->get_request_origin();
        if ( !empty( $origin ) && $this->is_origin_allowed( $origin ) ) {
            $this->send_cors_headers( $origin );
            header( 'Access-Control-Max-Age: 86400' );
            status_header( 204 );
            exit;
        }
        // Origin present but not allowed: block the preflight to prevent
        // WordPress core from sending permissive CORS headers later.
        if ( !empty( $origin ) ) {
            header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $this->get_default_origin() ) );
            status_header( 403 );
            exit;
        }
        // No Origin header — not a CORS request, let WordPress handle normally.
    }

    /**
     * Sends CORS headers for non-preflight requests on slidemind/v2 endpoints.
     *
     * Preflight OPTIONS are handled earlier by handle_cors_preflight().
     *
     * @param bool             $served  Whether the request has already been served.
     * @param \WP_REST_Response $result  Response object.
     * @param \WP_REST_Request  $request Request object.
     * @param \WP_REST_Server   $server  REST server.
     * @return bool
     */
    public function handle_cors(
        $served,
        $result,
        $request,
        $server
    ) {
        $route = $request->get_route();
        if ( strpos( $route, '/slidemind/' ) === false ) {
            return $served;
        }
        $origin = $this->get_request_origin();
        if ( !empty( $origin ) && $this->is_origin_allowed( $origin ) ) {
            $this->send_cors_headers( $origin );
        } elseif ( !empty( $origin ) ) {
            // Override WordPress core's permissive CORS headers for disallowed origins.
            // Setting the site's own origin ensures the browser blocks the response
            // since it won't match the requesting origin.
            header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $this->get_default_origin() ) );
        }
        return $served;
    }

    /**
     * Returns the sanitized HTTP_ORIGIN header value, or empty string.
     *
     * @return string
     */
    private function get_request_origin() : string {
        return ( isset( $_SERVER['HTTP_ORIGIN'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_ORIGIN'] ) ) : '' );
    }

    /**
     * Checks whether the given origin is allowed by CORS settings.
     *
     * Also auto-disables expired test mode as a side-effect.
     *
     * @param string $origin Sanitized origin value.
     * @return bool
     */
    private function is_origin_allowed( string $origin ) : bool {
        $settings = get_option( 'slidemind_settings', [] );
        if ( self::is_cors_test_mode_active( $settings ) ) {
            return true;
        }
        // Auto-disable expired test mode
        if ( !empty( $settings['cors_test_mode'] ) ) {
            $settings['cors_test_mode'] = false;
            $settings['cors_test_mode_until'] = 0;
            update_option( 'slidemind_settings', $settings );
        }
        $allowed_origins_raw = $settings['cors_allowed_origins'] ?? '';
        $origin_clean = rtrim( $origin, '/' );
        if ( !empty( trim( $allowed_origins_raw ) ) ) {
            $allowed = array_filter( array_map( function ( $line ) {
                return rtrim( trim( $line ), '/' );
            }, explode( "\n", $allowed_origins_raw ) ) );
        } else {
            $allowed = [$this->get_default_origin()];
        }
        return in_array( $origin_clean, $allowed, true );
    }

    /**
     * Sends CORS response headers for an allowed origin.
     *
     * Browsers send the literal string "null" as Origin for file:// and data: URLs.
     * esc_url_raw('null') would mangle it, so we echo it verbatim in that case.
     *
     * @param string $origin The allowed origin value.
     */
    private function send_cors_headers( string $origin ) : void {
        if ( $origin === 'null' ) {
            header( 'Access-Control-Allow-Origin: null' );
        } else {
            header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
            header( 'Access-Control-Allow-Credentials: true' );
        }
        header( 'Access-Control-Allow-Methods: GET, POST, OPTIONS' );
        header( 'Access-Control-Allow-Headers: Content-Type, Authorization, X-WP-Nonce, X-SlideMind-Token' );
    }

    /**
     * Extract the default origin (scheme+host+port) from site_url()
     *
     * @return string
     */
    private function get_default_origin() {
        $parsed = wp_parse_url( site_url() );
        $origin = $parsed['scheme'] . '://' . $parsed['host'];
        if ( !empty( $parsed['port'] ) ) {
            $origin .= ':' . $parsed['port'];
        }
        return $origin;
    }

    /**
     * Component getters
     */
    public function get_api_key_manager() {
        return $this->api_key_manager;
    }

    public function get_config_repository() {
        return $this->config_repository;
    }

    public function get_prompt_engine() {
        return $this->prompt_engine;
    }

    public function get_llm_proxy() {
        return $this->llm_proxy;
    }

    public function get_template_manager() {
        return $this->template_manager;
    }

    public function get_learner_memory() {
        return $this->learner_memory;
    }

    public function get_config_exporter() {
        return $this->config_exporter;
    }

    public function get_full_exporter() {
        return $this->full_exporter;
    }

    public function get_import_parser() {
        return $this->import_parser;
    }

    public function get_full_importer() {
        return $this->full_importer;
    }

    public function get_export_tool_page() {
        return $this->export_tool_page;
    }

    public function get_import_tool_page() {
        return $this->import_tool_page;
    }

    public function get_sandbox_controller() {
        return $this->sandbox_controller;
    }

    public function get_version() {
        return $this->version;
    }

    public function get_plugin_path() {
        return $this->plugin_path;
    }

    public function get_plugin_url() {
        return $this->plugin_url;
    }

    /**
     * Prevent cloning
     */
    private function __clone() {
    }

    /**
     * Prevent unserialization
     */
    public function __wakeup() {
        throw new \Exception('Cannot unserialize singleton');
    }

}
