<?php

/**
 * Configurations management page
 *
 * @package SlideMind
 * @since 2.0.0
 */
namespace SlideMind\Admin;

use SlideMind\Core\ConfigRepository;
use SlideMind\Core\FeatureGate;
use SlideMind\Core\LLMProxy;
use SlideMind\Core\LearnerMemory;
use SlideMind\Core\TemplateManager;
use SlideMind\Core\ConfigExporter;
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
class ConfigurationsPage {
    /**
     * Configuration repository
     * @var ConfigRepository
     */
    private $config_repository;

    /**
     * Proxy LLM
     * @var LLMProxy
     */
    private $llm_proxy;

    /**
     * @var LearnerMemory|null
     */
    private $learner_memory;

    /**
     * @var TemplateManager|null
     */
    private $template_manager;

    /**
     * @var ConfigExporter|null
     */
    private $config_exporter;

    /**
     * Constructor
     *
     * @param ConfigRepository $config_repository
     * @param LLMProxy $llm_proxy
     */
    public function __construct(
        ConfigRepository $config_repository,
        LLMProxy $llm_proxy,
        LearnerMemory $learner_memory = null,
        TemplateManager $template_manager = null,
        ConfigExporter $config_exporter = null
    ) {
        $this->config_repository = $config_repository;
        $this->llm_proxy = $llm_proxy;
        $this->learner_memory = $learner_memory;
        $this->template_manager = $template_manager;
        $this->config_exporter = $config_exporter;
        // Legacy (configurations v1)
        add_action( 'wp_ajax_slidemind_save_configuration', [$this, 'ajax_save_configuration'] );
        add_action( 'wp_ajax_slidemind_delete_configuration', [$this, 'ajax_delete_configuration'] );
        add_action( 'wp_ajax_slidemind_get_configuration', [$this, 'ajax_get_configuration'] );
        add_action( 'wp_ajax_slidemind_get_models', [$this, 'ajax_get_models'] );
        // Course & module admin lives in the premium content component (stripped
        // from the free build); the free product manages standalone slides only.
        // Slides
        add_action( 'wp_ajax_slidemind_get_slides', [$this, 'ajax_get_slides'] );
        add_action( 'wp_ajax_slidemind_get_slide', [$this, 'ajax_get_slide'] );
        add_action( 'wp_ajax_slidemind_save_slide', [$this, 'ajax_save_slide'] );
        add_action( 'wp_ajax_slidemind_delete_slide', [$this, 'ajax_delete_slide'] );
        add_action( 'wp_ajax_slidemind_get_sibling_editor_vars', [$this, 'ajax_get_sibling_editor_vars'] );
        // Inherited content
        add_action( 'wp_ajax_slidemind_get_inherited_content', [$this, 'ajax_get_inherited_content'] );
        // Autocomplete variables
        add_action( 'wp_ajax_slidemind_search_variables', [$this, 'ajax_search_variables'] );
        // AI prompt generation
        add_action( 'wp_ajax_slidemind_generate_prompt', [$this, 'ajax_generate_prompt'] );
        // Export / Import (course & module export live in the premium component)
        add_action( 'wp_ajax_slidemind_export_slide', [$this, 'ajax_export_slide'] );
        add_action( 'wp_ajax_slidemind_import_config', [$this, 'ajax_import_config'] );
    }

    /**
     * Retrieve all configurations for display
     *
     * @return array
     */
    public function get_configurations_for_display() {
        $configs = $this->config_repository->get_all();
        $display_configs = [];
        foreach ( $configs as $config ) {
            $display_configs[] = [
                'id'             => $config['id'],
                'title'          => $config['title'],
                'config_uid'     => $config['config_uid'],
                'course_name'    => $config['course_name'] ?? $config['formation_name'] ?? '',
                'model_id'       => $config['model_id'],
                'status'         => $config['status'],
                'has_custom_key' => $config['has_custom_key'],
                'usage_scope'    => $config['usage_scope'],
                'module_info'    => $config['module_info'],
                'slide_info'     => $config['slide_info'],
                'created_at'     => $config['created_at'],
                'updated_at'     => $config['updated_at'],
                'calls_count'    => $this->get_calls_count( $config['id'] ),
            ];
        }
        return $display_configs;
    }

    /**
     * Get the number of calls for a configuration
     *
     * @param int $config_id
     * @return int
     */
    private function get_calls_count( $config_id ) {
        global $wpdb;
        $table = $wpdb->prefix . 'slidemind_logs';
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        return (int) $wpdb->get_var( $wpdb->prepare( 
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe
            "SELECT COUNT(*) FROM {$table} WHERE config_id = %d",
            $config_id
         ) );
    }

    /**
     * Retrieve available models grouped by category
     *
     * @return array
     */
    public function get_models_grouped( $provider = null ) {
        $models = $this->llm_proxy->get_models( $provider );
        $grouped = [];
        foreach ( $models as $model ) {
            // Extract the provider (first part of the ID if contains /)
            $parts = explode( '/', $model['id'] );
            $group_name = ( count( $parts ) > 1 ? ucfirst( $parts[0] ) : (( $provider ? ucfirst( $provider ) : 'Models' )) );
            if ( !isset( $grouped[$group_name] ) ) {
                $grouped[$group_name] = [];
            }
            $grouped[$group_name][] = [
                'id'      => $model['id'],
                'name'    => $model['name'],
                'pricing' => ( isset( $model['pricing'] ) ? $model['pricing'] : null ),
            ];
        }
        // Sort providers
        ksort( $grouped );
        return $grouped;
    }

