<?php
/**
 * Migration: Backfill element_settings, global_values, widget_settings from Flat Columns
 *
 * Appearance presets created before the V2 visual editor have empty JSON columns.
 * The bot resolver falls back to flat columns at runtime, causing discrepancies
 * between what the admin UI shows (V2 defaults) and the live site (V1 flat values).
 *
 * This migration reads the flat column values and permanently populates the JSON
 * columns so the fallback is never triggered.
 *
 * Idempotent: only processes presets with empty/null JSON columns.
 *
 * @package BoostBot
 * @since   2.2.5
 */

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

class BoostBot_Migration_Backfill_Element_Settings {

	private static $flag_key = 'boostbot_migration_backfill_element_settings_done';

	/**
	 * Run the migration.
	 *
	 * @return bool
	 */
	public static function run(): bool {
		if ( '' !== get_option( self::$flag_key, '' ) ) {
			return true;
		}

		global $wpdb;
		$table = self::sanitize_identifier( $wpdb->prefix . 'boostbot_appearance_presets' );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$table_exists = $wpdb->get_var(
			$wpdb->prepare( 'SHOW TABLES LIKE %s', $table )
		);
		if ( ! $table_exists ) {
			update_option( self::$flag_key, current_time( 'mysql' ) );
			return true;
		}

		// Find presets where ANY of the three JSON columns is empty/null.
		// 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().
		$presets = $wpdb->get_results(
			"SELECT * FROM {$table}
			 WHERE (element_settings IS NULL OR element_settings IN ('', '[]', '{}'))
			    OR (global_values IS NULL OR global_values IN ('', '[]', '{}'))
			    OR (widget_settings IS NULL OR widget_settings IN ('', '[]', '{}'))"
		);

		if ( empty( $presets ) ) {
			update_option( self::$flag_key, current_time( 'mysql' ) );
			if ( class_exists( 'BoostBot_Logger' ) ) {
				BoostBot_Logger::info( '[Migration] backfill-element-settings: no presets need backfilling.' );
			}
			return true;
		}

		$migrated_es = 0;
		$migrated_gv = 0;
		$migrated_ws = 0;

		foreach ( $presets as $preset ) {
			$updates = array();
			$formats = array();

			// Backfill element_settings if empty
			$es = self::decode_json( $preset->element_settings );
			if ( empty( $es ) ) {
				$updates['element_settings'] = wp_json_encode( self::build_element_settings( $preset ) );
				$formats[] = '%s';
				$migrated_es++;
			}

			// Backfill global_values if empty
			$gv = self::decode_json( $preset->global_values );
			if ( empty( $gv ) || empty( $gv['colors'] ) ) {
				$updates['global_values'] = wp_json_encode( self::build_global_values( $preset ) );
				$formats[] = '%s';
				$migrated_gv++;
			}

			// Backfill widget_settings with empty object if null/empty string
			// (widget_settings has no flat-column equivalent — just ensure it's valid JSON)
			$ws = self::decode_json( $preset->widget_settings );
			if ( empty( $ws ) ) {
				$updates['widget_settings'] = wp_json_encode( new \stdClass() );
				$formats[] = '%s';
				$migrated_ws++;
			}

			if ( ! empty( $updates ) ) {
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				$wpdb->update(
					$table,
					$updates,
					array( 'id' => $preset->id ),
					$formats,
					array( '%d' )
				);
			}
		}

		if ( class_exists( 'BoostBot_Logger' ) ) {
			BoostBot_Logger::info( sprintf(
				'[Migration] backfill-element-settings: backfilled element_settings=%d, global_values=%d, widget_settings=%d presets.',
				$migrated_es,
				$migrated_gv,
				$migrated_ws
			) );
		}

		update_option( self::$flag_key, current_time( 'mysql' ) );
		return true;
	}

