<?php
/**
 * Color Manager Class.
 *
 * This file handles the CRUD operations for Divi's global colors, including
 * retrieval, saving, and updating references in post content.
 *
 * @package Color_Theme_Manager_For_Divi
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * Manages Divi global colors and their persistence.
 *
 * @since 1.0.0
 */
class CTMD_Color_Manager {

	/**
	 * The database option name where settings are stored.
	 *
	 * @since 1.0.0
	 * @var string
	 */
	private $option_name = 'et_divi';

	/**
	 * The page slug for the admin page.
	 *
	 * @since 1.0.0
	 * @var string
	 */
	private $page_slug = 'color-theme-manager-for-divi';

	/**
	 * A map of special color IDs to their readable labels.
	 *
	 * @since 1.0.0
	 * @var array
	 */
	private $special_colors_map = array();

	/**
	 * Default special colors.
	 *
	 * @since 1.0.0
	 * @var array
	 */
	private $default_special_colors = array();

	/**
	 * Instance of the Cache Manager class.
	 *
	 * @since 1.0.0
	 * @var CTMD_Cache_Manager
	 */
	private $cache_manager;

	/**
	 * Instance of the Palette Manager class.
	 *
	 * @since 1.0.0
	 * @var CTMD_Palette_Manager
	 */
	private $palette_manager;

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 *
	 * @param CTMD_Cache_Manager $cache_manager The cache manager instance.
	 */
	public function __construct( CTMD_Cache_Manager $cache_manager ) {
		$this->cache_manager = $cache_manager;

		$this->default_special_colors = array(
			'accent_color'           => '#2ea3f2',
			'secondary_accent_color' => '#2ea3f2',
			'header_color'           => '#666666',
			'font_color'             => '#666666',
		);
	}

	/**
	 * Initializes properties that require translation.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public function setup_properties() {
		$this->special_colors_map = array(
			'accent_color'           => __( 'Primary Color', 'color-theme-manager-for-divi' ),
			'secondary_accent_color' => __( 'Secondary Color', 'color-theme-manager-for-divi' ),
			'header_color'           => __( 'Heading Color', 'color-theme-manager-for-divi' ),
			'font_color'             => __( 'Text Color', 'color-theme-manager-for-divi' ),
		);
	}

	/**
	 * Sets the Palette Manager dependency.
	 *
	 * @since 1.0.0
	 *
	 * @param CTMD_Palette_Manager $palette_manager The palette manager instance.
	 * @return void
	 */
	public function set_palette_manager( CTMD_Palette_Manager $palette_manager ) {
		$this->palette_manager = $palette_manager;
	}

	/**
	 * Retrieves global colors from the database.
	 *
	 * @since 1.0.0
	 *
	 * @return array The array of global colors.
	 */
	public function get_global_colors() {
		$option_data = get_option( $this->option_name, array() );
		$option_data = maybe_unserialize( $option_data );

		if ( isset( $option_data['et_global_data'] ) ) {
			$et_global_data = maybe_unserialize( $option_data['et_global_data'] );
			if ( isset( $et_global_data['global_colors'] ) ) {
				$colors = $et_global_data['global_colors'];
				uasort(
					$colors,
					function ( $a, $b ) {
						$order_a = isset( $a['order'] ) ? (int) $a['order'] : 0;
						$order_b = isset( $b['order'] ) ? (int) $b['order'] : 0;
						return $order_a - $order_b;
					}
				);
				return $colors;
			}
		}

		return array();
	}

