<?php
/**
 * Preco class for Product Recommendations plugin.
 *
 * @package PRECO
 */

namespace PRECO\Includes;

defined( 'ABSPATH' ) || exit;

/**
 * Core class for managing plugin actions and integrations.
 *
 * @package PRECO
 */
class Preco {

	/**
	 * Recommendation trigger types (algorithms) that require an active Pro
	 * license. Kept in sync with the `isPro` flags on TRIGGER_OPTIONS in
	 * src/builder/pages/recommendation-builder/settings-drawer/settings-body/rules-builder/utils/rules/options.js
	 *
	 * @var string[]
	 */
	const PRO_TRIGGER_TYPES = array(
		'featured',
		'on_sale',
		'upsells',
		'best_selling',
		'relative_price',
		'high_conversion',
		'high_revenue',
		'low_selling',
		'high_click_through',
		'bought_together',
		'created',
		'customer_interest_categories',
		'customer_interest_tags',
		'customer_interest_attributes',
		'added_to_cart',
		'removed_from_cart',
	);

	/**
	 * Whether a recommendation trigger type is Pro-only.
	 *
	 * @param string $trigger_type Trigger type slug (e.g. 'best_selling').
	 * @return bool
	 */
	public static function is_pro_trigger_type( $trigger_type ) {
		return in_array( (string) $trigger_type, self::PRO_TRIGGER_TYPES, true );
	}

	/**
	 * WooCommerce product types the frontend renderers are able to render as
	 * recommendations (see RecommendationsRenderer::render_product_objects()
	 * and OrderBumpRecommendations::get_offer_products()). Also used to
	 * restrict the "Specific Products" product-search suggestions in the
	 * builder so admins can't pick a product type that will never render.
	 *
	 * @var string[]
	 */
	const PRODUCT_RENDER_TYPES = array(
		'simple',
		'variable',
		'grouped',
		'external',
	);

	/**
	 * Whether the store has at least one published product of a renderable
	 * type (see PRODUCT_RENDER_TYPES above). Onboarding and the
	 * Recommendation Builder both need a real product to build/preview a
	 * recommendation against, so the admin UI checks this before letting
	 * either one start.
	 *
	 * @return bool
	 */
	public static function has_products() {
		$query = new \WP_Query(
			array(
				'post_type'              => 'product',
				'post_status'            => 'publish',
				'posts_per_page'         => 1,
				'fields'                 => 'ids',
				'no_found_rows'          => true,
				'update_post_meta_cache' => false,
				'update_post_term_cache' => false,
				'tax_query'              => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
					array(
						'taxonomy' => 'product_type',
						'field'    => 'slug',
						'terms'    => self::PRODUCT_RENDER_TYPES,
					),
				),
			)
		);