    /**
     * AJAX: Save a configuration
     */
    public function ajax_save_configuration() {
        check_ajax_referer( 'slidemind_admin_nonce', 'nonce' );
        if ( !current_user_can( 'manage_slidemind_configs' ) ) {
            wp_send_json_error( [
                'message' => __( 'Unauthorized access', 'slidemind' ),
            ] );
        }
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON data, sanitized after decode
        $data = ( isset( $_POST['configuration'] ) ? wp_unslash( $_POST['configuration'] ) : [] );
        // Basic validation
        if ( empty( $data['title'] ) ) {
            wp_send_json_error( [
                'message' => __( 'Title is required', 'slidemind' ),
            ] );
        }
        if ( empty( $data['course_name'] ) && empty( $data['formation_name'] ) ) {
            wp_send_json_error( [
                'message' => __( 'Course name is required', 'slidemind' ),
            ] );
        }
        if ( empty( $data['model_id'] ) ) {
            wp_send_json_error( [
                'message' => __( 'Model is required', 'slidemind' ),
            ] );
        }
        // Create or update
        if ( !empty( $data['id'] ) ) {
            $result = $this->config_repository->update( (int) $data['id'], $data );
            $config_id = (int) $data['id'];
        } else {
            $result = $this->config_repository->create( $data );
            $config_id = $result;
        }
        if ( is_wp_error( $result ) ) {
            wp_send_json_error( [
                'message' => $result->get_error_message(),
            ] );
        }
        // Retrieve the updated config
        $config = $this->config_repository->get_by_id( $config_id );
        wp_send_json_success( [
            'message'    => __( 'Slide saved', 'slidemind' ),
            'config_id'  => $config_id,
            'config_uid' => $config['config_uid'],
        ] );
    }

    /**
     * AJAX: Delete a configuration
     */
    public function ajax_delete_configuration() {
        check_ajax_referer( 'slidemind_admin_nonce', 'nonce' );
        if ( !current_user_can( 'manage_slidemind_configs' ) ) {
            wp_send_json_error( [
                'message' => __( 'Unauthorized access', 'slidemind' ),
            ] );
        }
        $config_id = ( isset( $_POST['config_id'] ) ? (int) $_POST['config_id'] : 0 );
        if ( !$config_id ) {
            wp_send_json_error( [
                'message' => __( 'Invalid slide ID', 'slidemind' ),
            ] );
        }
        $result = $this->config_repository->delete( $config_id );
        if ( !$result ) {
            wp_send_json_error( [
                'message' => __( 'Unable to delete slide', 'slidemind' ),
            ] );
        }
        wp_send_json_success( [
            'message' => __( 'Slide deleted', 'slidemind' ),
        ] );
    }

    /**
     * AJAX: Retrieve a configuration
     */
    public function ajax_get_configuration() {
        check_ajax_referer( 'slidemind_admin_nonce', 'nonce' );
        if ( !current_user_can( 'manage_slidemind_configs' ) ) {
            wp_send_json_error( [
                'message' => __( 'Unauthorized access', 'slidemind' ),
            ] );
        }
        $config_id = ( isset( $_POST['config_id'] ) ? (int) $_POST['config_id'] : 0 );
        if ( !$config_id ) {
            wp_send_json_error( [
                'message' => __( 'Invalid slide ID', 'slidemind' ),
            ] );
        }
        $config = $this->config_repository->get_by_id( $config_id );
        if ( !$config ) {
            wp_send_json_error( [
                'message' => __( 'Slide not found', 'slidemind' ),
            ] );
        }
        // Do not return the decrypted API key
        unset($config['api_key']);
        wp_send_json_success( [
            'configuration' => $config,
        ] );
    }

    /**
     * AJAX: Retrieve the list of models
     */
    public function ajax_get_models() {
        check_ajax_referer( 'slidemind_admin_nonce', 'nonce' );
        if ( !current_user_can( 'manage_slidemind_configs' ) ) {
            wp_send_json_error( [
                'message' => __( 'Unauthorized access', 'slidemind' ),
            ] );
        }
        $provider = ( isset( $_POST['provider'] ) ? sanitize_text_field( wp_unslash( $_POST['provider'] ) ) : null );
        // Validate the provider
        if ( $provider !== null ) {
            $valid_providers = \SlideMind\Core\LLMProxy::get_provider_slugs();
            if ( !in_array( $provider, $valid_providers, true ) ) {
                $provider = null;
            }
        }
        $models = $this->get_models_grouped( $provider );
        wp_send_json_success( [
            'models' => $models,
        ] );
    }

    // =========================================================================
    // SLIDES
    // =========================================================================
    /**
     * AJAX: Retrieve slides (optionally filtered by module)
     */
    public function ajax_get_slides() {
        check_ajax_referer( 'slidemind_admin_nonce', 'nonce' );
        if ( !current_user_can( 'manage_slidemind_configs' ) ) {
            wp_send_json_error( [
                'message' => __( 'Unauthorized access', 'slidemind' ),
            ] );
        }
        $module_id = ( isset( $_POST['module_id'] ) ? (int) $_POST['module_id'] : 0 );
        if ( $module_id ) {
            $slides = $this->config_repository->get_slides_by_module( $module_id );
        } else {
            $slides = $this->config_repository->get_all_slides();
        }
        // Add the call count for each slide
        foreach ( $slides as &$slide ) {
            $slide['calls_count'] = $this->get_calls_count( $slide['id'] );
        }
        wp_send_json_success( [
            'slides' => $slides,
        ] );
    }