	/**
	 * Saves changes to global colors.
	 *
	 * Handles updating, reordering, and deleting global colors.
	 * Also updates references in post content if IDs change.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public function save_colors() {
		check_admin_referer( 'ctmd_save_colors_nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Unauthorized.', 'color-theme-manager-for-divi' ) );
		}

		// Sanitize POST data early.
		$post_special_colors = array();

		$raw_special_colors = filter_input( INPUT_POST, 'special_colors', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
		if ( is_array( $raw_special_colors ) ) {
			$raw_special_colors = wp_unslash( $raw_special_colors );
			foreach ( $raw_special_colors as $key => $color ) {
				if ( ! is_string( $color ) ) {
					continue; }
				$post_special_colors[ sanitize_key( $key ) ] = CTMD_Utilities::sanitize_rgba_color( $color );
			}
		}

		$post_colors = array();

		$raw_colors = filter_input( INPUT_POST, 'colors', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
		if ( is_array( $raw_colors ) ) {
			$raw_colors = wp_unslash( $raw_colors );
			foreach ( $raw_colors as $id => $submitted_data ) {
				$clean_id                 = sanitize_text_field( $id );
				$post_colors[ $clean_id ] = array();

				if ( isset( $submitted_data['id'] ) ) {
					if ( is_string( $submitted_data['id'] ) ) {
						$post_colors[ $clean_id ]['id'] = sanitize_text_field( $submitted_data['id'] );
					}
				}

				if ( isset( $submitted_data['label'] ) ) {
					if ( is_string( $submitted_data['label'] ) ) {
						$post_colors[ $clean_id ]['label'] = sanitize_text_field( $submitted_data['label'] );
					}
				}

				if ( isset( $submitted_data['color'] ) ) {
					if ( is_string( $submitted_data['color'] ) ) {
						$post_colors[ $clean_id ]['color'] = CTMD_Utilities::sanitize_rgba_color( $submitted_data['color'] );
					}
				}

				if ( isset( $submitted_data['status'] ) ) {
					$post_colors[ $clean_id ]['status'] = sanitize_key( $submitted_data['status'] );
				}

				if ( isset( $submitted_data['delete'] ) ) {
					$post_colors[ $clean_id ]['delete'] = true;
				}
			}
		}

		$safe_color_order = sanitize_text_field(
			filter_input( INPUT_POST, 'ctmd_color_order', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ?? ''
		);

		$option_data = get_option( $this->option_name, array() );
		$option_data = maybe_unserialize( $option_data );

		$et_global_data = array();
		if ( isset( $option_data['et_global_data'] ) ) {
			$et_global_data = maybe_unserialize( $option_data['et_global_data'] );
		}
		$old_colors = $et_global_data['global_colors'] ?? array();

		if ( is_array( $old_colors ) ) {
			uasort(
				$old_colors,
				function ( $a, $b ) {
					$order_a = isset( $a['order'] ) ? (int) $a['order'] : 0;
					$order_b = isset( $b['order'] ) ? (int) $b['order'] : 0;
					return $order_a <=> $order_b;
				}
			);
		}

		$final_colors_data = array();

		// --- Special Colors ---
		foreach ( $post_special_colors as $key => $color ) {
			if ( array_key_exists( $key, $this->special_colors_map ) ) {
				$option_data[ $key ] = $color;
			}
		}

		// --- Global Colors ---
		foreach ( $old_colors as $id => $color_data ) {
			$submitted_data = $post_colors[ $id ] ?? null;

			if ( isset( $submitted_data['delete'] ) ) {
				if ( $this->is_color_in_use( $id, $color_data['usedInPosts'] ?? array() ) ) {
					$color_data['status']     = 'inactive';
					$color_data['order']      = '';
					$final_colors_data[ $id ] = $color_data;
				}
				continue;
			}

			if ( ! $submitted_data ) {
				$final_colors_data[ $id ] = $color_data;
				continue;
			}

			$new_id = $submitted_data['id'] ?? $id;
			if ( ! preg_match( '/^gcid-/', $new_id ) ) {
				$new_id = 'gcid-' . preg_replace( '/[^a-zA-Z0-9_-]/', '', str_replace( 'gcid_', '', $new_id ) );
			}

			if ( $id !== $new_id ) {
				$this->update_color_id_references( $id, $new_id, $option_data );
			}

			$color_data['id']     = $new_id;
			$color_data['label']  = $submitted_data['label'] ?? $color_data['label'];
			$color_data['color']  = $submitted_data['color'] ?? $color_data['color'];
			$color_data['status'] = isset( $submitted_data['status'] ) && 'active' === $submitted_data['status'] ? 'active' : 'inactive';

			$final_colors_data[ $new_id ] = $color_data;
		}

		// Reorder.
		$ordered_ids   = ! empty( $safe_color_order ) ? explode( ',', $safe_color_order ) : array_keys( $final_colors_data );
		$order_counter = CTMD_COLOR_ORDER_START_INDEX;

		foreach ( $ordered_ids as $id ) {
			if ( isset( $final_colors_data[ $id ] ) ) {
				$final_colors_data[ $id ]['order'] = (string) $order_counter;
				++$order_counter;
			}
		}

		$et_global_data['global_colors'] = $final_colors_data;
		$option_data['et_global_data']   = maybe_serialize( $et_global_data );

		update_option( $this->option_name, $option_data );
		$this->cache_manager->clear_divi_cache();

		wp_safe_redirect( admin_url( 'admin.php?page=' . $this->page_slug . '&tab=colors&message=saved' ) );
		exit;
	}

	/**
	 * Checks if a color is used in any post.
	 *
	 * @since 1.0.0
	 *
	 * @param string $color_id           The ID of the color to check.
	 * @param array  $used_in_posts_array The array of posts where the color is already known to be used.
	 * @return bool True if used, false otherwise.
	 */
	private function is_color_in_use( $color_id, $used_in_posts_array ) {
		if ( ! empty( $used_in_posts_array ) ) {
			return true;
		}

		$args = array(
			'post_type'      => 'any',
			'post_status'    => 'any',
			's'              => $color_id,
			'posts_per_page' => 1,
			'fields'         => 'ids',
		);

		$query = new WP_Query( $args );

		return $query->have_posts();
	}

