<?php
/**
 * Appearance Preset Model for database operations.
 *
 * @package BoostBot
 * @since   1.3.0
 */

// Prevent direct access
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Class BoostBot_Appearance_Preset_Model
 *
 * Handles CRUD operations for appearance presets.
 *
 * @since 1.3.0
 */
class BoostBot_Appearance_Preset_Model extends BoostBot_Base_Model {

    use BoostBot_Localizable {
        get_default_for_language as protected trait_get_default_for_language;
    }

    /**
     * JSON fields that need to be encoded/decoded.
     *
     * @var array
     */
    protected $json_fields = array( 'global_values', 'element_settings', 'widget_settings' );

    /**
     * Cached default widget settings from the seed file.
     *
     * @var array|null
     */
    protected static $default_widget_settings_cache = null;

    /**
     * Constructor.
     */
    public function __construct() {
        parent::__construct();
        $this->table_name = self::sanitize_identifier( $this->wpdb->prefix . 'boostbot_appearance_presets' );
        $this->fillable = array(
            'name',
            'slug',
            'language',
            'description',
            'is_default',
            'is_system',
            // Colors
            'primary_color',
            'secondary_color',
            'background_color',
            'header_background',
            'header_text_color',
            'user_message_bg',
            'user_message_text',
            'bot_message_bg',
            'bot_message_text',
            'input_bg',
            'input_text',
            'input_border',
            'button_bg',
            'button_text',
            'button_hover_bg',
            'link_color',
            // Typography
            'font_family',
            'font_size_base',
            'font_size_header',
            'font_weight_normal',
            'font_weight_bold',
            'line_height',
            'letter_spacing',
            // Bubble
            'bubble_size',
            'bubble_icon',
            'bubble_custom_icon',
            'bubble_animation',
            // Window
            'window_width',
            'window_height',
            'window_border_radius',
            'window_shadow',
            // Custom
            'custom_css',
            // Week 21 V2 - Visual Editor JSON fields
            'global_values',
            'element_settings',
            'widget_settings',
        );
    }

    /**
     * Get a single record by ID with JSON fields decoded.
     *
     * @param int $id The record ID.
     * @return object|null The record or null if not found.
     */
    public function get( $id ) {
        $record = parent::get( $id );
        return $this->decode_json_fields( $record );
    }

    /**
     * Create a new preset with default widget settings when missing.
     *
     * @param array $data Preset data.
     * @return int|false
     */
    public function create( $data ) {
        if ( ! isset( $data['widget_settings'] ) || $this->is_empty_widget_settings( $data['widget_settings'] ) ) {
            $data['widget_settings'] = $this->get_default_widget_settings();
        }

        return parent::create( $data );
    }

    /**
     * Get all records with JSON fields decoded.
     *
     * @param array $args Optional. Query arguments.
     * @return array Array of records.
     */
    public function get_all( $args = array() ) {
        $records = parent::get_all( $args );
        foreach ( $records as &$record ) {
            $record = $this->decode_json_fields( $record );
        }
        return $records;
    }

    /**
     * Get the default preset for a language with decoded JSON fields.
     *
     * @param string $language Language code.
     * @return object|null
     */
    public function get_default_for_language( string $language = '' ): ?object {
        $record = $this->trait_get_default_for_language( $language );
        return $this->decode_json_fields( $record );
    }

    /**
     * Decode JSON fields in a record.
     *
     * @param object|null $record The record.
     * @return object|null The record with decoded JSON fields.
     */
    protected function decode_json_fields( $record ) {
        if ( ! $record ) {
            return $record;
        }

        foreach ( $this->json_fields as $field ) {
            if ( isset( $record->$field ) && is_string( $record->$field ) && ! empty( $record->$field ) ) {
                $decoded = json_decode( $record->$field );
                if ( json_last_error() === JSON_ERROR_NONE ) {
                    $record->$field = $decoded;
                }
            }
        }

        $record->widget_settings = $this->merge_widget_settings_with_defaults( $record->widget_settings ?? array() );

        return $record;
    }

    /**
     * Set preset as default (removes default from others).
     *
     * @param int $id The preset ID.
     * @return bool Success or failure.
     */
    public function set_default( $id ) {
        $preset    = $this->get( $id );
        $language  = $preset->language ?? 'he';

        // Remove default only within the preset's language
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $this->wpdb->update(
            $this->table_name,
            array( 'is_default' => 0 ),
            array(
                'is_default' => 1,
                'language'   => $language,
            ),
            array( '%d' ),
            array( '%d', '%s' )
        );
        
        // Set new default
        return $this->update( $id, array( 'is_default' => 1 ) );
    }