    /**
     * AJAX: Retrieve a slide
     */
    public function ajax_get_slide() {
        check_ajax_referer( 'slidemind_admin_nonce', 'nonce' );
        if ( !current_user_can( 'manage_slidemind_configs' ) ) {
            wp_send_json_error( [
                'message' => __( 'Unauthorized access', 'slidemind' ),
            ] );
        }
        $slide_id = ( isset( $_POST['slide_id'] ) ? (int) $_POST['slide_id'] : 0 );
        if ( !$slide_id ) {
            wp_send_json_error( [
                'message' => __( 'Invalid slide ID', 'slidemind' ),
            ] );
        }
        $slide = $this->config_repository->get_slide( $slide_id );
        if ( !$slide ) {
            wp_send_json_error( [
                'message' => __( 'Slide not found', 'slidemind' ),
            ] );
        }
        // Do not return the decrypted API key
        unset($slide['api_key']);
        // Add inherited content
        $slide['inherited'] = $this->config_repository->get_inherited_content( $slide_id );
        wp_send_json_success( [
            'slide' => $slide,
        ] );
    }

    /**
     * AJAX: Save a slide
     */
    public function ajax_save_slide() {
        check_ajax_referer( 'slidemind_admin_nonce', 'nonce' );
        if ( !current_user_can( 'manage_slidemind_configs' ) ) {
            wp_send_json_error( [
                'message' => __( 'Unauthorized access', 'slidemind' ),
            ] );
        }
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON data, sanitized after decode
        $data = ( isset( $_POST['slide'] ) ? wp_unslash( $_POST['slide'] ) : [] );
        // Validation
        if ( empty( $data['title'] ) ) {
            wp_send_json_error( [
                'message' => __( 'Title is required', 'slidemind' ),
            ] );
        }
        $widget_mode = ( isset( $data['widget_mode'] ) ? $data['widget_mode'] : 'qa' );
        if ( $widget_mode !== 'capture' && empty( $data['model_id'] ) ) {
            wp_send_json_error( [
                'message' => __( 'Model is required', 'slidemind' ),
            ] );
        }
        // Scope restrictions on memory variables and auto-save (2A.8)
        $mem_vars = $data['memory_variables'] ?? '';
        if ( is_string( $mem_vars ) ) {
            $mem_vars = json_decode( wp_unslash( $mem_vars ), true );
        }
        if ( !is_array( $mem_vars ) ) {
            $mem_vars = [];
        }
        // Sanitize each memory variable entry after decode
        $mem_vars = array_map( function ( $mvar ) {
            return [
                'name'  => ( isset( $mvar['name'] ) ? sanitize_text_field( $mvar['name'] ) : '' ),
                'scope' => ( isset( $mvar['scope'] ) ? sanitize_key( $mvar['scope'] ) : 'global' ),
            ];
        }, $mem_vars );
        // Free: global scope only. The premium block both widens the allowed
        // scopes and supplies plan-aware error messaging; in the free build the
        // whole block is stripped, leaving the neutral default rejection.
        $allowed_scopes = ['global'];
        $scope_error_payload = [
            'message' => __( 'This memory scope is not available.', 'slidemind' ),
        ];
        foreach ( $mem_vars as $mvar ) {
            if ( !in_array( $mvar['scope'], $allowed_scopes, true ) ) {
                wp_send_json_error( $scope_error_payload );
            }
        }
        $save_scope = ( isset( $data['save_interaction_scope'] ) ? sanitize_key( $data['save_interaction_scope'] ) : 'global' );
        if ( !in_array( $save_scope, $allowed_scopes, true ) ) {
            wp_send_json_error( $scope_error_payload );
        }
        // Persist guest access toggle (per-slide override: inherit | allow | deny).
        $allow_guests = ( isset( $data['allow_guests'] ) ? sanitize_key( $data['allow_guests'] ) : 'inherit' );
        if ( !in_array( $allow_guests, ['inherit', 'allow', 'deny'], true ) ) {
            $allow_guests = 'inherit';
        }
        // Strip api_key if feature not available (Agency only)
        $allow_custom_key = false;
        if ( !$allow_custom_key ) {
            unset($data['api_key']);
        }
        // Create or update
        if ( !empty( $data['id'] ) ) {
            $result = $this->config_repository->update_slide( (int) $data['id'], $data );
            $slide_id = (int) $data['id'];
        } else {
            $result = $this->config_repository->create_slide( $data );
            $slide_id = $result;
        }
        if ( is_wp_error( $result ) ) {
            wp_send_json_error( [
                'message' => $result->get_error_message(),
            ] );
        }
        // Persist guest access meta (post-save so we have a real $slide_id).
        if ( $allow_guests === 'inherit' ) {
            delete_post_meta( $slide_id, '_sm_allow_guests' );
        } else {
            update_post_meta( $slide_id, '_sm_allow_guests', $allow_guests );
        }
        // Sync global memory variables to standalone definitions (Memory Hub)
        $this->sync_global_vars_to_definitions( $data );
        $slide = $this->config_repository->get_slide( $slide_id );
        wp_send_json_success( [
            'message'    => __( 'Slide saved', 'slidemind' ),
            'slide_id'   => $slide_id,
            'config_uid' => $slide['config_uid'],
            'slide'      => $slide,
        ] );
    }

    /**
     * Syncs global-scoped memory variables from a slide to the standalone definitions pool.
     *
     * @param array $data Slide data from the save request
     */
    private function sync_global_vars_to_definitions( array $data ) : void {
        $mem_vars = $data['memory_variables'] ?? '';
        if ( is_string( $mem_vars ) ) {
            $mem_vars = json_decode( wp_unslash( $mem_vars ), true );
        }
        if ( !is_array( $mem_vars ) || empty( $mem_vars ) ) {
            return;
        }
        // Collect global-scoped variable names (sanitize after decode)
        $global_names = [];
        foreach ( $mem_vars as $var ) {
            $var_name = ( isset( $var['name'] ) ? sanitize_text_field( $var['name'] ) : '' );
            $var_scope = ( isset( $var['scope'] ) ? sanitize_key( $var['scope'] ) : '' );
            if ( !empty( $var_name ) && $var_scope === 'global' ) {
                if ( preg_match( '/^[a-z][a-z0-9_]*$/', $var_name ) ) {
                    $global_names[] = $var_name;
                }
            }
        }
        if ( empty( $global_names ) ) {
            return;
        }
        $definitions = get_option( 'slidemind_memory_definitions', [] );
        $existing_names = array_column( $definitions, 'name' );
        $added = false;
        foreach ( $global_names as $name ) {
            if ( !in_array( $name, $existing_names, true ) ) {
                $definitions[] = [
                    'id'          => wp_generate_uuid4(),
                    'name'        => $name,
                    'description' => '',
                    'created_at'  => gmdate( 'Y-m-d' ),
                ];
                $existing_names[] = $name;
                $added = true;
            }
        }
        if ( $added ) {
            update_option( 'slidemind_memory_definitions', $definitions );
        }
    }

