<?php

/**
 * Configuration repository
 *
 * Handles CRUD operations for configurations with API key encryption
 *
 * @package SlideMind
 * @since 2.0.0
 */
namespace SlideMind\Core;

if ( !defined( 'ABSPATH' ) ) {
    exit;
}
class ConfigRepository {
    /**
     * API key manager instance
     * @var ApiKeyManager
     */
    private $api_key_manager;

    /**
     * Post type
     * @var string
     */
    private $post_type = 'slidemind_config';

    /**
     * Meta keys prefix
     * @var string
     */
    private $meta_prefix = '_slidemind_';

    /**
     * Constructor
     *
     * @param ApiKeyManager $api_key_manager
     */
    public function __construct( ApiKeyManager $api_key_manager ) {
        $this->api_key_manager = $api_key_manager;
    }

    /**
     * Retrieves a configuration by its Configuration ID
     *
     * @param string $config_uid The unique identifier for the e-learning authoring tool
     * @return array|null The configuration or null if not found
     */
    public function get_by_config_uid( $config_uid ) {
        $posts = get_posts( [
            'post_type'      => $this->post_type,
            'post_status'    => 'publish',
            'posts_per_page' => 1,
            'meta_query'     => [[
                'key'     => $this->meta_prefix . 'config_uid',
                'value'   => sanitize_text_field( $config_uid ),
                'compare' => '=',
            ]],
        ] );
        if ( empty( $posts ) ) {
            return null;
        }
        return $this->format_config( $posts[0] );
    }

    /**
     * Retrieves a configuration by its post ID
     *
     * @param int $post_id
     * @return array|null
     */
    public function get_by_id( $post_id ) {
        $post = get_post( $post_id );
        if ( !$post || $post->post_type !== $this->post_type ) {
            return null;
        }
        return $this->format_config( $post );
    }

    /**
     * Retrieves all configurations
     *
     * @param array $args Additional arguments for get_posts
     * @return array
     */
    public function get_all( $args = [] ) {
        $defaults = [
            'post_type'      => $this->post_type,
            'post_status'    => 'any',
            'posts_per_page' => -1,
            'orderby'        => 'title',
            'order'          => 'ASC',
        ];
        $posts = get_posts( array_merge( $defaults, $args ) );
        $configs = [];
        foreach ( $posts as $post ) {
            $configs[] = $this->format_config( $post );
        }
        return $configs;
    }

    /**
     * Creates a new configuration
     *
     * @param array $data The configuration data
     * @return int|WP_Error The created post ID or an error
     */
    public function create( $data ) {
        $post_data = [
            'post_type'   => $this->post_type,
            'post_status' => $this->sanitize_status( $data['status'] ?? 'draft' ),
            'post_title'  => sanitize_text_field( $data['title'] ),
        ];
        $post_id = wp_insert_post( $post_data, true );
        if ( is_wp_error( $post_id ) ) {
            return $post_id;
        }
        // Generate the Configuration ID
        $config_uid = $this->generate_config_uid( $data, $post_id );
        // Save meta
        $this->save_meta( $post_id, $data, $config_uid );
        return $post_id;
    }

    /**
     * Updates a configuration
     *
     * @param int $post_id
     * @param array $data
     * @return bool|WP_Error
     */
    public function update( $post_id, $data ) {
        $post = get_post( $post_id );
        if ( !$post || $post->post_type !== $this->post_type ) {
            return new \WP_Error('invalid_post', __( 'Configuration not found', 'slidemind' ));
        }
        // Update the title if provided
        if ( isset( $data['title'] ) ) {
            wp_update_post( [
                'ID'         => $post_id,
                'post_title' => sanitize_text_field( $data['title'] ),
            ] );
        }
        // Update the status if provided (whitelisted)
        if ( isset( $data['status'] ) ) {
            wp_update_post( [
                'ID'          => $post_id,
                'post_status' => $this->sanitize_status( $data['status'] ),
            ] );
        }
        // Regenerate the Configuration ID if key data changes
        $config_uid = get_post_meta( $post_id, $this->meta_prefix . 'config_uid', true );
        if ( isset( $data['course_name'] ) || isset( $data['usage_scope'] ) ) {
            $current_data = $this->get_by_id( $post_id );
            $merged_data = array_merge( $current_data, $data );
            $config_uid = $this->generate_config_uid( $merged_data, $post_id );
        }
        // Save meta
        $this->save_meta( $post_id, $data, $config_uid );
        return true;
    }

    /**
     * Deletes a configuration
     *
     * @param int $post_id
     * @param bool $force_delete Permanent deletion or trash
     * @return bool
     */
    public function delete( $post_id, $force_delete = false ) {
        $post = get_post( $post_id );
        if ( !$post || $post->post_type !== $this->post_type ) {
            return false;
        }
        if ( $force_delete ) {
            return wp_delete_post( $post_id, true ) !== false;
        }
        return wp_trash_post( $post_id ) !== false;
    }