    /**
     * Get default preset.
     *
     * @return object|null The default preset or null.
     */
    public function get_default() {
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $record = $this->wpdb->get_row(
            "SELECT * FROM {$this->table_name} WHERE is_default = 1 LIMIT 1" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        );

        return $this->decode_json_fields( $record );
    }

    /**
     * Get bots using this preset.
     *
     * @param int $preset_id The preset ID.
     * @return array Array of bots.
     */
    public function get_linked_bots( $preset_id ) {
        $bot_table = self::sanitize_identifier( $this->wpdb->prefix . 'boostbot_bots' );
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table identifier sanitized via sanitize_identifier(); values bind via prepare().
        return $this->wpdb->get_results(
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $this->wpdb->prepare(
                "SELECT id, bot_name FROM {$bot_table} WHERE appearance_preset_id = %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                $preset_id
            )
        );
    }

    /**
     * Get all presets for dropdown.
     *
     * @return array Array of presets with id, name, is_default, is_system.
     */
    public function get_for_dropdown() {
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        return $this->wpdb->get_results(
            "SELECT id, name, is_default, is_system FROM {$this->table_name} ORDER BY name ASC" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        );
    }

    /**
     * Validate preset data.
     *
     * @param array $data The data to validate.
     * @param int   $id   Optional. The ID for update validation.
     * @return true|WP_Error True if valid, WP_Error otherwise.
     */
    public function validate( $data, $id = null ) {
        if ( empty( $data['name'] ) ) {
            return new WP_Error( 'validation_error', __( 'Name is required.', 'boostbot' ) );
        }

        if ( strlen( $data['name'] ) > 255 ) {
            return new WP_Error( 'validation_error', __( 'Name must be less than 255 characters.', 'boostbot' ) );
        }

        // Validate colors
        $color_fields = array(
            'primary_color',
            'secondary_color',
            'background_color',
            'header_background',
            'header_text_color',
            'user_message_bg',
            'user_message_text',
            'bot_message_bg',
            'bot_message_text',
            'input_bg',
            'input_text',
            'input_border',
            'button_bg',
            'button_text',
            'button_hover_bg',
            'link_color',
        );

        foreach ( $color_fields as $field ) {
            if ( ! empty( $data[ $field ] ) && ! preg_match( '/^#[0-9A-Fa-f]{3,8}$/', $data[ $field ] ) ) {
                return new WP_Error( 'validation_error', sprintf(
                    /* translators: %s: field name */
                    __( 'Invalid color format for %s. Use hex format (e.g., #3BC2D0).', 'boostbot' ),
                    $field
                ) );
            }
        }

        return true;
    }

    /**
     * Sanitize preset data.
     *
     * @param array $data The data to sanitize.
     * @return array The sanitized data.
     */
    public function sanitize( $data ) {
        $sanitized = array();

        // Text fields
        if ( isset( $data['name'] ) ) {
            $sanitized['name'] = sanitize_text_field( $data['name'] );
        }
        if ( isset( $data['slug'] ) ) {
            $sanitized['slug'] = sanitize_title( $data['slug'] );
        }
        if ( isset( $data['description'] ) ) {
            $sanitized['description'] = sanitize_textarea_field( $data['description'] );
        }

        // Boolean fields
        if ( isset( $data['is_default'] ) ) {
            $sanitized['is_default'] = (int) (bool) $data['is_default'];
        }
        if ( isset( $data['is_system'] ) ) {
            $sanitized['is_system'] = (int) (bool) $data['is_system'];
        }

        // Color fields
        $color_fields = array(
            'primary_color',
            'secondary_color',
            'background_color',
            'header_background',
            'header_text_color',
            'user_message_bg',
            'user_message_text',
            'bot_message_bg',
            'bot_message_text',
            'input_bg',
            'input_text',
            'input_border',
            'button_bg',
            'button_text',
            'button_hover_bg',
            'link_color',
        );
        foreach ( $color_fields as $field ) {
            if ( isset( $data[ $field ] ) ) {
                $sanitized[ $field ] = sanitize_hex_color( $data[ $field ] );
            }
        }

        // Size/typography fields
        $size_fields = array(
            'font_family',
            'font_size_base',
            'font_size_header',
            'font_weight_normal',
            'font_weight_bold',
            'line_height',
            'letter_spacing',
            'bubble_size',
            'bubble_icon',
            'bubble_animation',
            'window_width',
            'window_height',
            'window_border_radius',
            'window_shadow',
        );
        foreach ( $size_fields as $field ) {
            if ( isset( $data[ $field ] ) ) {
                $sanitized[ $field ] = sanitize_text_field( $data[ $field ] );
            }
        }

        // URL field
        if ( isset( $data['bubble_custom_icon'] ) ) {
            $sanitized['bubble_custom_icon'] = esc_url_raw( $data['bubble_custom_icon'] );
        }

        // Custom CSS - allow safe CSS
        if ( isset( $data['custom_css'] ) ) {
            $sanitized['custom_css'] = wp_strip_all_tags( $data['custom_css'] );
        }

        // Week 21 V2 - Visual Editor JSON fields
        $json_fields = array( 'global_values', 'element_settings', 'widget_settings' );
        foreach ( $json_fields as $field ) {
            if ( isset( $data[ $field ] ) ) {
                if ( 'widget_settings' === $field ) {
                    $sanitized[ $field ] = wp_json_encode(
                        $this->merge_widget_settings_with_defaults( $data[ $field ] )
                    );
                } elseif ( is_array( $data[ $field ] ) || is_object( $data[ $field ] ) ) {
                    $sanitized[ $field ] = wp_json_encode( $data[ $field ] );
                } elseif ( is_string( $data[ $field ] ) ) {
                    // Already JSON string, validate it
                    $decoded = json_decode( $data[ $field ], true );
                    if ( json_last_error() === JSON_ERROR_NONE ) {
                        if ( 'widget_settings' === $field ) {
                            $sanitized[ $field ] = wp_json_encode(
                                $this->merge_widget_settings_with_defaults( $decoded )
                            );
                        } else {
                            $sanitized[ $field ] = $data[ $field ];
                        }
                    }
                }
            }
        }

        if ( isset( $data['language'] ) ) {
            $sanitized['language'] = sanitize_text_field( $data['language'] );
        }

        return $sanitized;
    }