	/**
	 * Build element_settings JSON from flat preset columns.
	 *
	 * Replicates the bot resolver's fallback logic so the stored JSON
	 * produces the same config the live site was already seeing.
	 *
	 * @param object $preset DB row from boostbot_appearance_presets.
	 * @return array
	 */
	private static function build_element_settings( $preset ): array {
		$language = $preset->language ?? 'he';
		$is_hebrew = ( 'he' === $language );

		return array(
			'chatBubble'  => array(
				'size'          => array( 'type' => 'custom', 'value' => $preset->bubble_size ?: '60px' ),
				'background'    => array( 'type' => 'global', 'ref' => 'colors.primary' ),
				'icon'          => $preset->bubble_icon ?: 'robot',
				'customIconUrl' => $preset->bubble_custom_icon ?: '',
				'animation'     => ( $preset->bubble_animation !== null && $preset->bubble_animation !== '' )
					? $preset->bubble_animation
					: 'none',
				'position'      => 'bottom-right',
				'margin'        => array( 'type' => 'custom', 'value' => '24px' ),
			),
			'header'      => array(
				'background'      => array( 'type' => 'custom', 'value' => $preset->header_background ?: $preset->primary_color ?: '#3BC2D0' ),
				'text'            => array( 'type' => 'custom', 'value' => $preset->header_text_color ?: '#FFFFFF' ),
				'height'          => array( 'type' => 'custom', 'value' => '60px' ),
				'padding'         => array( 'type' => 'custom', 'value' => '0 16px' ),
				'showCloseButton' => true,
				'title'           => $preset->name ?: 'BoostBot',
				'subtitle'        => $is_hebrew ? 'מקוון' : 'Online',
			),
			'chatWindow'  => array(
				'width'        => array( 'type' => 'custom', 'value' => $preset->window_width ?: '380px' ),
				'height'       => array( 'type' => 'custom', 'value' => $preset->window_height ?: '600px' ),
				'background'   => array( 'type' => 'custom', 'value' => $preset->background_color ?: '#FFFFFF' ),
				'borderRadius' => array( 'type' => 'custom', 'value' => $preset->window_border_radius ?: '16px' ),
				'shadow'       => array( 'type' => 'custom', 'value' => $preset->window_shadow ?: '0 10px 40px rgba(0,0,0,0.15)' ),
				'padding'      => array( 'type' => 'custom', 'value' => '0' ),
			),
			'userMessage' => array(
				'background'   => array( 'type' => 'custom', 'value' => $preset->user_message_bg ?: $preset->primary_color ?: '#3BC2D0' ),
				'text'         => array( 'type' => 'custom', 'value' => $preset->user_message_text ?: '#FFFFFF' ),
				'borderRadius' => array( 'type' => 'global', 'ref' => 'effects.borderRadiusMedium' ),
				'padding'      => array( 'type' => 'custom', 'value' => '12px 16px' ),
				'maxWidth'     => array( 'type' => 'custom', 'value' => '80%' ),
			),
			'botMessage'  => array(
				'background'   => array( 'type' => 'custom', 'value' => $preset->bot_message_bg ?: '#F9FAFB' ),
				'text'         => array( 'type' => 'custom', 'value' => $preset->bot_message_text ?: '#1F2937' ),
				'borderRadius' => array( 'type' => 'global', 'ref' => 'effects.borderRadiusMedium' ),
				'padding'      => array( 'type' => 'custom', 'value' => '12px 16px' ),
				'maxWidth'     => array( 'type' => 'custom', 'value' => '80%' ),
				'showAvatar'   => true,
			),
			'inputArea'   => array(
				'background'          => array( 'type' => 'custom', 'value' => $preset->input_bg ?: '#FFFFFF' ),
				'text'                => array( 'type' => 'custom', 'value' => $preset->input_text ?: '#1F2937' ),
				'border'              => array( 'type' => 'custom', 'value' => $preset->input_border ?: '#E5E7EB' ),
				'placeholder'         => $is_hebrew ? 'הקלד הודעה...' : 'Type a message...',
				'sendButtonBackground' => array( 'type' => 'global', 'ref' => 'colors.primary' ),
			),
		);
	}

	/**
	 * Build global_values JSON from flat preset columns.
	 *
	 * Replicates the bot resolver's fallback logic for global_values.
	 *
	 * @param object $preset DB row from boostbot_appearance_presets.
	 * @return array
	 */
	private static function build_global_values( $preset ): array {
		return array(
			'colors'     => array(
				'primary'    => $preset->primary_color ?: '#3BC2D0',
				'secondary'  => $preset->secondary_color ?: '#E32774',
				'accent'     => $preset->secondary_color ?: '#E32774',
				'background' => $preset->background_color ?: '#FFFFFF',
				'surface'    => $preset->bot_message_bg ?: '#F9FAFB',
				'text'       => $preset->bot_message_text ?: '#1F2937',
				'textMuted'  => '#6B7280',
				'border'     => $preset->input_border ?: '#E5E7EB',
			),
			'typography' => array(
				'fontFamily' => $preset->font_family ?: 'inherit',
				'heading'    => array(
					'fontFamily' => $preset->font_family ?: 'inherit',
					'fontSize'   => $preset->font_size_header ?: '18px',
					'fontWeight' => $preset->font_weight_bold ?: '600',
					'lineHeight' => $preset->line_height ?: '1.4',
				),
				'body'       => array(
					'fontFamily'    => $preset->font_family ?: 'inherit',
					'fontSize'      => $preset->font_size_base ?: '14px',
					'fontWeight'    => $preset->font_weight_normal ?: '400',
					'lineHeight'    => $preset->line_height ?: '1.5',
					'letterSpacing' => $preset->letter_spacing ?: '0',
				),
			),
			'effects'    => array(
				'borderRadiusSmall'  => '8px',
				'borderRadiusMedium' => '12px',
				'borderRadiusLarge'  => $preset->window_border_radius ?: '16px',
				'shadowSmall'        => '0 2px 8px rgba(0,0,0,0.08)',
				'shadowMedium'       => '0 4px 16px rgba(0,0,0,0.12)',
				'shadowLarge'        => $preset->window_shadow ?: '0 10px 40px rgba(0,0,0,0.15)',
			),
			'spacing'    => array(
				'xs' => '4px',
				'sm' => '8px',
				'md' => '16px',
				'lg' => '24px',
				'xl' => '32px',
			),
		);
	}

	/**
	 * Decode a JSON field to array.
	 *
	 * @param mixed $value Raw DB value.
	 * @return array
	 */
	private static function decode_json( $value ): array {
		if ( is_string( $value ) ) {
			$decoded = json_decode( $value, true );
			return is_array( $decoded ) ? $decoded : array();
		}
		if ( is_object( $value ) ) {
			return json_decode( wp_json_encode( $value ), true ) ?: array();
		}
		if ( is_array( $value ) ) {
			return $value;
		}
		return array();
	}

    /**
     * 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.
     */
    private static function sanitize_identifier( $identifier ) {
        return preg_replace( '/[^A-Za-z0-9_]/', '', (string) $identifier );
    }
}