    /**
     * AJAX: Delete a slide
     */
    public function ajax_delete_slide() {
        check_ajax_referer( 'slidemind_admin_nonce', 'nonce' );
        if ( !current_user_can( 'manage_slidemind_configs' ) ) {
            wp_send_json_error( [
                'message' => __( 'Unauthorized access', 'slidemind' ),
            ] );
        }
        $slide_id = ( isset( $_POST['slide_id'] ) ? (int) $_POST['slide_id'] : 0 );
        if ( !$slide_id ) {
            wp_send_json_error( [
                'message' => __( 'Invalid slide ID', 'slidemind' ),
            ] );
        }
        $result = $this->config_repository->delete( $slide_id );
        if ( !$result ) {
            wp_send_json_error( [
                'message' => __( 'Unable to delete slide', 'slidemind' ),
            ] );
        }
        wp_send_json_success( [
            'message' => __( 'Slide deleted', 'slidemind' ),
        ] );
    }

    /**
     * AJAX: Returns authoring tool variable names for sibling slides in a course (for duplicate detection)
     */
    public function ajax_get_sibling_editor_vars() {
        check_ajax_referer( 'slidemind_admin_nonce', 'nonce' );
        if ( !current_user_can( 'manage_slidemind_configs' ) ) {
            wp_send_json_error( [
                'message' => __( 'Unauthorized access', 'slidemind' ),
            ] );
        }
        $course_id = ( isset( $_POST['course_id'] ) ? absint( wp_unslash( $_POST['course_id'] ) ) : 0 );
        $exclude_id = ( isset( $_POST['exclude_slide_id'] ) ? absint( wp_unslash( $_POST['exclude_slide_id'] ) ) : 0 );
        if ( !$course_id ) {
            wp_send_json_success( [
                'siblings' => [],
            ] );
        }
        $all_slides = $this->config_repository->get_all_slides();
        $siblings = [];
        foreach ( $all_slides as $s ) {
            if ( (int) $s['parent_course_id'] !== $course_id ) {
                continue;
            }
            if ( $exclude_id && (int) $s['id'] === $exclude_id ) {
                continue;
            }
            $siblings[] = [
                'slide_id'               => $s['id'],
                'title'                  => $s['title'],
                'editor_input_var'       => $s['editor_input_var'],
                'editor_output_var'      => $s['editor_output_var'],
                'save_input_key'         => $s['save_input_key'] ?? '',
                'save_response_key'      => $s['save_response_key'] ?? '',
                'save_interaction_scope' => $s['save_interaction_scope'] ?? 'global',
            ];
        }
        wp_send_json_success( [
            'siblings' => $siblings,
        ] );
    }

    // =========================================================================
    // INHERITED CONTENT
    // =========================================================================
    /**
     * AJAX: Retrieve inherited content for a module
     */
    public function ajax_get_inherited_content() {
        check_ajax_referer( 'slidemind_admin_nonce', 'nonce' );
        if ( !current_user_can( 'manage_slidemind_configs' ) ) {
            wp_send_json_error( [
                'message' => __( 'Unauthorized access', 'slidemind' ),
            ] );
        }
        $module_id = ( isset( $_POST['module_id'] ) ? (int) $_POST['module_id'] : 0 );
        if ( !$module_id ) {
            wp_send_json_error( [
                'message' => __( 'Invalid module ID', 'slidemind' ),
            ] );
        }
        $inherited = $this->config_repository->get_inherited_content( $module_id, true );
        wp_send_json_success( [
            'inherited' => $inherited,
        ] );
    }