		return $query->have_posts();
	}

	/**
	 * Display Conditions "operator" (frequency) values that require an active
	 * Pro license. Kept in sync with the `isPro` flags on operatorOptions in
	 * src/builder/pages/recommendation-builder/settings-drawer/display-conditions.js
	 *
	 * @var string[]
	 */
	const PRO_OPERATOR_TYPES = array(
		'after_delay',
		'page_reloads',
	);

	/**
	 * Whether a Display Conditions operator (frequency) value is Pro-only.
	 *
	 * @param string $operator_type Operator slug (e.g. 'after_delay').
	 * @return bool
	 */
	public static function is_pro_operator_type( $operator_type ) {
		return in_array( (string) $operator_type, self::PRO_OPERATOR_TYPES, true );
	}

	/**
	 * Mirrors the JS generateCustomCSS() utility.
	 * Converts design_data into a string of CSS custom-property declarations
	 * suitable for use as an inline style attribute value.
	 *
	 * @param array $design_data The recommendation design/style configuration.
	 * @return string CSS custom-property declarations, e.g. " --preco-cs-cols-lg:4".
	 */
	public static function adding_specific_properties( $design_data ) {
		$content_size_map = array(
			'heading'   => array(
				'large'  => '22px',
				'medium' => '18px',
			),
			'price'     => array(
				'large'  => '16px',
				'medium' => '14px',
			),
			'title'     => array(
				'large'  => '16px',
				'medium' => '14px',
			),
			'addToCart' => array(
				'large'  => '16px',
				'medium' => '14px',
			),
			'rating'    => array(
				'large'  => '16px',
				'medium' => '14px',
			),
			'category'  => array(
				'large'  => '16px',
				'medium' => '14px',
			),
		);

		$default_colors = array(
			'primary'        => '#1E1E1E',
			'over_primary'   => '#FFFFFF',
			'text_primary'   => '#0A0A0A',
			'text_secondary' => '#757575',
			'background'     => '#FFFFFF',
			'border_color'   => '#DDDDDD',
		);

		$design_data       = is_array( $design_data ) ? $design_data : array();
		$columns           = isset( $design_data['columns'] ) && is_array( $design_data['columns'] ) ? $design_data['columns'] : array();
		$style             = isset( $design_data['style'] ) && is_array( $design_data['style'] ) ? $design_data['style'] : array();
		$container_border  = ! empty( $style['container_border'] ) ? $style['container_border'] : 'default';
		$container_padding = 'default' === $container_border ? ( isset( $style['container_padding'] ) ? $style['container_padding'] : 16 ) : 0;

		$css_vars = array(
			'--preco-cs-cols-lg'           => isset( $columns['lg'] ) ? intval( $columns['lg'] ) : 4,
			'--preco-cs-cols-md'           => isset( $columns['md'] ) ? intval( $columns['md'] ) : 2,
			'--preco-cs-cols-sm'           => isset( $columns['sm'] ) ? intval( $columns['sm'] ) : 1,
			'--preco-cs-container-padding' => $container_padding . 'px ',
		);

		$parts = array();
		foreach ( $css_vars as $prop => $value ) {
			$parts[] = $prop . ':' . $value;
		}

		return implode( ';', $parts );
	}

	/**
	 * Gets license key
	 *
	 * @return string
	 */
	public static function get_lc_key() {
		return get_option( 'edd_preco_license_key', '' );
	}

	/**
	 * Checks if the license key is active.
	 *
	 * @return bool True if the license is active, false otherwise.
	 */
	public static function is_lc_active() {
		if ( defined( 'PRECO_PRO_VER' ) ) {
			$license_data = get_option( 'edd_preco_license_data', array() );
			return isset( $license_data['license'] ) && 'valid' === $license_data['license'];
		}
		return false;
	}

	/**
	 * Checks if the license has expired.
	 *
	 * This method checks the stored license data in the WordPress options table
	 * and determines if the license status is set to 'expired'. It returns `true`
	 * if the license is expired, otherwise `false`.
	 *
	 * @return bool True if the license is expired, otherwise false.
	 */
	public static function is_lc_expired() {
		$license_data = get_option( 'edd_preco_license_data', array() );
		return isset( $license_data['license'] ) && 'expired' === $license_data['license'];
	}

	/**
	 * Get Option Value bypassing cache
	 * Inspired By WordPress Core get_option
	 *
	 * @since v.1.0.7
	 * @param string  $option Option Name.
	 * @param boolean $default_value option default value.
	 * @return mixed
	 */
	public static function get_option_without_cache( $option, $default_value = false ) {
		global $wpdb;

		if ( is_scalar( $option ) ) {
			$option = trim( $option );
		}

		if ( empty( $option ) ) {
			return false;
		}

		$value = $default_value;

		$row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching

		if ( is_object( $row ) ) {
			$value = $row->option_value;
		} else {
			return apply_filters( "preco_default_option_{$option}", $default_value, $option );
		}

		return apply_filters( "preco_option_{$option}", maybe_unserialize( $value ), $option );
	}

	/**
	 * Add option without adding to the cache
	 * Inspired By WordPress Core set_transient
	 *
	 * @since v.1.0.7
	 * @param string $option option name.
	 * @param string $value option value.
	 * @param string $autoload whether to load WordPress startup.
	 * @return bool
	 */
	public static function add_option_without_cache( $option, $value = '', $autoload = 'yes' ) {
		global $wpdb;

		if ( is_scalar( $option ) ) {
			$option = trim( $option );
		}

		if ( empty( $option ) ) {
			return false;
		}

		wp_protect_special_option( $option );

		if ( is_object( $value ) ) {
			$value = clone $value;
		}

		$value = sanitize_option( $option, $value );

		/*
		* Make sure the option doesn't already exist.
		*/

		if ( apply_filters( "preco_default_option_{$option}", false, $option, false ) !== self::get_option_without_cache( $option ) ) {
			return false;
		}

		$serialized_value = maybe_serialize( $value );
		$autoload         = ( 'no' === $autoload || false === $autoload ) ? 'no' : 'yes';

		$result = $wpdb->query( $wpdb->prepare( "INSERT INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE `option_name` = VALUES(`option_name`), `option_value` = VALUES(`option_value`), `autoload` = VALUES(`autoload`)", $option, $serialized_value, $autoload ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		if ( ! $result ) {
			return false;
		}

		return true;
	}

	/**
	 * Get Transient Value bypassing cache
	 * Inspired By WordPress Core get_transient
	 *
	 * @since v.1.0.7
	 * @param string $transient Transient Name.
	 * @return mixed
	 */
	public static function get_transient_without_cache( $transient ) {
		$transient_option  = '_transient_' . $transient;
		$transient_timeout = '_transient_timeout_' . $transient;
		$timeout           = self::get_option_without_cache( $transient_timeout );

		if ( false !== $timeout && $timeout < time() ) {
			delete_option( $transient_option );
			delete_option( $transient_timeout );
			$value = false;
		}

		if ( ! isset( $value ) ) {
			$value = self::get_option_without_cache( $transient_option );
		}

		return apply_filters( "preco_transient_{$transient}", $value, $transient );
	}

	/**
	 * Set transient without adding to the cache
	 * Inspired By WordPress Core set_transient
	 *
	 * @since v.1.0.7
	 * @param string  $transient Transient Name.
	 * @param mixed   $value Transient Value.
	 * @param integer $expiration Time until expiration in seconds.
	 * @return bool
	 */
	public static function set_transient_without_cache( $transient, $value, $expiration = 0 ) {
		$expiration = (int) $expiration;

		$transient_timeout = '_transient_timeout_' . $transient;
		$transient_option  = '_transient_' . $transient;

		$result = false;

		if ( false === self::get_option_without_cache( $transient_option ) ) {
			$autoload = 'yes';
			if ( $expiration ) {
				$autoload = 'no';
				self::add_option_without_cache( $transient_timeout, time() + $expiration, 'no' );
			}
			$result = self::add_option_without_cache( $transient_option, $value, $autoload );
		} else {
			/*
			* If expiration is requested, but the transient has no timeout option,
			* delete, then re-create transient rather than update.
			*/
			$update = true;

			if ( $expiration ) {
				if ( false === self::get_option_without_cache( $transient_timeout ) ) {
					delete_option( $transient_option );
					self::add_option_without_cache( $transient_timeout, time() + $expiration, 'no' );
					$result = self::add_option_without_cache( $transient_option, $value, 'no' );
					$update = false;
				} else {
					update_option( $transient_timeout, time() + $expiration );
				}
			}

			if ( $update ) {
				$result = update_option( $transient_option, $value );
			}
		}

		return $result;
	}

	/**
	 * Installs and activates a plugin by its name only.
	 *
	 * @param string $name The name or slug of the plugin to install and activate.
	 */
	public static function install_and_active_plugin( $name ) {
		$to_r        = array( 'done' => true );
		$plugin_slug = '';
		switch ( $name ) {
			case 'wow_shipping':
				$plugin_slug = 'wow-table-rate-shipping';
				break;
			case 'post_x':
				$plugin_slug = 'ultimate-post';
				break;
			case 'wow_store':
				$plugin_slug = 'product-blocks';
				break;
			case 'wow_optin':
				$plugin_slug = 'optin';
				break;
			case 'wow_revenue':
				$plugin_slug = 'revenue';
				break;
			case 'wholesale_x':
				$plugin_slug = 'wholesalex';
				break;
			case 'wow_addon':
				$plugin_slug = 'product-addons';
				break;
			case 'wow_invoice':
				$plugin_slug = 'wow-pdf-invoices-packing-slips';
				break;
			case 'woocommerce':
				$plugin_slug = 'woocommerce';
				break;
		}

		if ( '' === $plugin_slug ) {
			return array(
				'done'    => true,
				'message' => __( 'Invalid plugin slug.', 'wow-ai-product-recommendations' ),
			);
		}

		if ( ! file_exists( WP_PLUGIN_DIR . '/' . $plugin_slug . '/' . $plugin_slug . '.php' ) ) {
				$to_r = self::plugin_install( $plugin_slug . '/' . $plugin_slug . '.php', $plugin_slug );
		} else {
			activate_plugin( $plugin_slug . '/' . $plugin_slug . '.php' );
		}
		return $to_r;
	}

	/**
	 * Installs a plugin based on the provided plugin file and slug.
	 *
	 * This function is expected to handle the logic required to install a plugin,
	 * such as downloading, unpacking, and activating the plugin using the provided
	 * plugin file and slug.
	 *
	 * @param string $plugin The plugin file path or identifier (e.g., 'plugin-directory/plugin-file.php').
	 * @param string $slug   The plugin slug (typically the directory name of the plugin).
	 */
	public static function plugin_install( $plugin, $slug ) {
		require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
		require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';

		if ( ! class_exists( 'Plugin_Upgrader' ) ) {
			require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php';
		}
		if ( ! class_exists( 'WP_Ajax_Upgrader_Skin' ) ) {
			require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';
		}

		$api = plugins_api(
			'plugin_information',
			array(
				'slug'   => $slug,
				'fields' => array(
					'short_description' => false,
					'sections'          => false,
					'requires'          => false,
					'rating'            => false,
					'ratings'           => false,
					'downloaded'        => false,
					'last_updated'      => false,
					'added'             => false,
					'tags'              => false,
					'compatibility'     => false,
					'homepage'          => false,
					'donate_link'       => false,
				),
			)
		);

		if ( is_wp_error( $api ) ) {
			wp_die( $api ); //phpcs:ignore
		}

		$upgrader       = new \Plugin_Upgrader( new \WP_Ajax_Upgrader_Skin( compact( 'title', 'url', 'nonce', 'plugin', 'api' ) ) );
		$install_result = $upgrader->install( $api->download_link );

		if ( ! is_wp_error( $install_result ) ) {
			activate_plugin( $plugin );
			return array( 'done' => false );
		}

		return array( 'done' => true );
	}

	/**
	 * Get WOW Products Details
	 *
	 * @return array
	 */
	public static function get_wow_products_details() {
		return array(
			'products'        => array(
				'wow_shipping' => file_exists( WP_PLUGIN_DIR . '/wow-table-rate-shipping/wow-table-rate-shipping.php' ),
				'post_x'       => file_exists( WP_PLUGIN_DIR . '/ultimate-post/ultimate-post.php' ),
				'wow_store'    => file_exists( WP_PLUGIN_DIR . '/product-blocks/product-blocks.php' ),
				'wow_optin'    => file_exists( WP_PLUGIN_DIR . '/optin/optin.php' ),
				'wow_revenue'  => file_exists( WP_PLUGIN_DIR . '/revenue/revenue.php' ),
				'wholesale_x'  => file_exists( WP_PLUGIN_DIR . '/wholesalex/wholesalex.php' ),
				'wow_invoice'  => file_exists( WP_PLUGIN_DIR . '/wow-pdf-invoices-packing-slips/wow-pdf-invoices-packing-slips.php' ),
				'wow_addon'    => file_exists( WP_PLUGIN_DIR . '/product-addons/product-addons.php' ),
			),
			'products_active' => array(
				'wow_shipping' => defined( 'WTRS_VER' ),
				'post_x'       => defined( 'ULTP_VER' ),
				'wow_store'    => defined( 'WOPB_VER' ),
				'wow_optin'    => defined( 'OPTN_VERSION' ),
				'wow_revenue'  => defined( 'REVENUE_VER' ),
				'wholesale_x'  => defined( 'WHOLESALEX_VER' ),
				'wow_invoice'  => defined( 'WINV_VER' ),
				'wow_addon'    => defined( 'PRAD_VER' ),
			),
		);
	}

	/**
	 * Generates a URL with UTM parameters for tracking.
	 *
	 * @param array $params {
	 *     Optional. Parameters for generating the UTM link.
	 *
	 *     @type string $url       The base URL to which UTM parameters will be added.
	 *     @type string $utmKey    The key to select a default UTM configuration.
	 *     @type string $affiliate Affiliate ID to append as a 'ref' parameter.
	 *     @type string $hash      Hash fragment to append to the URL.
	 *     @type array  $config    Custom UTM configuration array.
	 * }
	 * @return string The generated URL with UTM parameters.
	 */
	public static function generate_utm_link( $params ) {
		$default_config = array(
			'example'     => array(
				'source'   => 'db-wowrecom-featurename',
				'medium'   => 'block-feature',
				'campaign' => 'wowrecom-dashboard',
			),
			'sub_menu'    => array(
				'source'   => 'db-wowrecom-plugin',
				'medium'   => 'sub-menu',
				'campaign' => 'wowrecom-dashboard',
			),
			'plugin_meta' => array(
				'source'   => 'db-wowrecom-plugin',
				'medium'   => 'plugin-meta',
				'campaign' => 'wowrecom-dashboard',
			),
		);

		// Step 1: Get parameters.
		$base_url      = $params['url'] ?? 'https://www.wpxpo.com/product/wowrecommend/';
		$utm_key       = $params['utmKey'] ?? null;
		$affiliate     = $params['affiliate'] ?? apply_filters( 'preco_affiliate_id', '' );
		$hash          = $params['hash'] ?? 'pricing';
		$custom_config = $params['config'] ?? null;

		$parsed_url = wp_parse_url( $base_url );
		$scheme     = $parsed_url['scheme'] ?? 'https';
		$host       = $parsed_url['host'] ?? '';
		$path       = $parsed_url['path'] ?? '';
		$query      = array();

		// Step 3: Extract existing query params if present.
		if ( isset( $parsed_url['query'] ) ) {
			parse_str( $parsed_url['query'], $query );
		}

		// Step 4: Determine config.
		$utm_config = $custom_config ?? ( $utm_key && isset( $default_config[ $utm_key ] ) ? $default_config[ $utm_key ] : array() );

		// Step 5: Add UTM parameters.
		if ( ! empty( $utm_config ) ) {
			$query = array_merge(
				$query,
				array(
					'utm_source'   => $utm_config['source'],
					'utm_medium'   => $utm_config['medium'],
					'utm_campaign' => $utm_config['campaign'],
				)
			);
		}

		// Step 6: Add affiliate if present.
		if ( $affiliate ) {
			$query['ref'] = $affiliate;
		}

		// Step 7: Reconstruct URL.
		$final_url = $scheme . '://' . $host . $path;

		if ( ! empty( $query ) ) {
			$final_url .= '?' . http_build_query( $query );
		}

		if ( $hash ) {
			$final_url .= '#' . $hash;
		}

		return $final_url;
	}
}