    /**
     * Generate slug from name.
     *
     * @param string $name The name.
     * @return string The generated slug.
     */
    public function generate_slug( $name ) {
        $slug = sanitize_title( $name );
        
        // Ensure uniqueness
        $original_slug = $slug;
        $counter = 1;
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        while ( $this->wpdb->get_var( $this->wpdb->prepare(
            "SELECT COUNT(*) FROM {$this->table_name} WHERE slug = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $slug
        ) ) > 0 ) {
            $slug = $original_slug . '-' . $counter;
            $counter++;
        }
        
        return $slug;
    }

    /**
     * Get default widget settings from the seed file.
     *
     * @return array
     */
    protected function get_default_widget_settings(): array {
        if ( null !== self::$default_widget_settings_cache ) {
            return self::$default_widget_settings_cache;
        }

        $seed_file = BOOSTBOT_PLUGIN_DIR . 'includes/database/seeds/default-appearance-preset.json';
        if ( ! file_exists( $seed_file ) ) {
            self::$default_widget_settings_cache = array();
            return self::$default_widget_settings_cache;
        }

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
        $seed_json = file_get_contents( $seed_file );
        $seed_data = json_decode( $seed_json, true );

        self::$default_widget_settings_cache = is_array( $seed_data['widget_settings'] ?? null )
            ? $seed_data['widget_settings']
            : array();

        return self::$default_widget_settings_cache;
    }

    /**
     * Convert widget settings to an associative array.
     *
     * @param mixed $widget_settings Raw widget settings.
     * @return array
     */
    protected function normalize_widget_settings( $widget_settings ): array {
        if ( is_array( $widget_settings ) ) {
            return $widget_settings;
        }

        if ( is_object( $widget_settings ) ) {
            return json_decode( wp_json_encode( $widget_settings ), true ) ?: array();
        }

        if ( is_string( $widget_settings ) && '' !== trim( $widget_settings ) ) {
            $decoded = json_decode( $widget_settings, true );
            if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
                return $decoded;
            }
        }

        return array();
    }

    /**
     * Merge stored widget settings over the seeded defaults.
     *
     * @param mixed $widget_settings Raw widget settings.
     * @return array
     */
    protected function merge_widget_settings_with_defaults( $widget_settings ): array {
        $stored   = $this->normalize_widget_settings( $widget_settings );
        $defaults = $this->get_default_widget_settings();

        if ( empty( $defaults ) ) {
            return $stored;
        }

        return array_replace_recursive( $defaults, $stored );
    }

    /**
     * Check whether widget settings are effectively empty.
     *
     * @param mixed $widget_settings Raw widget settings.
     * @return bool
     */
    protected function is_empty_widget_settings( $widget_settings ): bool {
        return array() === $this->normalize_widget_settings( $widget_settings );
    }

    /**
     * Restrict a SQL identifier to [A-Za-z0-9_] characters.
     *
     * DDL cannot use $wpdb->prepare() for identifiers, so any identifier
     * that ends up interpolated into a query first passes through this
     * allow-list.
     *
     * @param string $identifier Raw identifier.
     * @return string Identifier with disallowed characters removed.
     */
    protected static function sanitize_identifier( $identifier ) {
        return preg_replace( '/[^A-Za-z0-9_]/', '', (string) $identifier );
    }
}