    // =========================================================================
    // AUTOCOMPLETE VARIABLES
    // =========================================================================
    /**
     * AJAX: Search available variables for prompt autocomplete
     *
     * Collects variables from 5 sources:
     * - System (hardcoded course/module fields)
     * - Memory (learner variables stored in DB)
     * - Profile (user fields allowed in settings)
     * - Template (variables declared in templates)
     */
    public function ajax_search_variables() {
        check_ajax_referer( 'slidemind_admin_nonce', 'nonce' );
        if ( !current_user_can( 'manage_slidemind_configs' ) ) {
            wp_send_json_error( [
                'message' => __( 'Unauthorized access', 'slidemind' ),
            ] );
        }
        $variables = [];
        // 1. System variables (hardcoded) — requires indicates the needed hierarchy level
        $system_vars = [
            [
                'key'      => '{{course_theme}}',
                'value'    => 'course_theme',
                'type'     => 'system',
                'label'    => __( 'Course theme', 'slidemind' ),
                'requires' => 'course',
            ],
            [
                'key'      => '{{course_objectives}}',
                'value'    => 'course_objectives',
                'type'     => 'system',
                'label'    => __( 'Course objectives', 'slidemind' ),
                'requires' => 'course',
            ],
            [
                'key'      => '{{course_content}}',
                'value'    => 'course_content',
                'type'     => 'system',
                'label'    => __( 'Course content', 'slidemind' ),
                'requires' => 'course',
            ],
            [
                'key'      => '{{course_response_example}}',
                'value'    => 'course_response_example',
                'type'     => 'system',
                'label'    => __( 'Course response example', 'slidemind' ),
                'requires' => 'course',
            ],
            [
                'key'      => '{{module_theme}}',
                'value'    => 'module_theme',
                'type'     => 'system',
                'label'    => __( 'Module theme', 'slidemind' ),
                'requires' => 'module',
            ],
            [
                'key'      => '{{module_objectives}}',
                'value'    => 'module_objectives',
                'type'     => 'system',
                'label'    => __( 'Module objectives', 'slidemind' ),
                'requires' => 'module',
            ],
            [
                'key'      => '{{module_content}}',
                'value'    => 'module_content',
                'type'     => 'system',
                'label'    => __( 'Module content', 'slidemind' ),
                'requires' => 'module',
            ],
            [
                'key'      => '{{module_response_example}}',
                'value'    => 'module_response_example',
                'type'     => 'system',
                'label'    => __( 'Module response example', 'slidemind' ),
                'requires' => 'module',
            ]
        ];
        $variables = array_merge( $variables, $system_vars );
        // 2. Memory variables
        // 2a. From existing learner data (with scope)
        global $wpdb;
        $mem_table = $wpdb->prefix . 'slidemind_learner_memory';
        // phpcs:disable WordPress.DB.DirectDatabaseQuery
        $memory_rows = $wpdb->get_results( $wpdb->prepare( "SELECT variable_name, MIN(CASE scope\n                    WHEN 'global' THEN 0 WHEN 'course' THEN 1 WHEN 'formation' THEN 1 WHEN 'module' THEN 2 ELSE 0\n                END) AS min_scope FROM %i GROUP BY variable_name ORDER BY variable_name ASC LIMIT 50", $mem_table ) );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery
        $scope_requires = [
            0 => '',
            1 => 'course',
            2 => 'module',
        ];
        foreach ( $memory_rows as $row ) {
            $req = $scope_requires[(int) $row->min_scope] ?? '';
            $var = [
                'key'   => '{{memory.' . $row->variable_name . '}}',
                'value' => 'memory.' . $row->variable_name,
                'type'  => 'memory',
                'label' => $row->variable_name,
            ];
            if ( $req ) {
                $var['requires'] = $req;
            }
            $variables[] = $var;
        }
        // 2b. From central definitions (Memory Hub)
        $central_defs = get_option( 'slidemind_memory_definitions', [] );
        foreach ( $central_defs as $def ) {
            if ( !empty( $def['name'] ) ) {
                $variables[] = [
                    'key'   => '{{memory.' . $def['name'] . '}}',
                    'value' => 'memory.' . $def['name'],
                    'type'  => 'memory',
                    'label' => ( !empty( $def['description'] ) ? $def['description'] : $def['name'] ),
                ];
            }
        }
        // 2c. From memory variables defined on slides (with scope)
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery
        $slide_meta_rows = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT pm.meta_value FROM {$wpdb->postmeta} pm\n                 INNER JOIN {$wpdb->postmeta} pt ON pm.post_id = pt.post_id AND pt.meta_key = '_slidemind_config_type' AND pt.meta_value = 'slide'\n                 WHERE pm.meta_key = %s AND pm.meta_value != '' AND pm.meta_value != '[]'", '_slidemind_memory_variables' ) );
        foreach ( $slide_meta_rows as $json_str ) {
            $mem_vars = json_decode( $json_str, true );
            if ( !is_array( $mem_vars ) ) {
                continue;
            }
            foreach ( $mem_vars as $mvar ) {
                $var_name = ( isset( $mvar['name'] ) ? sanitize_text_field( $mvar['name'] ) : '' );
                if ( !empty( $var_name ) ) {
                    $var = [
                        'key'   => '{{memory.' . $var_name . '}}',
                        'value' => 'memory.' . $var_name,
                        'type'  => 'memory',
                        'label' => $var_name,
                    ];
                    $scope = ( isset( $mvar['scope'] ) ? sanitize_key( $mvar['scope'] ) : 'global' );
                    if ( $scope !== 'global' ) {
                        $var['requires'] = $scope;
                    }
                    $variables[] = $var;
                }
            }
        }
        // 3. Profile variables (fields allowed in settings)
        $settings = get_option( 'slidemind_settings', [] );
        $allowed_fields = $settings['allowed_profile_fields'] ?? [];
        $aliases = $settings['profile_field_aliases'] ?? [];
        $all_profile_fields = SettingsPage::get_available_profile_fields();
        foreach ( $all_profile_fields as $field ) {
            if ( in_array( $field['key'], $allowed_fields, true ) ) {
                $var_name = ( isset( $aliases[$field['key']] ) && $aliases[$field['key']] !== '' ? $aliases[$field['key']] : $field['key'] );
                $variables[] = [
                    'key'   => '{{profile.' . $var_name . '}}',
                    'value' => 'profile.' . $var_name,
                    'type'  => 'profile',
                    'label' => $field['label'],
                ];
            }
        }
        // 4. Template variables — temporarily disabled (scope to be reviewed)
        // if ($this->template_manager) { ... }
        // Deduplicate by key — keep the broadest scope (global > course > module)
        $unique = [];
        $seen_keys = [];
        $requires_priority = [
            ''       => 0,
            'course' => 1,
            'module' => 2,
        ];
        foreach ( $variables as $var ) {
            $key = $var['key'];
            $req = $var['requires'] ?? '';
            if ( !isset( $seen_keys[$key] ) ) {
                $seen_keys[$key] = count( $unique );
                $unique[] = $var;
            } else {
                // If the new occurrence has a broader scope, it replaces the existing one
                $idx = $seen_keys[$key];
                $existing_req = $unique[$idx]['requires'] ?? '';
                if ( ($requires_priority[$req] ?? 0) < ($requires_priority[$existing_req] ?? 0) ) {
                    $unique[$idx] = $var;
                }
            }
        }
        wp_send_json_success( [
            'variables' => $unique,
        ] );
    }