	/**
	 * Adds a new global color.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public function add_new_color() {
		check_admin_referer( 'ctmd_add_new_color_nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Unauthorized.', 'color-theme-manager-for-divi' ) );
		}

		$post_new_color = array();
		$raw_new_color  = filter_input( INPUT_POST, 'new_color', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );

		if ( is_array( $raw_new_color ) ) {
			$raw_new_color = wp_unslash( $raw_new_color );

			if ( isset( $raw_new_color['id'] ) ) {
				$post_new_color['id'] = sanitize_text_field( $raw_new_color['id'] );
			}
			if ( isset( $raw_new_color['label'] ) ) {
				$post_new_color['label'] = sanitize_text_field( $raw_new_color['label'] );
			}
			if ( isset( $raw_new_color['color'] ) ) {
				$post_new_color['color'] = CTMD_Utilities::sanitize_rgba_color( $raw_new_color['color'] );
			}
		}

		$option_data = get_option( $this->option_name, array() );
		$option_data = maybe_unserialize( $option_data );

		$et_global_data = array();
		if ( isset( $option_data['et_global_data'] ) ) {
			$et_global_data = maybe_unserialize( $option_data['et_global_data'] );
		}
		$global_colors = isset( $et_global_data['global_colors'] ) ? $et_global_data['global_colors'] : array();

		$redirect_params = '&message=color_added';

		if ( ! empty( $post_new_color ) ) {
			$new_id = $post_new_color['id'] ?? '';
			$label  = $post_new_color['label'] ?? '';
			$color  = $post_new_color['color'] ?? '';

			if ( ! empty( $new_id ) && ! empty( $color ) ) {
				if ( ! preg_match( '/^gcid-/', $new_id ) ) {
					$new_id = 'gcid-' . preg_replace( '/[^a-zA-Z0-9_-]/', '', str_replace( 'gcid_', '', $new_id ) );
				}

				if ( ! array_key_exists( $new_id, $global_colors ) ) {
					$global_colors[ $new_id ]        = array(
						'id'          => $new_id,
						'lastUpdated' => current_time( 'c' ),
						'label'       => $label,
						'color'       => $color,
						'order'       => (string) ( count( $global_colors ) + CTMD_COLOR_ORDER_START_INDEX ),
						'status'      => 'active',
						'folder'      => '',
						'usedInPosts' => array(),
					);
					$et_global_data['global_colors'] = $global_colors;
					$option_data['et_global_data']   = maybe_serialize( $et_global_data );
					update_option( $this->option_name, $option_data );
					$this->cache_manager->clear_divi_cache();
				} else {
					$redirect_params = '&error=duplicate_id&failed_id=' . urlencode( $new_id );
				}
			} else {
				$redirect_params = '&error=missing_fields';
			}
		}

		wp_safe_redirect( admin_url( 'admin.php?page=' . $this->page_slug . '&tab=colors' . $redirect_params ) );
		exit;
	}

	/**
	 * Resets colors to their default state.
	 *
	 * Can optionally clear all palettes.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public function reset_colors() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Unauthorized.', 'color-theme-manager-for-divi' ) );
		}
		check_admin_referer( 'ctmd_reset_nonce' );

		$reset_defaults  = sanitize_key(
			filter_input( INPUT_POST, 'reset_default_colors', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ?? '0'
		);
		$delete_palettes = sanitize_key(
			filter_input( INPUT_POST, 'delete_all_palettes', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ?? '0'
		);

		$option_data = get_option( $this->option_name, array() );
		$option_data = maybe_unserialize( $option_data );

		if ( isset( $option_data['et_global_data'] ) ) {
			$et_global_data                  = maybe_unserialize( $option_data['et_global_data'] );
			$et_global_data['global_colors'] = array();
			$option_data['et_global_data']   = maybe_serialize( $et_global_data );
		}

		if ( '1' === $reset_defaults ) {
			foreach ( $this->default_special_colors as $key => $color ) {
				$option_data[ $key ] = $color;
			}
		}

		if ( '1' === $delete_palettes ) {
			$this->palette_manager->delete_all_palettes();
		}

		update_option( $this->option_name, $option_data );
		$this->cache_manager->clear_divi_cache();

		$settings = get_option( 'ctmd_settings', array() );
		if ( isset( $settings['active_theme_name'] ) ) {
			unset( $settings['active_theme_name'] );
			update_option( 'ctmd_settings', $settings );
		}

		delete_option( 'ctmd_master_layout_is_custom' );

		wp_safe_redirect( admin_url( 'admin.php?page=' . $this->page_slug . '&tab=settings&message=reset_success' ) );
		exit;
	}

	/**
	 * Updates references to a color ID in posts and settings.
	 *
	 * @since 1.0.0
	 *
	 * @param string $old_id      The old color ID.
	 * @param string $new_id      The new color ID.
	 * @param array  $option_data The current options data (passed by reference).
	 * @return void
	 */
	private function update_color_id_references( $old_id, $new_id, &$option_data ) {
		$args = array(
			'post_type'      => 'any',
			'post_status'    => 'any',
			's'              => $old_id,
			'posts_per_page' => -1,
			'fields'         => 'ids',
		);

		$query = new WP_Query( $args );

		if ( $query->have_posts() ) {
			foreach ( $query->posts as $post_id ) {
				$post = get_post( $post_id );
				if ( ! $post ) {
					continue;
				}

				$post_content = $post->post_content;
				if ( strpos( $post_content, $old_id ) === false ) {
					continue;
				}

				$blocks = parse_blocks( $post_content );

				$this->replace_id_in_blocks( $blocks, $old_id, $new_id );

				$updated_content = serialize_blocks( $blocks );

				if ( $updated_content !== $post_content ) {
					wp_update_post(
						array(
							'ID'           => $post_id,
							'post_content' => wp_slash( $updated_content ),
						)
					);
				}
			}
		}

		if ( isset( $option_data['divi_custom_css'] ) ) {
			$option_data['divi_custom_css'] = str_replace( $old_id, $new_id, $option_data['divi_custom_css'] );
		}
	}