    /**
     * Formats a configuration for use
     *
     * @param WP_Post $post
     * @return array
     */
    private function format_config( $post ) {
        $encrypted_key = get_post_meta( $post->ID, $this->meta_prefix . 'api_key_encrypted', true );
        $provider = get_post_meta( $post->ID, $this->meta_prefix . 'provider', true );
        $resolved_provider = ( !empty( $provider ) ? $provider : get_option( 'slidemind_global_provider', 'openrouter' ) );
        // Use the specific key or the global key
        $api_key = '';
        if ( !empty( $encrypted_key ) ) {
            $api_key = $this->api_key_manager->decrypt( $encrypted_key );
        }
        // Fallback to the global key for the correct provider
        if ( empty( $api_key ) ) {
            $api_key = $this->api_key_manager->get_global_api_key( $resolved_provider );
        }
        // Resolve parent response examples (for slides)
        $module_id = get_post_meta( $post->ID, $this->meta_prefix . 'parent_module_id', true );
        $course_id = '';
        if ( $module_id ) {
            $course_id = get_post_meta( $module_id, $this->meta_prefix . 'parent_course_id', true );
        }
        return [
            'id'                      => $post->ID,
            'title'                   => $post->post_title,
            'status'                  => $post->post_status,
            'config_uid'              => get_post_meta( $post->ID, $this->meta_prefix . 'config_uid', true ),
            'provider'                => $provider,
            'resolved_provider'       => $resolved_provider,
            'course_name'             => get_post_meta( $post->ID, $this->meta_prefix . 'course_name', true ),
            'usage_scope'             => ( get_post_meta( $post->ID, $this->meta_prefix . 'usage_scope', true ) ?: 'course' ),
            'module_info'             => get_post_meta( $post->ID, $this->meta_prefix . 'module_info', true ),
            'slide_info'              => get_post_meta( $post->ID, $this->meta_prefix . 'slide_info', true ),
            'model_id'                => get_post_meta( $post->ID, $this->meta_prefix . 'model_id', true ),
            'system_prompt'           => get_post_meta( $post->ID, $this->meta_prefix . 'system_prompt', true ),
            'temperature'             => ( (float) get_post_meta( $post->ID, $this->meta_prefix . 'temperature', true ) ?: 0.7 ),
            'max_tokens'              => ( (int) get_post_meta( $post->ID, $this->meta_prefix . 'max_tokens', true ) ?: 1024 ),
            'variables'               => ( get_post_meta( $post->ID, $this->meta_prefix . 'variables', true ) ?: [] ),
            'widget_mode'             => ( get_post_meta( $post->ID, $this->meta_prefix . 'widget_mode', true ) ?: 'qa' ),
            'max_turns'               => (int) get_post_meta( $post->ID, $this->meta_prefix . 'max_turns', true ),
            'closing_message'         => ( get_post_meta( $post->ID, $this->meta_prefix . 'closing_message', true ) ?: '' ),
            'opening_message'         => ( get_post_meta( $post->ID, $this->meta_prefix . 'opening_message', true ) ?: '' ),
            'widget_color'            => ( get_post_meta( $post->ID, $this->meta_prefix . 'widget_color', true ) ?: '' ),
            'widget_ai_color'         => ( get_post_meta( $post->ID, $this->meta_prefix . 'widget_ai_color', true ) ?: '' ),
            'widget_theme'            => ( get_post_meta( $post->ID, $this->meta_prefix . 'widget_theme', true ) ?: '' ),
            'widget_show_header'      => ( get_post_meta( $post->ID, $this->meta_prefix . 'widget_show_header', true ) ?: '' ),
            'widget_ai_text_color'    => ( get_post_meta( $post->ID, $this->meta_prefix . 'widget_ai_text_color', true ) ?: '' ),
            'widget_border_radius'    => ( get_post_meta( $post->ID, $this->meta_prefix . 'widget_border_radius', true ) ?: '' ),
            'widget_title'            => ( get_post_meta( $post->ID, $this->meta_prefix . 'widget_title', true ) ?: '' ),
            'api_key'                 => $api_key,
            'has_custom_key'          => !empty( $encrypted_key ),
            'notes'                   => get_post_meta( $post->ID, $this->meta_prefix . 'notes', true ),
            'output_variables'        => ( json_decode( ( get_post_meta( $post->ID, $this->meta_prefix . 'output_variables', true ) ?: '[]' ), true ) ?: [] ),
            'response_example'        => get_post_meta( $post->ID, $this->meta_prefix . 'response_example', true ),
            'course_response_example' => ( $course_id ? get_post_meta( $course_id, $this->meta_prefix . 'course_response_example', true ) : '' ),
            'module_response_example' => ( $module_id ? get_post_meta( $module_id, $this->meta_prefix . 'module_response_example', true ) : '' ),
            'memory_variables'        => ( json_decode( ( get_post_meta( $post->ID, $this->meta_prefix . 'memory_variables', true ) ?: '[]' ), true ) ?: [] ),
            'save_user_input'         => ( get_post_meta( $post->ID, $this->meta_prefix . 'save_user_input', true ) ?: '0' ),
            'save_ai_response'        => ( get_post_meta( $post->ID, $this->meta_prefix . 'save_ai_response', true ) ?: '0' ),
            'save_input_key'          => ( get_post_meta( $post->ID, $this->meta_prefix . 'save_input_key', true ) ?: '' ),
            'save_response_key'       => ( get_post_meta( $post->ID, $this->meta_prefix . 'save_response_key', true ) ?: '' ),
            'save_interaction_scope'  => ( get_post_meta( $post->ID, $this->meta_prefix . 'save_interaction_scope', true ) ?: 'global' ),
            'created_at'              => $post->post_date,
            'updated_at'              => $post->post_modified,
        ];
    }