    // =========================================================================
    // EXPORT / IMPORT
    // =========================================================================
    /**
     * AJAX: Export a slide (downloadable JSON)
     */
    public function ajax_export_slide() {
        check_ajax_referer( 'slidemind_admin_nonce', 'nonce' );
        if ( !current_user_can( 'manage_slidemind_configs' ) ) {
            wp_send_json_error( [
                'message' => __( 'Unauthorized access', 'slidemind' ),
            ] );
        }
        if ( !$this->config_exporter ) {
            wp_send_json_error( [
                'message' => __( 'Export not available', 'slidemind' ),
            ] );
        }
        $slide_id = ( isset( $_POST['slide_id'] ) ? (int) $_POST['slide_id'] : 0 );
        if ( !$slide_id ) {
            wp_send_json_error( [
                'message' => __( 'Invalid slide ID', 'slidemind' ),
            ] );
        }
        $slide = $this->config_repository->get_slide( $slide_id );
        if ( !$slide ) {
            wp_send_json_error( [
                'message' => __( 'Slide not found', 'slidemind' ),
            ] );
        }
        $json = $this->config_exporter->export_slide( $slide_id );
        if ( is_wp_error( $json ) ) {
            wp_send_json_error( [
                'message' => $json->get_error_message(),
            ] );
        }
        $slug = ( sanitize_title( $slide['title'] ) ?: 'slide' );
        $filename = 'slide-' . $slug . '-' . gmdate( 'Y-m-d' ) . '.json';
        wp_send_json_success( [
            'json'     => $json,
            'filename' => $filename,
        ] );
    }

    /**
     * AJAX: Import a configuration (course, module or slide)
     */
    public function ajax_import_config() {
        check_ajax_referer( 'slidemind_admin_nonce', 'nonce' );
        if ( !current_user_can( 'manage_slidemind_configs' ) ) {
            wp_send_json_error( [
                'message' => __( 'Unauthorized access', 'slidemind' ),
            ] );
        }
        if ( !$this->config_exporter ) {
            wp_send_json_error( [
                'message' => __( 'Import not available', 'slidemind' ),
            ] );
        }
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON data, sanitized after decode
        $json = ( isset( $_POST['json'] ) ? wp_unslash( $_POST['json'] ) : '' );
        if ( empty( $json ) ) {
            wp_send_json_error( [
                'message' => __( 'JSON content is required', 'slidemind' ),
            ] );
        }
        // Paid-plan gates + scope normalization for imports.
        $scope_warnings = [];
        $import_data = json_decode( $json, true );
        if ( !is_array( $import_data ) ) {
            wp_send_json_error( [
                'message' => __( 'Invalid JSON data.', 'slidemind' ),
            ] );
        }
        // json_decode does not sanitize — clean every scalar before the data
        // is imported, and keep the raw JSON string in sync for the importer.
        $import_data = \SlideMind\Core\ImportSanitizer::sanitize( $import_data );
        $json = wp_json_encode( $import_data );
        $type = ( isset( $import_data['export_type'] ) ? sanitize_key( $import_data['export_type'] ) : '' );
        if ( !empty( $type ) ) {
            if ( in_array( $type, [
                'course',
                'courses',
                'module',
                'modules',
                'template',
                'templates'
            ], true ) ) {
                wp_send_json_error( [
                    'message' => __( 'This import type is not supported in this version. Only individual slides can be imported.', 'slidemind' ),
                ] );
            }
            // Collect the slide payloads exposed at this level so we can
            // normalize their memory-variable scopes (free-tier safety net).
            $all_import_slides = [];
            switch ( $type ) {
                case 'course':
                    foreach ( $import_data['modules'] ?? [] as $m ) {
                        foreach ( $m['slides'] ?? [] as $slide ) {
                            $all_import_slides[] = $slide;
                        }
                    }
                    break;
                case 'module':
                case 'slides':
                    foreach ( $import_data['slides'] ?? [] as $slide ) {
                        $all_import_slides[] = $slide;
                    }
                    break;
                case 'slide':
                    $all_import_slides[] = $import_data;
                    break;
            }
            // Filter non-global scope variables from imported slides
            $course_scope_allowed = false;
            if ( !empty( $all_import_slides ) && !$course_scope_allowed ) {
                $import_modified = false;
                switch ( $type ) {
                    case 'course':
                        foreach ( $import_data['modules'] ?? [] as &$imp_m ) {
                            foreach ( $imp_m['slides'] ?? [] as &$imp_s ) {
                                if ( $this->filter_import_slide_scopes( $imp_s, $scope_warnings ) ) {
                                    $import_modified = true;
                                }
                            }
                            unset($imp_s);
                        }
                        unset($imp_m);
                        break;
                    case 'module':
                    case 'slides':
                        foreach ( $import_data['slides'] ?? [] as &$imp_s ) {
                            if ( $this->filter_import_slide_scopes( $imp_s, $scope_warnings ) ) {
                                $import_modified = true;
                            }
                        }
                        unset($imp_s);
                        break;
                    case 'slide':
                        if ( $this->filter_import_slide_scopes( $import_data, $scope_warnings ) ) {
                            $import_modified = true;
                        }
                        break;
                }
                if ( $import_modified ) {
                    $json = wp_json_encode( $import_data );
                }
            }
        }
        $options = [];
        if ( !empty( $_POST['course_id'] ) ) {
            $options['course_id'] = absint( wp_unslash( $_POST['course_id'] ) );
        } elseif ( !empty( $_POST['formation_id'] ) ) {
            // Legacy compat
            $options['course_id'] = absint( wp_unslash( $_POST['formation_id'] ) );
        }
        if ( !empty( $_POST['module_id'] ) ) {
            $options['module_id'] = absint( wp_unslash( $_POST['module_id'] ) );
        }
        $result = $this->config_exporter->import( $json, $options );
        if ( is_wp_error( $result ) ) {
            wp_send_json_error( [
                'message' => $result->get_error_message(),
            ] );
        }
        $response = [
            'message' => __( 'Import successful', 'slidemind' ),
            'created' => $result['created'],
        ];
        if ( !empty( $scope_warnings ) ) {
            $response['warnings'] = sprintf( 
                /* translators: %s: list of variable names */
                __( 'Some non-global scope variables were reset to global: %s', 'slidemind' ),
                implode( ', ', array_unique( $scope_warnings ) )
             );
        }
        wp_send_json_success( $response );
    }