	/**
	 * Recursively replaces a color ID in an array of blocks.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $blocks  The blocks array (passed by reference).
	 * @param string $old_id  The old color ID.
	 * @param string $new_id  The new color ID.
	 * @return void
	 */
	private function replace_id_in_blocks( &$blocks, $old_id, $new_id ) {
		foreach ( $blocks as &$block ) {
			if ( ! empty( $block['attrs'] ) ) {
				$this->replace_id_in_attributes( $block['attrs'], $old_id, $new_id );
			}
			if ( ! empty( $block['innerBlocks'] ) ) {
				$this->replace_id_in_blocks( $block['innerBlocks'], $old_id, $new_id );
			}
		}
	}

	/**
	 * Recursively replaces a color ID in block attributes.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $attributes The attributes array (passed by reference).
	 * @param string $old_id     The old color ID.
	 * @param string $new_id     The new color ID.
	 * @return void
	 */
	private function replace_id_in_attributes( &$attributes, $old_id, $new_id ) {
		foreach ( $attributes as &$value ) {
			if ( is_string( $value ) ) {
				if ( strpos( $value, '$variable(' ) === 0 && substr( $value, -2 ) === ')$' ) {
					$json_str = substr( $value, 10, -2 );
					$json_str = str_replace( 'u0022', '"', $json_str );
					$decoded  = json_decode( $json_str, true );

					if ( is_array( $decoded ) && isset( $decoded['type'], $decoded['value']['name'] ) && 'color' === $decoded['type'] && $old_id === $decoded['value']['name'] ) {
						$decoded['value']['name'] = $new_id;
						$new_json_str             = wp_json_encode( $decoded );
						$value                    = '$variable(' . $new_json_str . ')$';
					}
				}
			} elseif ( is_array( $value ) ) {
				$this->replace_id_in_attributes( $value, $old_id, $new_id );
			}
		}
	}
}