    /**
     * Saves the meta of a configuration
     *
     * @param int $post_id
     * @param array $data
     * @param string $config_uid
     */
    private function save_meta( $post_id, $data, $config_uid ) {
        // Configuration ID
        update_post_meta( $post_id, $this->meta_prefix . 'config_uid', $config_uid );
        // Basic info
        $text_fields = [
            'course_name',
            'usage_scope',
            'module_info',
            'slide_info',
            'model_id',
            'notes'
        ];
        foreach ( $text_fields as $field ) {
            if ( isset( $data[$field] ) ) {
                update_post_meta( $post_id, $this->meta_prefix . $field, sanitize_text_field( $data[$field] ) );
            }
        }
        // System prompt (allows more HTML)
        if ( isset( $data['system_prompt'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'system_prompt', wp_kses_post( $data['system_prompt'] ) );
        }
        // Numeric parameters
        if ( isset( $data['temperature'] ) ) {
            $temp = max( 0, min( 2, (float) $data['temperature'] ) );
            update_post_meta( $post_id, $this->meta_prefix . 'temperature', $temp );
        }
        if ( isset( $data['max_tokens'] ) ) {
            $tokens = max( 1, min( 128000, (int) $data['max_tokens'] ) );
            update_post_meta( $post_id, $this->meta_prefix . 'max_tokens', $tokens );
        }
        // Variables (array) — deep-sanitize each entry before persisting.
        if ( isset( $data['variables'] ) && is_array( $data['variables'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'variables', $this->sanitize_variables( $data['variables'] ) );
        }
        // API key (encrypted)
        if ( isset( $data['api_key'] ) ) {
            $key_value = $data['api_key'];
            if ( $key_value === '__CLEAR__' ) {
                delete_post_meta( $post_id, $this->meta_prefix . 'api_key_encrypted' );
            } elseif ( $key_value !== '' ) {
                $encrypted = $this->api_key_manager->encrypt( $key_value );
                update_post_meta( $post_id, $this->meta_prefix . 'api_key_encrypted', $encrypted );
            }
            // Empty = no change, keep the existing key
        }
    }

    /**
     * Whitelist a post status from untrusted input.
     *
     * @param mixed  $status   Candidate status.
     * @param string $fallback Default if not allowed.
     * @return string
     */
    private function sanitize_status( $status, string $fallback = 'draft' ) : string {
        $allowed = [
            'publish',
            'draft',
            'pending',
            'private'
        ];
        $status = ( is_string( $status ) ? sanitize_key( $status ) : '' );
        return ( in_array( $status, $allowed, true ) ? $status : $fallback );
    }

    /**
     * Recursively sanitize a variables definition array coming from $_POST.
     *
     * Keys are sanitized as text; scalar values as text fields; nested arrays
     * recurse. This keeps arbitrary HTML/scripts out of stored meta while
     * preserving the definition structure.
     *
     * @param array $variables
     * @return array
     */
    private function sanitize_variables( array $variables ) : array {
        $clean = [];
        foreach ( $variables as $key => $value ) {
            $safe_key = ( is_string( $key ) ? sanitize_text_field( $key ) : $key );
            if ( is_array( $value ) ) {
                $clean[$safe_key] = $this->sanitize_variables( $value );
            } else {
                $clean[$safe_key] = sanitize_text_field( (string) $value );
            }
        }
        return $clean;
    }

    /**
     * Generates a unique Configuration ID
     *
     * @param array $data
     * @param int $post_id
     * @return string
     */
    private function generate_config_uid( $data, $post_id ) {
        $slug_parts = [];
        // Course name
        if ( !empty( $data['course_name'] ) ) {
            $slug_parts[] = sanitize_title( $data['course_name'] );
        }
        // Module si applicable
        $scope = ( isset( $data['usage_scope'] ) ? $data['usage_scope'] : 'course' );
        if ( ($scope === 'module' || $scope === 'slide') && !empty( $data['module_info'] ) ) {
            $slug_parts[] = sanitize_title( $data['module_info'] );
        }
        // Slide si applicable
        if ( $scope === 'slide' && !empty( $data['slide_info'] ) ) {
            $slug_parts[] = sanitize_title( $data['slide_info'] );
        }
        $human_part = implode( '-', $slug_parts );
        // Retrieve or generate the token
        $token = get_post_meta( $post_id, $this->meta_prefix . 'id_token', true );
        if ( empty( $token ) ) {
            $token = wp_generate_password( 8, false );
            update_post_meta( $post_id, $this->meta_prefix . 'id_token', $token );
        }
        return $human_part . '-' . $token;
    }

    /**
     * Counts the total number of configurations
     *
     * @param string $status Optional, filter by status
     * @param string $config_type Optional, filter by type (course, module, slide)
     * @return int
     */
    public function count( $status = 'any', $config_type = null ) {
        $args = [
            'post_type'      => $this->post_type,
            'post_status'    => $status,
            'posts_per_page' => -1,
            'fields'         => 'ids',
        ];
        if ( $config_type ) {
            $args['meta_query'] = [[
                'key'     => $this->meta_prefix . 'config_type',
                'value'   => $config_type,
                'compare' => '=',
            ]];
        }
        return count( get_posts( $args ) );
    }

    // =========================================================================
    // METHODS FOR THE COURSE > MODULE > SLIDE HIERARCHY
    // =========================================================================
    /**
     * Retrieves all courses
     *
     * @param string $status Optional, filter by status
     * @return array
     */
    public function get_courses( $status = 'any' ) {
        $args = [
            'post_type'      => $this->post_type,
            'post_status'    => $status,
            'posts_per_page' => -1,
            'orderby'        => 'title',
            'order'          => 'ASC',
            'meta_query'     => [[
                'key'     => $this->meta_prefix . 'config_type',
                'value'   => 'course',
                'compare' => '=',
            ]],
        ];
        $posts = get_posts( $args );
        $courses = [];
        foreach ( $posts as $post ) {
            $courses[] = $this->format_course( $post );
        }
        return $courses;
    }

    /**
     * Retrieves a course by its ID
     *
     * @param int $post_id
     * @return array|null
     */
    public function get_course( $post_id ) {
        $post = get_post( $post_id );
        if ( !$post || $post->post_type !== $this->post_type ) {
            return null;
        }
        $type = get_post_meta( $post_id, $this->meta_prefix . 'config_type', true );
        if ( $type !== 'course' ) {
            return null;
        }
        return $this->format_course( $post );
    }

    /**
     * Creates a new course
     *
     * @param array $data
     * @return int|WP_Error
     */
    public function create_course( $data ) {
        return new \WP_Error('not_available', __( 'Course management is not available in this version.', 'slidemind' ));
    }

    /**
     * Updates a course
     *
     * @param int $post_id
     * @param array $data
     * @return bool|WP_Error
     */
    public function update_course( $post_id, $data ) {
        return new \WP_Error('not_available', __( 'Course management is not available in this version.', 'slidemind' ));
    }

    /**
     * Retrieves all modules for a course
     *
     * @param int $course_id
     * @param string $status
     * @return array
     */
    public function get_modules_by_course( $course_id, $status = 'any' ) {
        $args = [
            'post_type'      => $this->post_type,
            'post_status'    => $status,
            'posts_per_page' => -1,
            'orderby'        => 'title',
            'order'          => 'ASC',
            'meta_query'     => [
                'relation' => 'AND',
                [
                    'key'     => $this->meta_prefix . 'config_type',
                    'value'   => 'module',
                    'compare' => '=',
                ],
                [
                    'key'     => $this->meta_prefix . 'parent_course_id',
                    'value'   => $course_id,
                    'compare' => '=',
                ],
            ],
        ];
        $posts = get_posts( $args );
        $modules = [];
        foreach ( $posts as $post ) {
            $modules[] = $this->format_module( $post );
        }
        return $modules;
    }

    /**
     * Retrieves all modules
     *
     * @param string $status
     * @return array
     */
    public function get_all_modules( $status = 'any' ) {
        $args = [
            'post_type'      => $this->post_type,
            'post_status'    => $status,
            'posts_per_page' => -1,
            'orderby'        => 'title',
            'order'          => 'ASC',
            'meta_query'     => [[
                'key'     => $this->meta_prefix . 'config_type',
                'value'   => 'module',
                'compare' => '=',
            ]],
        ];
        $posts = get_posts( $args );
        $modules = [];
        foreach ( $posts as $post ) {
            $modules[] = $this->format_module( $post );
        }
        return $modules;
    }

    /**
     * Retrieves a module by its ID
     *
     * @param int $post_id
     * @return array|null
     */
    public function get_module( $post_id ) {
        $post = get_post( $post_id );
        if ( !$post || $post->post_type !== $this->post_type ) {
            return null;
        }
        $type = get_post_meta( $post_id, $this->meta_prefix . 'config_type', true );
        if ( $type !== 'module' ) {
            return null;
        }
        return $this->format_module( $post );
    }

    /**
     * Creates a new module
     *
     * @param array $data
     * @return int|WP_Error
     */
    public function create_module( $data ) {
        return new \WP_Error('not_available', __( 'Module management is not available in this version.', 'slidemind' ));
    }

    /**
     * Updates a module
     *
     * @param int $post_id
     * @param array $data
     * @return bool|WP_Error
     */
    public function update_module( $post_id, $data ) {
        return new \WP_Error('not_available', __( 'Module management is not available in this version.', 'slidemind' ));
    }

    /**
     * Retrieves all slides for a module
     *
     * @param int $module_id
     * @param string $status
     * @return array
     */
    public function get_slides_by_module( $module_id, $status = 'any' ) {
        $args = [
            'post_type'      => $this->post_type,
            'post_status'    => $status,
            'posts_per_page' => -1,
            'orderby'        => 'title',
            'order'          => 'ASC',
            'meta_query'     => [
                'relation' => 'AND',
                [
                    'key'     => $this->meta_prefix . 'config_type',
                    'value'   => 'slide',
                    'compare' => '=',
                ],
                [
                    'key'     => $this->meta_prefix . 'parent_module_id',
                    'value'   => $module_id,
                    'compare' => '=',
                ],
            ],
        ];
        $posts = get_posts( $args );
        $slides = [];
        foreach ( $posts as $post ) {
            $slides[] = $this->format_slide( $post );
        }
        return $slides;
    }

    /**
     * Retrieves all slides
     *
     * @param string $status
     * @return array
     */
    public function get_all_slides( $status = 'any' ) {
        $args = [
            'post_type'      => $this->post_type,
            'post_status'    => $status,
            'posts_per_page' => -1,
            'orderby'        => 'title',
            'order'          => 'ASC',
            'meta_query'     => [[
                'key'     => $this->meta_prefix . 'config_type',
                'value'   => 'slide',
                'compare' => '=',
            ]],
        ];
        $posts = get_posts( $args );
        $slides = [];
        foreach ( $posts as $post ) {
            $slides[] = $this->format_slide( $post );
        }
        return $slides;
    }

    /**
     * Retrieves a slide by its ID
     *
     * @param int $post_id
     * @return array|null
     */
    public function get_slide( $post_id ) {
        $post = get_post( $post_id );
        if ( !$post || $post->post_type !== $this->post_type ) {
            return null;
        }
        $type = get_post_meta( $post_id, $this->meta_prefix . 'config_type', true );
        if ( $type !== 'slide' ) {
            return null;
        }
        return $this->format_slide( $post );
    }

    /**
     * Creates a new slide
     *
     * @param array $data
     * @return int|WP_Error
     */
    public function create_slide( $data ) {
        $post_data = [
            'post_type'   => $this->post_type,
            'post_status' => ( isset( $data['status'] ) ? $this->sanitize_status( $data['status'], 'draft' ) : 'draft' ),
            'post_title'  => sanitize_text_field( $data['title'] ),
        ];
        $post_id = wp_insert_post( $post_data, true );
        if ( is_wp_error( $post_id ) ) {
            return $post_id;
        }
        // Mark as slide
        update_post_meta( $post_id, $this->meta_prefix . 'config_type', 'slide' );
        // Generate the Configuration ID
        $config_uid = $this->generate_slide_config_uid( $post_id, $data );
        // Save specific meta
        $this->save_slide_meta( $post_id, $data, $config_uid );
        return $post_id;
    }

    /**
     * Updates a slide
     *
     * @param int $post_id
     * @param array $data
     * @return bool|WP_Error
     */
    public function update_slide( $post_id, $data ) {
        $slide = $this->get_slide( $post_id );
        if ( !$slide ) {
            return new \WP_Error('invalid_slide', __( 'Slide not found', 'slidemind' ));
        }
        if ( isset( $data['title'] ) ) {
            wp_update_post( [
                'ID'         => $post_id,
                'post_title' => sanitize_text_field( $data['title'] ),
            ] );
        }
        if ( isset( $data['status'] ) ) {
            wp_update_post( [
                'ID'          => $post_id,
                'post_status' => $this->sanitize_status( $data['status'], 'draft' ),
            ] );
        }
        // Retrieve the existing Configuration ID
        $config_uid = get_post_meta( $post_id, $this->meta_prefix . 'config_uid', true );
        $this->save_slide_meta( $post_id, $data, $config_uid );
        return true;
    }

    /**
     * Retrieves inherited content for a slide
     *
     * @param int $slide_id OU int $module_id si $from_module = true
     * @param bool $from_module Si true, part du module au lieu de la slide
     * @return array
     */
    public function get_inherited_content( $slide_id, $from_module = false ) {
        $result = [
            'course' => null,
            'module' => null,
        ];
        if ( $from_module ) {
            $module_id = $slide_id;
        } else {
            $module_id = get_post_meta( $slide_id, $this->meta_prefix . 'parent_module_id', true );
        }
        if ( $module_id ) {
            // Retrieve the module
            $module = $this->get_module( $module_id );
            if ( $module ) {
                $result['module'] = [
                    'id'               => $module['id'],
                    'title'            => $module['title'],
                    'theme'            => $module['theme'],
                    'objectives'       => $module['objectives'],
                    'content'          => $module['content'],
                    'response_example' => $module['response_example'],
                    'has_custom_key'   => $module['has_custom_key'],
                ];
                // Retrieve the parent course via the module
                if ( !empty( $module['parent_course_id'] ) ) {
                    $course = $this->get_course( $module['parent_course_id'] );
                    if ( $course ) {
                        $result['course'] = [
                            'id'               => $course['id'],
                            'title'            => $course['title'],
                            'theme'            => $course['theme'],
                            'objectives'       => $course['objectives'],
                            'content'          => $course['content'],
                            'has_custom_key'   => $course['has_custom_key'],
                            'response_example' => $course['response_example'],
                        ];
                    }
                }
            }
        }
        // If no module, check direct course link on the slide
        if ( !$module_id && !$from_module ) {
            $direct_course_id = get_post_meta( $slide_id, $this->meta_prefix . 'parent_course_id', true );
            if ( $direct_course_id ) {
                $course = $this->get_course( (int) $direct_course_id );
                if ( $course ) {
                    $result['course'] = [
                        'id'               => $course['id'],
                        'title'            => $course['title'],
                        'theme'            => $course['theme'],
                        'objectives'       => $course['objectives'],
                        'content'          => $course['content'],
                        'has_custom_key'   => $course['has_custom_key'],
                        'response_example' => $course['response_example'],
                    ];
                }
            }
        }
        return $result;
    }

    /**
     * Counts modules for a course
     *
     * @param int $course_id
     * @return int
     */
    public function count_modules_by_course( $course_id ) {
        $args = [
            'post_type'      => $this->post_type,
            'post_status'    => 'any',
            'posts_per_page' => -1,
            'fields'         => 'ids',
            'meta_query'     => [
                'relation' => 'AND',
                [
                    'key'     => $this->meta_prefix . 'config_type',
                    'value'   => 'module',
                    'compare' => '=',
                ],
                [
                    'key'     => $this->meta_prefix . 'parent_course_id',
                    'value'   => $course_id,
                    'compare' => '=',
                ],
            ],
        ];
        return count( get_posts( $args ) );
    }

    /**
     * Counts slides for a module
     *
     * @param int $module_id
     * @return int
     */
    public function count_slides_by_module( $module_id ) {
        $args = [
            'post_type'      => $this->post_type,
            'post_status'    => 'any',
            'posts_per_page' => -1,
            'fields'         => 'ids',
            'meta_query'     => [
                'relation' => 'AND',
                [
                    'key'     => $this->meta_prefix . 'config_type',
                    'value'   => 'slide',
                    'compare' => '=',
                ],
                [
                    'key'     => $this->meta_prefix . 'parent_module_id',
                    'value'   => $module_id,
                    'compare' => '=',
                ],
            ],
        ];
        return count( get_posts( $args ) );
    }

    /**
     * Counts slides for a course (via its modules)
     *
     * @param int $course_id
     * @return int
     */
    public function count_slides_by_course( $course_id ) {
        // Retrieve only module IDs (no formatting)
        $module_ids = get_posts( [
            'post_type'      => $this->post_type,
            'post_status'    => 'any',
            'posts_per_page' => -1,
            'fields'         => 'ids',
            'meta_query'     => [
                'relation' => 'AND',
                [
                    'key'     => $this->meta_prefix . 'config_type',
                    'value'   => 'module',
                    'compare' => '=',
                ],
                [
                    'key'     => $this->meta_prefix . 'parent_course_id',
                    'value'   => $course_id,
                    'compare' => '=',
                ],
            ],
        ] );
        $count = 0;
        foreach ( $module_ids as $module_id ) {
            $count += $this->count_slides_by_module( $module_id );
        }
        return $count;
    }

    // =========================================================================
    // FORMATTING METHODS
    // =========================================================================
    /**
     * Formats a course
     *
     * @param WP_Post $post
     * @return array
     */
    private function format_course( $post ) {
        $encrypted_key = get_post_meta( $post->ID, $this->meta_prefix . 'api_key_encrypted', true );
        $provider = get_post_meta( $post->ID, $this->meta_prefix . 'provider', true );
        return [
            'id'               => $post->ID,
            'title'            => $post->post_title,
            'status'           => $post->post_status,
            'config_type'      => 'course',
            'provider'         => $provider,
            'theme'            => get_post_meta( $post->ID, $this->meta_prefix . 'course_theme', true ),
            'objectives'       => get_post_meta( $post->ID, $this->meta_prefix . 'course_objectives', true ),
            'content'          => get_post_meta( $post->ID, $this->meta_prefix . 'course_content', true ),
            'has_custom_key'   => !empty( $encrypted_key ),
            'response_example' => get_post_meta( $post->ID, $this->meta_prefix . 'course_response_example', true ),
            'modules_count'    => $this->count_modules_by_course( $post->ID ),
            'slides_count'     => $this->count_slides_by_course( $post->ID ),
            'created_at'       => $post->post_date,
            'updated_at'       => $post->post_modified,
        ];
    }

    /**
     * Formats a module
     *
     * @param WP_Post $post
     * @return array
     */
    private function format_module( $post ) {
        $course_id = get_post_meta( $post->ID, $this->meta_prefix . 'parent_course_id', true );
        $course_title = '';
        $encrypted_key = get_post_meta( $post->ID, $this->meta_prefix . 'api_key_encrypted', true );
        if ( $course_id ) {
            $course_post = get_post( $course_id );
            if ( $course_post ) {
                $course_title = $course_post->post_title;
            }
        }
        return [
            'id'                  => $post->ID,
            'title'               => $post->post_title,
            'status'              => $post->post_status,
            'config_type'         => 'module',
            'parent_course_id'    => (int) $course_id,
            'parent_course_title' => $course_title,
            'theme'               => get_post_meta( $post->ID, $this->meta_prefix . 'module_theme', true ),
            'objectives'          => get_post_meta( $post->ID, $this->meta_prefix . 'module_objectives', true ),
            'content'             => get_post_meta( $post->ID, $this->meta_prefix . 'module_content', true ),
            'response_example'    => get_post_meta( $post->ID, $this->meta_prefix . 'module_response_example', true ),
            'has_custom_key'      => !empty( $encrypted_key ),
            'slides_count'        => $this->count_slides_by_module( $post->ID ),
            'created_at'          => $post->post_date,
            'updated_at'          => $post->post_modified,
        ];
    }

    /**
     * Formats a slide
     *
     * @param WP_Post $post
     * @return array
     */
    private function format_slide( $post ) {
        $encrypted_key = get_post_meta( $post->ID, $this->meta_prefix . 'api_key_encrypted', true );
        $api_key = '';
        $module_id = get_post_meta( $post->ID, $this->meta_prefix . 'parent_module_id', true );
        $module_title = '';
        $course_id = '';
        $course_title = '';
        $course_provider = '';
        // Direct course link on slide (course-level slide without module)
        $direct_course_id = get_post_meta( $post->ID, $this->meta_prefix . 'parent_course_id', true );
        if ( $module_id ) {
            $module = $this->get_module( $module_id );
            if ( $module ) {
                $module_title = $module['title'];
                $course_id = $module['parent_course_id'];
                $course_title = $module['parent_course_title'];
            }
        }
        // If no course from module, use direct link
        if ( !$course_id && $direct_course_id ) {
            $course_id = (int) $direct_course_id;
            $course_post = get_post( $course_id );
            if ( $course_post ) {
                $course_title = $course_post->post_title;
            }
        }
        // Cascade provider : slide → course → global
        $slide_provider = get_post_meta( $post->ID, $this->meta_prefix . 'provider', true );
        if ( $course_id ) {
            $course_provider = get_post_meta( $course_id, $this->meta_prefix . 'provider', true );
        }
        $global_provider = get_option( 'slidemind_global_provider', 'openrouter' );
        $resolved_provider = ( !empty( $slide_provider ) ? $slide_provider : (( !empty( $course_provider ) ? $course_provider : $global_provider )) );
        if ( empty( $api_key ) ) {
            $api_key = $this->api_key_manager->get_global_api_key( $resolved_provider );
        }
        return [
            'id'                      => $post->ID,
            'title'                   => $post->post_title,
            'status'                  => $post->post_status,
            'config_type'             => 'slide',
            'config_uid'              => get_post_meta( $post->ID, $this->meta_prefix . 'config_uid', true ),
            'provider'                => $slide_provider,
            'resolved_provider'       => $resolved_provider,
            'parent_module_id'        => (int) $module_id,
            'parent_module_title'     => $module_title,
            'parent_course_id'        => (int) $course_id,
            'parent_course_title'     => $course_title,
            'slide_number'            => get_post_meta( $post->ID, $this->meta_prefix . 'slide_number', true ),
            'widget_mode'             => ( get_post_meta( $post->ID, $this->meta_prefix . 'widget_mode', true ) ?: 'qa' ),
            'max_turns'               => (int) get_post_meta( $post->ID, $this->meta_prefix . 'max_turns', true ),
            'closing_message'         => ( get_post_meta( $post->ID, $this->meta_prefix . 'closing_message', true ) ?: '' ),
            'opening_message'         => ( get_post_meta( $post->ID, $this->meta_prefix . 'opening_message', true ) ?: '' ),
            'graceful_close'          => ( get_post_meta( $post->ID, $this->meta_prefix . 'graceful_close', true ) ?: '0' ),
            'graceful_close_prompt'   => ( get_post_meta( $post->ID, $this->meta_prefix . 'graceful_close_prompt', true ) ?: '' ),
            'widget_color'            => ( get_post_meta( $post->ID, $this->meta_prefix . 'widget_color', true ) ?: '' ),
            'widget_ai_color'         => ( get_post_meta( $post->ID, $this->meta_prefix . 'widget_ai_color', true ) ?: '' ),
            'widget_theme'            => ( get_post_meta( $post->ID, $this->meta_prefix . 'widget_theme', true ) ?: '' ),
            'widget_show_header'      => ( get_post_meta( $post->ID, $this->meta_prefix . 'widget_show_header', true ) ?: '' ),
            'widget_ai_text_color'    => ( get_post_meta( $post->ID, $this->meta_prefix . 'widget_ai_text_color', true ) ?: '' ),
            'widget_border_radius'    => ( get_post_meta( $post->ID, $this->meta_prefix . 'widget_border_radius', true ) ?: '' ),
            'widget_title'            => ( get_post_meta( $post->ID, $this->meta_prefix . 'widget_title', true ) ?: '' ),
            'model_id'                => get_post_meta( $post->ID, $this->meta_prefix . 'model_id', true ),
            'system_prompt'           => get_post_meta( $post->ID, $this->meta_prefix . 'system_prompt', true ),
            'temperature'             => ( (float) get_post_meta( $post->ID, $this->meta_prefix . 'temperature', true ) ?: 0.7 ),
            'max_tokens'              => ( (int) get_post_meta( $post->ID, $this->meta_prefix . 'max_tokens', true ) ?: 1024 ),
            'variables'               => ( get_post_meta( $post->ID, $this->meta_prefix . 'variables', true ) ?: [] ),
            'override_course'         => ( get_post_meta( $post->ID, $this->meta_prefix . 'override_course', true ) ?: [] ),
            'override_module'         => ( get_post_meta( $post->ID, $this->meta_prefix . 'override_module', true ) ?: [] ),
            'api_key'                 => $api_key,
            'has_custom_key'          => !empty( $encrypted_key ),
            'notes'                   => get_post_meta( $post->ID, $this->meta_prefix . 'notes', true ),
            'output_variables'        => ( json_decode( ( get_post_meta( $post->ID, $this->meta_prefix . 'output_variables', true ) ?: '[]' ), true ) ?: [] ),
            'response_example'        => get_post_meta( $post->ID, $this->meta_prefix . 'response_example', true ),
            'course_response_example' => ( $course_id ? get_post_meta( $course_id, $this->meta_prefix . 'course_response_example', true ) : '' ),
            'module_response_example' => ( $module_id ? get_post_meta( $module_id, $this->meta_prefix . 'module_response_example', true ) : '' ),
            'memory_variables'        => ( json_decode( ( get_post_meta( $post->ID, $this->meta_prefix . 'memory_variables', true ) ?: '[]' ), true ) ?: [] ),
            'save_user_input'         => ( get_post_meta( $post->ID, $this->meta_prefix . 'save_user_input', true ) ?: '0' ),
            'save_ai_response'        => ( get_post_meta( $post->ID, $this->meta_prefix . 'save_ai_response', true ) ?: '0' ),
            'save_input_key'          => ( get_post_meta( $post->ID, $this->meta_prefix . 'save_input_key', true ) ?: '' ),
            'save_response_key'       => ( get_post_meta( $post->ID, $this->meta_prefix . 'save_response_key', true ) ?: '' ),
            'save_interaction_scope'  => ( get_post_meta( $post->ID, $this->meta_prefix . 'save_interaction_scope', true ) ?: 'global' ),
            'editor_input_var'        => ( get_post_meta( $post->ID, $this->meta_prefix . 'editor_input_var', true ) ?: 'userMessage' ),
            'editor_output_var'       => ( get_post_meta( $post->ID, $this->meta_prefix . 'editor_output_var', true ) ?: 'aiResponse' ),
            'created_at'              => $post->post_date,
            'updated_at'              => $post->post_modified,
        ];
    }

    // =========================================================================
    // META SAVE METHODS
    // =========================================================================
    /**
     * Saves the meta of a course
     *
     * @param int $post_id
     * @param array $data
     */
    private function save_course_meta( $post_id, $data ) {
        // LLM provider
        if ( isset( $data['provider'] ) ) {
            $valid_providers = \SlideMind\Core\LLMProxy::get_provider_slugs();
            $provider = sanitize_text_field( $data['provider'] );
            if ( empty( $provider ) || in_array( $provider, $valid_providers, true ) ) {
                update_post_meta( $post_id, $this->meta_prefix . 'provider', $provider );
            }
        }
        if ( isset( $data['theme'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'course_theme', sanitize_text_field( $data['theme'] ) );
        }
        if ( isset( $data['objectives'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'course_objectives', wp_kses_post( $data['objectives'] ) );
        }
        if ( isset( $data['content'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'course_content', wp_kses_post( $data['content'] ) );
        }
        if ( isset( $data['response_example'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'course_response_example', wp_kses_post( $data['response_example'] ) );
        }
        // API key (encrypted)
        if ( isset( $data['api_key'] ) ) {
            $key_value = $data['api_key'];
            if ( $key_value === '__CLEAR__' ) {
                // Remove the dedicated key (will use the global one)
                delete_post_meta( $post_id, $this->meta_prefix . 'api_key_encrypted' );
            } elseif ( $key_value !== '' ) {
                $encrypted = $this->api_key_manager->encrypt( $key_value );
                update_post_meta( $post_id, $this->meta_prefix . 'api_key_encrypted', $encrypted );
            }
            // Empty = no change, keep the existing key
        }
    }

    /**
     * Saves the meta of a module
     *
     * @param int $post_id
     * @param array $data
     */
    private function save_module_meta( $post_id, $data ) {
        if ( isset( $data['parent_course_id'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'parent_course_id', (int) $data['parent_course_id'] );
        }
        if ( isset( $data['theme'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'module_theme', sanitize_text_field( $data['theme'] ) );
        }
        if ( isset( $data['objectives'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'module_objectives', wp_kses_post( $data['objectives'] ) );
        }
        if ( isset( $data['content'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'module_content', wp_kses_post( $data['content'] ) );
        }
        if ( isset( $data['response_example'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'module_response_example', wp_kses_post( $data['response_example'] ) );
        }
        // API key (encrypted)
        if ( isset( $data['api_key'] ) ) {
            $key_value = $data['api_key'];
            if ( $key_value === '__CLEAR__' ) {
                delete_post_meta( $post_id, $this->meta_prefix . 'api_key_encrypted' );
            } elseif ( $key_value !== '' ) {
                $encrypted = $this->api_key_manager->encrypt( $key_value );
                update_post_meta( $post_id, $this->meta_prefix . 'api_key_encrypted', $encrypted );
            }
            // Empty = no change, keep the existing key
        }
    }

    /**
     * Saves the meta of a slide
     *
     * @param int $post_id
     * @param array $data
     * @param string $config_uid
     */
    private function save_slide_meta( $post_id, $data, $config_uid ) {
        // Configuration ID
        update_post_meta( $post_id, $this->meta_prefix . 'config_uid', $config_uid );
        // LLM provider
        if ( isset( $data['provider'] ) ) {
            $valid_providers = \SlideMind\Core\LLMProxy::get_provider_slugs();
            $provider = sanitize_text_field( $data['provider'] );
            if ( empty( $provider ) || in_array( $provider, $valid_providers, true ) ) {
                update_post_meta( $post_id, $this->meta_prefix . 'provider', $provider );
            }
        }
        // Parents
        if ( isset( $data['parent_course_id'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'parent_course_id', (int) $data['parent_course_id'] );
        }
        if ( isset( $data['parent_module_id'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'parent_module_id', (int) $data['parent_module_id'] );
        }
        // Slide number
        if ( isset( $data['slide_number'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'slide_number', ( $data['slide_number'] !== '' ? (int) $data['slide_number'] : '' ) );
        }
        // Mode widget (chat|qa|capture)
        if ( isset( $data['widget_mode'] ) ) {
            $widget_mode = ( in_array( $data['widget_mode'], ['chat', 'qa', 'capture'], true ) ? $data['widget_mode'] : 'qa' );
            update_post_meta( $post_id, $this->meta_prefix . 'widget_mode', $widget_mode );
        }
        // Turn limit (chat mode)
        if ( isset( $data['max_turns'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'max_turns', max( 0, (int) $data['max_turns'] ) );
        }
        // Closing message (chat mode)
        if ( isset( $data['closing_message'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'closing_message', sanitize_textarea_field( $data['closing_message'] ) );
        }
        if ( isset( $data['opening_message'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'opening_message', sanitize_textarea_field( $data['opening_message'] ) );
        }
        // Graceful close
        if ( isset( $data['graceful_close'] ) ) {
            $gc = ( in_array( $data['graceful_close'], ['1', 1, true], true ) ? '1' : '0' );
            update_post_meta( $post_id, $this->meta_prefix . 'graceful_close', $gc );
        }
        if ( isset( $data['graceful_close_prompt'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'graceful_close_prompt', sanitize_textarea_field( $data['graceful_close_prompt'] ) );
        }
        // Widget appearance
        if ( isset( $data['widget_color'] ) ) {
            $color = sanitize_hex_color( $data['widget_color'] );
            update_post_meta( $post_id, $this->meta_prefix . 'widget_color', ( $color ?: '' ) );
        }
        if ( isset( $data['widget_ai_color'] ) ) {
            $color = sanitize_hex_color( $data['widget_ai_color'] );
            update_post_meta( $post_id, $this->meta_prefix . 'widget_ai_color', ( $color ?: '' ) );
        }
        if ( isset( $data['widget_theme'] ) ) {
            $theme = ( in_array( $data['widget_theme'], ['light', 'dark', ''], true ) ? $data['widget_theme'] : '' );
            update_post_meta( $post_id, $this->meta_prefix . 'widget_theme', $theme );
        }
        if ( isset( $data['widget_show_header'] ) ) {
            $val = ( in_array( $data['widget_show_header'], ['0', '1', ''], true ) ? $data['widget_show_header'] : '' );
            update_post_meta( $post_id, $this->meta_prefix . 'widget_show_header', $val );
        }
        if ( isset( $data['widget_ai_text_color'] ) ) {
            $color = sanitize_hex_color( $data['widget_ai_text_color'] );
            update_post_meta( $post_id, $this->meta_prefix . 'widget_ai_text_color', ( $color ?: '' ) );
        }
        if ( isset( $data['widget_border_radius'] ) ) {
            $radius = ( $data['widget_border_radius'] !== '' ? max( 0, min( 24, (int) $data['widget_border_radius'] ) ) : '' );
            update_post_meta( $post_id, $this->meta_prefix . 'widget_border_radius', $radius );
        }
        if ( isset( $data['widget_title'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'widget_title', sanitize_text_field( $data['widget_title'] ) );
        }
        // Model and parameters
        if ( isset( $data['model_id'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'model_id', sanitize_text_field( $data['model_id'] ) );
        }
        if ( isset( $data['system_prompt'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'system_prompt', wp_kses_post( $data['system_prompt'] ) );
        }
        if ( isset( $data['temperature'] ) ) {
            $temp = max( 0, min( 2, (float) $data['temperature'] ) );
            update_post_meta( $post_id, $this->meta_prefix . 'temperature', $temp );
        }
        if ( isset( $data['max_tokens'] ) ) {
            $tokens = max( 1, min( 128000, (int) $data['max_tokens'] ) );
            update_post_meta( $post_id, $this->meta_prefix . 'max_tokens', $tokens );
        }
        // Variables
        if ( isset( $data['variables'] ) && is_array( $data['variables'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'variables', $data['variables'] );
        }
        // Overrides (JSON)
        if ( isset( $data['override_course'] ) ) {
            $override = ( is_array( $data['override_course'] ) ? $data['override_course'] : [] );
            update_post_meta( $post_id, $this->meta_prefix . 'override_course', $override );
        }
        if ( isset( $data['override_module'] ) ) {
            $override = ( is_array( $data['override_module'] ) ? $data['override_module'] : [] );
            update_post_meta( $post_id, $this->meta_prefix . 'override_module', $override );
        }
        // Response example
        if ( isset( $data['response_example'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'response_example', wp_kses_post( $data['response_example'] ) );
        }
        // Notes
        if ( isset( $data['notes'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'notes', sanitize_textarea_field( $data['notes'] ) );
        }
        // Output variables
        if ( isset( $data['output_variables'] ) ) {
            $output_vars = $data['output_variables'];
            if ( is_string( $output_vars ) ) {
                $output_vars = json_decode( wp_unslash( $output_vars ), true );
            }
            if ( is_array( $output_vars ) ) {
                $sanitized = $this->sanitize_output_variables( $output_vars );
                update_post_meta( $post_id, $this->meta_prefix . 'output_variables', wp_json_encode( $sanitized, JSON_UNESCAPED_UNICODE ) );
            }
        }
        // Memory variables (JSON)
        if ( isset( $data['memory_variables'] ) ) {
            $mem_vars = $data['memory_variables'];
            if ( is_string( $mem_vars ) ) {
                $mem_vars = json_decode( wp_unslash( $mem_vars ), true );
            }
            if ( is_array( $mem_vars ) ) {
                $sanitized = $this->sanitize_memory_variables( $mem_vars );
                update_post_meta( $post_id, $this->meta_prefix . 'memory_variables', wp_json_encode( $sanitized, JSON_UNESCAPED_UNICODE ) );
            }
        }
        // Auto-save interaction flags
        if ( isset( $data['save_user_input'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'save_user_input', ( in_array( $data['save_user_input'], ['1', 1, true], true ) ? '1' : '0' ) );
        }
        if ( isset( $data['save_ai_response'] ) ) {
            update_post_meta( $post_id, $this->meta_prefix . 'save_ai_response', ( in_array( $data['save_ai_response'], ['1', 1, true], true ) ? '1' : '0' ) );
        }
        if ( isset( $data['save_input_key'] ) ) {
            $key = sanitize_text_field( $data['save_input_key'] );
            if ( $key && !preg_match( '/^[a-z][a-z0-9_]{0,254}$/', $key ) ) {
                $key = '';
            }
            update_post_meta( $post_id, $this->meta_prefix . 'save_input_key', $key );
        }
        if ( isset( $data['save_response_key'] ) ) {
            $key = sanitize_text_field( $data['save_response_key'] );
            if ( $key && !preg_match( '/^[a-z][a-z0-9_]{0,254}$/', $key ) ) {
                $key = '';
            }
            update_post_meta( $post_id, $this->meta_prefix . 'save_response_key', $key );
        }
        if ( isset( $data['save_interaction_scope'] ) ) {
            $scope = sanitize_text_field( $data['save_interaction_scope'] );
            $scope = ( in_array( $scope, ['global', 'course', 'module'], true ) ? $scope : 'global' );
            update_post_meta( $post_id, $this->meta_prefix . 'save_interaction_scope', $scope );
        }
        // Authoring tool variable names
        if ( isset( $data['editor_input_var'] ) ) {
            $val = sanitize_text_field( $data['editor_input_var'] );
            update_post_meta( $post_id, $this->meta_prefix . 'editor_input_var', ( $val ?: 'userMessage' ) );
        }
        if ( isset( $data['editor_output_var'] ) ) {
            $val = sanitize_text_field( $data['editor_output_var'] );
            update_post_meta( $post_id, $this->meta_prefix . 'editor_output_var', ( $val ?: 'aiResponse' ) );
        }
        // API key
        if ( isset( $data['api_key'] ) ) {
            $key_value = $data['api_key'];
            if ( $key_value === '__CLEAR__' ) {
                delete_post_meta( $post_id, $this->meta_prefix . 'api_key_encrypted' );
            } elseif ( $key_value !== '' ) {
                $encrypted = $this->api_key_manager->encrypt( $key_value );
                update_post_meta( $post_id, $this->meta_prefix . 'api_key_encrypted', $encrypted );
            }
            // Empty = no change, keep the existing key
        }
    }

    /**
     * Sanitizes persistent memory variables
     *
     * @param array $variables
     * @return array
     */
    private function sanitize_memory_variables( array $variables ) : array {
        $sanitized = [];
        $seen_names = [];
        foreach ( $variables as $var ) {
            if ( !is_array( $var ) || empty( $var['name'] ) ) {
                continue;
            }
            $name = strtolower( trim( $var['name'] ) );
            // Validate name format
            if ( !preg_match( '/^[a-z][a-z0-9_]{0,254}$/', $name ) ) {
                continue;
            }
            // No duplicates
            if ( isset( $seen_names[$name] ) ) {
                continue;
            }
            $seen_names[$name] = true;
            // Validate scope
            $scope = ( isset( $var['scope'] ) && in_array( $var['scope'], ['global', 'course', 'module'], true ) ? $var['scope'] : 'global' );
            // Source required
            $source = ( isset( $var['source'] ) ? sanitize_text_field( $var['source'] ) : '' );
            if ( empty( $source ) ) {
                continue;
            }
            $sanitized[] = [
                'name'   => $name,
                'scope'  => $scope,
                'source' => $source,
            ];
        }
        return $sanitized;
    }

    /**
     * Generates an opaque 8-character config UID for a slide.
     *
     * Why: the UID is published in iframe URLs embedded in external LMS
     * courses; keeping it opaque and stable guarantees the URL never
     * changes when the author later edits the slide (widget_mode, title,
     * etc.). Collision probability is negligible (~1 in 218 trillion) but
     * the loop guarantees uniqueness.
     *
     * @param int   $post_id Current post ID, used to detect "reuse existing UID".
     * @param array $data    Unused, kept for backward-compatible call sites.
     * @return string 8-char alphanumeric token.
     */
    private function generate_slide_config_uid( $post_id, $data = [] ) {
        $existing = get_post_meta( $post_id, $this->meta_prefix . 'config_uid', true );
        if ( !empty( $existing ) ) {
            return $existing;
        }
        do {
            $token = wp_generate_password( 8, false );
            $collision = get_posts( [
                'post_type'   => $this->post_type,
                'meta_key'    => $this->meta_prefix . 'config_uid',
                'meta_value'  => $token,
                'fields'      => 'ids',
                'numberposts' => 1,
            ] );
        } while ( !empty( $collision ) );
        return $token;
    }

    /**
     * Sanitizes output variables
     *
     * @param array $variables
     * @return array
     */
    private function sanitize_output_variables( array $variables ) : array {
        $sanitized = [];
        foreach ( $variables as $var ) {
            if ( !is_array( $var ) || empty( $var['name'] ) ) {
                continue;
            }
            $name = strtolower( trim( $var['name'] ) );
            // Validate name format: starts with a letter, followed by letters/digits/underscores
            if ( !preg_match( '/^[a-z][a-z0-9_]*$/', $name ) ) {
                continue;
            }
            // Validate type
            $allowed_types = [
                'texte',
                'nombre',
                'booleen',
                'enum'
            ];
            $type = ( isset( $var['type'] ) && in_array( $var['type'], $allowed_types, true ) ? $var['type'] : 'texte' );
            $entry = [
                'name'        => $name,
                'type'        => $type,
                'description' => ( isset( $var['description'] ) ? sanitize_text_field( $var['description'] ) : '' ),
            ];
            // Enum: parse the values
            if ( $type === 'enum' ) {
                $values = ( isset( $var['values'] ) ? $var['values'] : '' );
                if ( is_string( $values ) ) {
                    $values = array_map( 'trim', explode( ',', $values ) );
                    $values = array_filter( $values, function ( $v ) {
                        return $v !== '';
                    } );
                }
                if ( is_array( $values ) ) {
                    $values = array_map( 'sanitize_text_field', $values );
                    $values = array_values( array_unique( array_filter( $values, function ( $v ) {
                        return $v !== '';
                    } ) ) );
                }
                if ( empty( $values ) ) {
                    continue;
                    // Enum sans valeurs = invalide
                }
                $entry['values'] = $values;
            }
            $sanitized[] = $entry;
        }
        return $sanitized;
    }

}