    /**
     * Filters non-global scope variables from a single imported slide.
     *
     * Resets memory_variables with non-global scope to global and
     * normalizes save_interaction_scope.
     *
     * @param array    $slide    Slide data (passed by reference).
     * @param string[] $warnings Collects names of filtered variables (passed by reference).
     * @return bool True if the slide was modified.
     */
    private function filter_import_slide_scopes( array &$slide, array &$warnings ) : bool {
        $modified = false;
        $mem_vars = $slide['memory_variables'] ?? [];
        if ( is_string( $mem_vars ) ) {
            $mem_vars = ( json_decode( $mem_vars, true ) ?: [] );
        }
        if ( is_array( $mem_vars ) ) {
            $filtered = [];
            foreach ( $mem_vars as $mvar ) {
                $mvar_scope = ( isset( $mvar['scope'] ) ? sanitize_key( $mvar['scope'] ) : 'global' );
                $mvar_name = ( isset( $mvar['name'] ) ? sanitize_text_field( $mvar['name'] ) : '?' );
                if ( $mvar_scope !== 'global' ) {
                    $warnings[] = $mvar_name;
                    $modified = true;
                } else {
                    $filtered[] = $mvar;
                }
            }
            if ( $modified ) {
                $slide['memory_variables'] = $filtered;
            }
        }
        if ( isset( $slide['save_interaction_scope'] ) && $slide['save_interaction_scope'] !== 'global' ) {
            $warnings[] = 'save_interaction_scope';
            $slide['save_interaction_scope'] = 'global';
            $modified = true;
        }
        return $modified;
    }

    // =========================================================================
    // AI PROMPT GENERATION
    // =========================================================================
    /**
     * AJAX: Generate a system prompt via LLM
     */
    public function ajax_generate_prompt() {
        check_ajax_referer( 'slidemind_admin_nonce', 'nonce' );
        if ( !current_user_can( 'manage_slidemind_configs' ) ) {
            wp_send_json_error( [
                'message' => __( 'Unauthorized access', 'slidemind' ),
            ] );
        }
        // Rate limiting: 10 requests per minute per user
        $user_id = get_current_user_id();
        $rate_key = 'slidemind_gen_prompt_' . $user_id;
        $rate_count = (int) get_transient( $rate_key );
        if ( $rate_count >= 10 ) {
            wp_send_json_error( [
                'message' => __( 'Too many requests. Please wait 60 seconds.', 'slidemind' ),
            ] );
        }
        set_transient( $rate_key, $rate_count + 1, 60 );
        // Validate parameters
        $type = ( isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '' );
        $subject = ( isset( $_POST['subject'] ) ? sanitize_text_field( wp_unslash( $_POST['subject'] ) ) : '' );
        $objective = ( isset( $_POST['objective'] ) ? sanitize_text_field( wp_unslash( $_POST['objective'] ) ) : '' );
        $tone = ( isset( $_POST['tone'] ) ? sanitize_text_field( wp_unslash( $_POST['tone'] ) ) : '' );
        $level = ( isset( $_POST['level'] ) ? sanitize_text_field( wp_unslash( $_POST['level'] ) ) : '' );
        $model = ( isset( $_POST['model'] ) ? sanitize_text_field( wp_unslash( $_POST['model'] ) ) : '' );
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON data, sanitized after decode
        $variables = ( isset( $_POST['variables'] ) ? (array) wp_unslash( $_POST['variables'] ) : [] );
        if ( count( $variables ) > 50 ) {
            wp_send_json_error( [
                'message' => __( 'Too many variables selected (max 50)', 'slidemind' ),
            ] );
        }
        $variables = array_map( 'sanitize_text_field', array_filter( $variables, 'is_string' ) );
        if ( empty( $type ) ) {
            wp_send_json_error( [
                'message' => __( 'Interaction type is required', 'slidemind' ),
            ] );
        }
        if ( empty( $subject ) ) {
            wp_send_json_error( [
                'message' => __( 'Subject is required', 'slidemind' ),
            ] );
        }
        // Retrieve the global API key
        $plugin = \SlideMind\Plugin::instance();
        $global_key = $plugin->get_api_key_manager()->get_global_api_key();
        if ( empty( $global_key ) ) {
            wp_send_json_error( [
                'message' => __( 'No global API key configured. Go to Settings to add one.', 'slidemind' ),
            ] );
        }
        // Retrieve default settings
        $settings = wp_parse_args( get_option( 'slidemind_settings', [] ), [
            'default_model'      => 'anthropic/claude-3.5-sonnet',
            'default_max_tokens' => 1024,
        ] );
        // Virtual config (same pattern as SandboxEndpoint)
        $virtual_config = [
            'id'            => 0,
            'api_key'       => $global_key,
            'model_id'      => ( !empty( $model ) ? $model : $settings['default_model'] ),
            'system_prompt' => '',
            'temperature'   => 0.7,
            'max_tokens'    => (int) $settings['default_max_tokens'],
        ];
        // Build the meta-prompt
        $meta_prompt = $this->build_meta_prompt(
            $type,
            $subject,
            $objective,
            $tone,
            $level,
            $variables
        );
        // Call the LLM
        $messages = [[
            'role'    => 'user',
            'content' => $meta_prompt,
        ]];
        $result = $this->llm_proxy->chat( $virtual_config, $messages );
        if ( !$result['success'] ) {
            wp_send_json_error( [
                'message' => $result['error'] ?? __( 'Error during generation', 'slidemind' ),
            ] );
        }
        // Clean the generated prompt
        $generated = $this->clean_generated_prompt( $result['data']['message']['content'] ?? '' );
        wp_send_json_success( [
            'prompt' => $generated,
            'usage'  => $result['data']['usage'] ?? null,
        ] );
    }

    /**
     * Build the meta-prompt for generation.
     *
     * Output language follows the admin user's WordPress locale so the generated
     * system prompt matches the language the learner-facing UI will be authored in.
     */
    private function build_meta_prompt(
        string $type,
        string $subject,
        string $objective,
        string $tone,
        string $level,
        array $variables
    ) : string {
        // Cleanup: remove line breaks and limit length
        $type = substr( str_replace( ["\n", "\r"], ' ', $type ), 0, 100 );
        $subject = substr( str_replace( ["\n", "\r"], ' ', $subject ), 0, 200 );
        $objective = substr( str_replace( ["\n", "\r"], ' ', $objective ), 0, 300 );
        $tone = substr( str_replace( ["\n", "\r"], ' ', $tone ), 0, 100 );
        $level = substr( str_replace( ["\n", "\r"], ' ', $level ), 0, 50 );
        $vars_text = '';
        if ( !empty( $variables ) ) {
            $vars_list = array_map( function ( $v ) {
                return '{{' . $v . '}}';
            }, $variables );
            $vars_text = implode( ', ', $vars_list );
        }
        $output_language = $this->resolve_prompt_language();
        $prompt = "You are an expert in prompt engineering for e-learning pedagogical applications.\n\n";
        $prompt .= "Your task: generate a system prompt for an AI that will interact with learners inside an e-learning module produced with an authoring tool (Articulate Storyline, Adobe Captivate, Lectora, DominKnow, iSpring, etc.).\n\n";
        $prompt .= "Criteria:\n";
        $prompt .= "- Interaction type: {$type}\n";
        $prompt .= "- Subject: {$subject}\n";
        if ( !empty( $objective ) ) {
            $prompt .= "- Pedagogical objective: {$objective}\n";
        }
        if ( !empty( $tone ) ) {
            $prompt .= "- Tone: {$tone}\n";
        }
        if ( !empty( $level ) ) {
            $prompt .= "- Learner level: {$level}\n";
        }
        if ( !empty( $vars_text ) ) {
            $prompt .= "\nVariables available for substitution in the prompt: {$vars_text}\n";
            $prompt .= "Insert these variables verbatim (with the double curly braces) inside the generated prompt to personalize the learner experience.\n";
        }
        $prompt .= "\nRules:\n";
        $prompt .= "- Write the generated system prompt in {$output_language}\n";
        $prompt .= "- Length between 150 and 400 words\n";
        $prompt .= "- Clearly define the AI's role\n";
        $prompt .= "- Describe the expected behavior with precision\n";
        $prompt .= "- Encourage active learning and reflection\n";
        $prompt .= "- No greeting or introduction in the AI's responses\n";
        $prompt .= "\nReturn ONLY the system prompt, with no introduction, explanation, commentary, or code fences.";
        return $prompt;
    }

    /**
     * Resolve the natural-language label used to instruct the LLM about the
     * generated prompt's language. Falls back to English for locales we don't
     * map explicitly — the LLM still understands the locale code itself.
     */
    private function resolve_prompt_language() : string {
        $locale = ( function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale() );
        $key = strtolower( substr( (string) $locale, 0, 2 ) );
        $map = [
            'en' => 'English',
            'fr' => 'French',
            'es' => 'Spanish',
            'de' => 'German',
            'it' => 'Italian',
            'pt' => 'Portuguese',
            'nl' => 'Dutch',
            'sv' => 'Swedish',
            'da' => 'Danish',
            'no' => 'Norwegian',
            'fi' => 'Finnish',
            'pl' => 'Polish',
            'cs' => 'Czech',
            'ru' => 'Russian',
            'ja' => 'Japanese',
            'zh' => 'Chinese',
            'ko' => 'Korean',
            'ar' => 'Arabic',
            'he' => 'Hebrew',
            'tr' => 'Turkish',
        ];
        return $map[$key] ?? sprintf( 'the language matching locale "%s"', $locale );
    }

    /**
     * Clean the generated prompt (remove code fences and surrounding quotes)
     */
    private function clean_generated_prompt( string $text ) : string {
        // Remove markdown code fences
        $text = preg_replace( '/^```[\\w]*\\n?/m', '', $text );
        $text = preg_replace( '/\\n?```$/m', '', $text );
        // Remove surrounding quotes. Extracted booleans avoid the Freemius
        // preprocessor collapsing the parentheses on the free-build output.
        $text = trim( $text );
        $wrapped_in_double_quotes = str_starts_with( $text, '"' ) && str_ends_with( $text, '"' );
        $wrapped_in_french_quotes = str_starts_with( $text, '«' ) && str_ends_with( $text, '»' );
        if ( $wrapped_in_double_quotes || $wrapped_in_french_quotes ) {
            $text = mb_substr( $text, 1, mb_strlen( $text ) - 2 );
        }
        return trim( $text );
    }

}
