<?php

namespace SweetCode\Pixel_Manager;

use ActionScheduler_Store;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumberFormat;
use libphonenumber\PhoneNumberUtil;
use stdClass;
use WC_Log_Handler_File;
use SweetCode\Pixel_Manager\Admin\Environment;

defined('ABSPATH') || exit; // Exit if accessed directly

class Helpers {

	private static $user_data;
	private static $current_screen_id;
	private static $current_screen_post_type;

	/**
	 * This function takes any of the input types and sanitizes them automatically.
	 *
	 * @param string $input_type
	 *        The type of input to sanitize. Can be INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, or INPUT_ENV.
	 *
	 * @param bool   $raw
	 *        Whether to apply raw sanitization or not. Default is false.
	 *        If true, the function will apply FILTER_UNSAFE_RAW sanitization.
	 *        This is potentially unsafe and should be used with caution.
	 *
	 * @return mixed
	 */
	public static function get_input_vars( $input_type, $raw = false ) {

		$data = filter_input_array($input_type, FILTER_UNSAFE_RAW);

		return self::generic_sanitization($data, $raw);
	}

	private static function sanitize_string( $string, $raw = false ) {

		if ($raw) {
			return filter_var($string, FILTER_UNSAFE_RAW);
		}

		// Don't use FILTER_SANITIZE_STRING as it is deprecated from PHP 8.x
		// Don't use FILTER_SANITIZE_FULL_SPECIAL_CHARS as it will reformat Umlauts like ä, ö, ü
		// Don't use strip_tags() as it will remove information.
		// https://stackoverflow.com/a/69207369/4688612
		// https://gist.github.com/alewolf/e9235adeaf26dc024bab4f53825ec6da

		return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
	}

	public static function generic_sanitization( $input, $raw = false ) {

		if (is_array($input)) {
			$sanitized_array = [];

			foreach ($input as $key => $value) {
				$sanitized_array[$key] = self::generic_sanitization($value, $raw);
			}

			return $sanitized_array;
		}

		if (is_object($input)) {
			$sanitized_object = new stdClass();

			foreach ($input as $key => $value) {
				$sanitized_object->$key = self::generic_sanitization($value, $raw);
			}

			return $sanitized_object;
		}

		if (is_string($input)) {
			return self::sanitize_string($input, $raw);
		}

		if (is_bool($input)) {
			// No sanitization needed for boolean values
			return $input;
		}

		// If the input is of any other type (e.g., int, float), no sanitization is needed
		return $input;
	}

	public static function is_allowed_notification_page( $page ) {

		$allowed_pages = [ 'page_pmw', 'index.php', 'dashboard' ];

		foreach ($allowed_pages as $allowed_page) {
			if (strpos($page, $allowed_page) !== false) {
				return true;
			}
		}

		return false;
	}

	public static function is_wc_hpos_enabled() {
		return class_exists('Automattic\WooCommerce\Utilities\OrderUtil') && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled();
	}

	/**
	 * Checks if the current screen is the WooCommerce Orders page.
	 *
	 * @return bool
	 *
	 * @since 1.43.5
	 */
	public static function is_orders_page() {

		if (!is_admin()) {
			return false;
		}

		$_get = self::get_input_vars(INPUT_GET);

		// Detects the order page in the admin on non-HPOS enabled shops
		if ('edit-shop_order' === self::pmw_current_screen('id')) {
			return true;
		}

		// Detects the order page in the admin on HPOS enabled shops
		if (
			'woocommerce_page_wc-orders' === self::pmw_current_screen('id')
			&& 'shop_order' === self::pmw_current_screen('post_type')
			&& !isset($_get['action'])
		) {
			return true;
		}

		return false;
	}

	/**
	 * Checks if the current screen is the WooCommerce Edit Order page.
	 *
	 * @return bool
	 *
	 * @since 1.43.5
	 */
	public static function is_edit_order_page() {

		if (!is_admin()) {
			return false;
		}

		$_get = self::get_input_vars(INPUT_GET);

		// Detects the order page in the admin on non-HPOS enabled shops
		if ('shop_order' === self::pmw_current_screen('id')) {
			return true;
		}

		// Detects the order page in the admin on HPOS enabled shops
		if (
			'woocommerce_page_wc-orders' === self::pmw_current_screen('id')
			&& 'shop_order' === self::pmw_current_screen('post_type')
			&& isset($_get['action'])
			&& isset($_get['id'])
		) {
			return true;
		}

		return false;
	}

	public static function is_email( $email ) {
		return filter_var($email, FILTER_VALIDATE_EMAIL);
	}

	public static function is_url( $url ) {
		return filter_var($url, FILTER_VALIDATE_URL);
	}

	public static function clean_product_name_for_output( $name ) {
		return html_entity_decode($name, ENT_QUOTES | ENT_HTML5, 'UTF-8');
	}

	public static function get_e164_formatted_phone_number( $number, $country ) {

		try {
			$phone_util    = PhoneNumberUtil::getInstance();
			$number_parsed = $phone_util->parse($number, $country);
			return $phone_util->format($number_parsed, PhoneNumberFormat::E164);
		} catch (NumberParseException $e) {
			/**
			 * Don't error log the exception. It leads to more confusion than it helps:
			 * https://wordpress.org/support/topic/php-errors-in-version-1-27-0/
			 */
			return $number;
		}
	}

	// https://stackoverflow.com/a/60199374/4688612
	public static function is_iframe() {
		if (isset($_SERVER['HTTP_SEC_FETCH_DEST']) && 'iframe' === $_SERVER['HTTP_SEC_FETCH_DEST']) {
			return true;
		} else {
			return false;
		}
	}

	public static function get_percentage( $counter, $denominator ) {
		return $denominator > 0 ? round($counter / $denominator * 100) : 0;
	}

	public static function get_user_country_code( $user ) {

		// If the country code is set on the user, return it
		if (isset($user->billing_country)) {
			return $user->billing_country;
		}

		// Geolocate the user IP and return the country code
		return Geolocation::get_visitor_country();
	}

	public static function is_dashboard() {

		global $pagenow;

		// Don't check for the plugin settings page. Notifications have to be handled there.
		$allowed_pages = [
			'index.php',
			'dashboard',
		];

		foreach ($allowed_pages as $allowed_page) {
			if (strpos($pagenow, $allowed_page) !== false) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Trim a settings page input string by removing all whitespace, newlines, tabs, double quotes, single quotes and backticks.
	 *
	 * @return string
	 * @since 1.27.10
	 */
	public static function trim_string( $string ) {

		$string = trim($string);

		// Return the string with all newlines, tabs, double quotes, single quotes and backticks removed. Keep spaces within the string.
		$string = preg_replace('/[\n\r\t"\'`]/', '', $string);

		// Remove all quotes
		$string = str_replace('"', '', $string);

		// Remove html quote entities
		$string = str_replace('&quot;', '', $string);

		// Remove anything from the front and back of the string that looks like &#039; or similar
		$string = preg_replace('/&[^;]+;/', '', $string);

		// Remove a ; at the end of the string
		return rtrim($string, ';');
	}

	private static function normalize_for_snapchat( $string ) {

		// to lower case
		$string = strtolower($string);

		// remove punctuation
		$string = self::remove_punctuation($string);

		return $string;
	}

	/**
	 * Remove punctuation from a string.
	 *
	 * @param string $string
	 * @return string
	 *
	 * @since 1.42.7
	 */
	public static function remove_punctuation( $string ) {
		return str_replace([ '.', ',', ';', ':', '!', '?', '(', ')', '[', ']', '{', '}', '<', '>', '/', '\\', '|', '@', '#', '$', '%', '^', '&', '*', '+', '=', '~', '`', '"', "'" ], '', $string);
	}

	private static function normalize_google_email_address( $email ) {

		// If the email address is a @gmail.com or @googlemail.com address, remove dots and plus signs in the first part of the email address
		if (preg_match('/@gmail.com$/', $email) || preg_match('/@googlemail.com$/', $email)) {
			$email_parts = explode('@', $email);

			// remove all dots in the first part
			$email_parts[0] = str_replace('.', '', $email_parts[0]);

			// remove all plus signs in the first part
			$email_parts[0] = str_replace('+', '', $email_parts[0]);

			$email = implode('@', $email_parts);
		}

		return $email;
	}

	/**
	 * Filter to enable PMW lazy loading.
	 *
	 * @return bool
	 * @since 1.28.0
	 */
	public static function lazy_load_pmw__premium_only() {

		if (
			Options::lazy_load_requirements()
			&& Options::is_lazy_load_pmw_active()
		) {
			return true;
		}

		if (apply_filters('experimental_lazy_load_pmw', false)) {
			return true;
		}

		return false;
	}

	/**
	 * Filter to return the Facebook fbevents.js script URL.
	 *
	 * It allows to either return the latest version or a specific version.
	 *
	 * @return string
	 * @since 1.30.0
	 */
	public static function get_facebook_fbevents_js_url() {

		$fbevents_standard_url = 'https://connect.facebook.net/en_US/fbevents.js';

		if (apply_filters('pmw_facebook_fbevents_script_version', '')) {
			return $fbevents_standard_url . '?v=' . apply_filters('pmw_facebook_fbevents_script_version', '');
		}

		if (apply_filters('pmw_facebook_fbevents_script_url', '')) {
			return apply_filters('pmw_facebook_fbevents_script_url', '');
		}

		return $fbevents_standard_url;
	}

	/**
	 * Given a datetime string, return the unix timestamp for the local timezone.
	 *
	 * @param $datetime_string
	 * @return false|string
	 *
	 * @since 1.30.8
	 */
	public static function datetime_string_to_unix_timestamp_in_local_timezone( $datetime_string ) {
		return wp_date('U', strtotime($datetime_string . ' ' . wp_timezone_string()));
	}

	public static function is_admin_page( $page_ids = [] ) {

		// If no page IDs are given, check if the current page is an admin page
		if (empty($page_ids)) {
			return false;
		}

		// Check if the current page is an admin page and if it is one of the given page IDs
//		return is_admin() && in_array(get_current_screen()->id, $page_ids);

		$_get = self::get_input_vars(INPUT_GET);

		if (
			!isset($_get['page'])
			|| !in_array($_get['page'], $page_ids)
		) {
			return false;
		}

		return true;
	}

	public static function hash_string( $string, $algo = 'sha256' ) {

		// If the given algorithm is supported, hash the string
		if (in_array($algo, hash_algos(), true)) {
			return hash($algo, $string);
		}

		return $string;
	}

	public static function get_random_ip() {

		$ip = '';

		// Generate a random IP address
		for ($i = 0; $i < 4; $i++) {
			$ip .= wp_rand(0, 255);
			if ($i < 3) {
				$ip .= '.';
			}
		}

		return $ip;
	}

	/**
	 * We are controlling the entire output in all formats from here. Why?
	 * Because each pixel has different requirements for each data field.
	 * Hashed, not hashed, lower case, not lower case, phone with + or without +,
	 * etc.
	 *
	 * @return array
	 */
	public static function get_user_data( $order = null ) {

		// if the user_data array is already set and not empty, return it
		if (!empty(self::$user_data)) {
			return self::$user_data;
		}

		$user_data = [];

		$order
			= (
			Environment::is_woocommerce_active()
			&& !$order
			&& Shop::pmw_is_order_received_page()
			&& Shop::pmw_get_current_order()
		)
			? Shop::pmw_get_current_order()
			: $order;

		// If the order is not null,
		// get the $current_user from the order.
		if ($order && $order->get_user()) {
			$current_user = $order->get_user();
		} else {
			$current_user = is_user_logged_in() ? wp_get_current_user() : null;
		}

		// If the user is logged in, get the user data
		if ($current_user) {
			$user_data['id']['raw']    = get_current_user_id();
			$user_data['id']['sha256'] = self::hash_string($user_data['id']['raw']);
		}

		/**
		 * Determine the details.
		 *
		 * If logged in use the logged-in user data.
		 * On the order page, override the logged-in user data with the order data.
		 */

		$raw_user_data = self::get_raw_user_data($order, $current_user);

		// Add the details to the data array and return it
		// Only add the data if it exists
//		$data = $email ? self::get_user_object_email($email) : $data;

		// If $email is not empty,
		// get the data from self::get_user_object_email($email) and add it to $data['email']

		if (!empty($raw_user_data['email'])) {
			$user_data['email'] = self::get_user_object_email($raw_user_data['email']);
		}

		if (!empty($raw_user_data['first_name'])) {
			$user_data['first_name'] = self::get_user_object_first_name($raw_user_data['first_name']);
		}

		if (!empty($raw_user_data['last_name'])) {
			$user_data['last_name'] = self::get_user_object_last_name($raw_user_data['last_name']);
		}

		if (!empty($raw_user_data['phone'])) {
			$user_data['phone'] = self::get_user_object_phone($raw_user_data['phone'], $current_user);
		}

		if (!empty($raw_user_data['city'])) {
			$user_data['city'] = self::get_user_object_city($raw_user_data['city']);
		}

		if (!empty($raw_user_data['state'])) {
			$user_data['state'] = self::get_user_object_state($raw_user_data['state']);
		}

		if (!empty($raw_user_data['postcode'])) {
			$user_data['postcode'] = self::get_user_object_postcode($raw_user_data['postcode']);
		}

		if (!empty($raw_user_data['country'])) {
			$user_data['country'] = self::get_user_object_country($raw_user_data['country']);
		}

		if (!empty($raw_user_data['roles'])) {
			$user_data['roles'] = $raw_user_data['roles'];
		}

		self::$user_data = $user_data;

		return $user_data;
	}

	public static function get_raw_user_data( $order = null, $current_user = null ) {

		// Email
		$email = is_user_logged_in() && $current_user->user_email ? $current_user->user_email : '';
		$email = $order && $order->get_billing_email() ? $order->get_billing_email() : $email;

		// First name
		$first_name = is_user_logged_in() && $current_user->first_name ? $current_user->first_name : '';
		$first_name = $order && $order->get_billing_first_name() ? $order->get_billing_first_name() : $first_name;

		// Last name
		$last_name = is_user_logged_in() && $current_user->last_name ? $current_user->last_name : '';
		$last_name = $order && $order->get_billing_last_name() ? $order->get_billing_last_name() : $last_name;

		// Phone
		$phone = is_user_logged_in() && $current_user->billing_phone ? $current_user->billing_phone : '';
		$phone = $order && $order->get_billing_phone() ? $order->get_billing_phone() : $phone;

		// City
		$city = is_user_logged_in() && $current_user->billing_city ? $current_user->billing_city : '';
		$city = $order && $order->get_billing_city() ? $order->get_billing_city() : $city;

		// State
		$state = is_user_logged_in() && $current_user->billing_state ? $current_user->billing_state : '';
		$state = $order && $order->get_billing_state() ? $order->get_billing_state() : $state;

		// Postcode
		$postcode = is_user_logged_in() && $current_user->billing_postcode ? $current_user->billing_postcode : '';
		$postcode = $order && $order->get_billing_postcode() ? $order->get_billing_postcode() : $postcode;

		// Country
		$country = is_user_logged_in() && $current_user->billing_country ? $current_user->billing_country : '';
		$country = $order && $order->get_billing_country() ? $order->get_billing_country() : $country;

		// Roles
		$roles = is_user_logged_in() && $current_user ? $current_user->roles : [];

		return [
			'email'      => $email,
			'first_name' => $first_name,
			'last_name'  => $last_name,
			'phone'      => $phone,
			'city'       => $city,
			'state'      => $state,
			'postcode'   => $postcode,
			'country'    => $country,
			'roles'      => $roles,
		];
	}

	public static function get_user_data_object( $order = null ) {
		$data = self::get_user_data($order);
		return json_decode(wp_json_encode($data));
	}

	private static function get_user_object_email( $email ) {

		$email = self::trim_string($email);
		$email = strtolower($email);
//		$email = self::normalize_google_email_address($email);

		return [
			'raw'       => $email,
			'sha256'    => self::hash_string($email),
			'facebook'  => self::hash_string($email),
			'pinterest' => self::hash_string($email),
			'tiktok'    => self::hash_string($email),
		];
	}

	private static function get_user_object_first_name( $first_name ) {

		$first_name = self::trim_string($first_name);

		return [
			'raw'       => $first_name,
			'sha256'    => self::hash_string($first_name),
			'facebook'  => self::hash_string(strtolower($first_name)),
			'pinterest' => self::hash_string(strtolower($first_name)),
			'snapchat'  => self::hash_string(self::normalize_for_snapchat($first_name)),
		];
	}

	private static function get_user_object_last_name( $last_name ) {

		$last_name = self::trim_string($last_name);

		return [
			'raw'       => $last_name,
			'sha256'    => self::hash_string($last_name),
			'facebook'  => self::hash_string(strtolower($last_name)),
			'pinterest' => self::hash_string(strtolower($last_name)),
			'snapchat'  => self::hash_string(self::normalize_for_snapchat($last_name)),
		];
	}

	private static function get_user_object_phone( $phone, $current_user ) {

		$phone      = self::trim_string($phone);
		$phone_e164 = self::get_e164_formatted_phone_number(strtolower($phone), self::get_user_country_code($current_user));

		return [
			'raw'       => $phone,
			'e164'      => $phone_e164,
			'facebook'  => self::hash_string(str_replace('+', '', strtolower($phone))),
			'pinterest' => self::hash_string(preg_replace('/[^0-9]/', '', $phone_e164)),
			'snapchat'  => self::hash_string(str_replace('+', '', $phone_e164)),
			'tiktok'    => self::hash_string($phone_e164),
			'sha256'    => [
				'raw'  => self::hash_string($phone),
				'e164' => self::hash_string($phone_e164),
			],
		];
	}

	private static function get_user_object_city( $city ) {

		$city = self::trim_string($city);

		return [
			'raw'       => $city,
			'sha256'    => self::hash_string($city),
			'facebook'  => self::hash_string(strtolower($city)),
			'pinterest' => self::hash_string(strtolower(preg_replace('/[^A-Za-z0-9\-]/', '', $city))),
			'snapchat'  => self::hash_string(self::normalize_for_snapchat($city)),
		];
	}

	private static function get_user_object_state( $state ) {

		$state = self::trim_string($state);

		return [
			'raw'       => $state,
			'sha256'    => self::hash_string($state),
			'facebook'  => self::hash_string(preg_replace('/[a-zA-Z]{2}-/', '', strtolower($state))),
			'pinterest' => self::hash_string(strtolower($state)),
			'snapchat'  => self::hash_string(self::normalize_for_snapchat($state)),
		];
	}

	private static function get_user_object_postcode( $postcode ) {

		$postcode = self::trim_string($postcode);

		return [
			'raw'       => $postcode,
			'sha256'    => self::hash_string($postcode),
			'facebook'  => self::hash_string(strtolower($postcode)),
			'pinterest' => self::hash_string(preg_replace('/[^0-9]/', '', $postcode)),
		];
	}

	/**
	 * This function takes a user data array and a country string and adds the country values to the user object.
	 *
	 * @param string $country
	 * @return array
	 */
	private static function get_user_object_country( $country ) {

		$country = self::trim_string($country);

		return [
			'raw'       => $country,
			'sha256'    => self::hash_string($country),
			'facebook'  => self::hash_string(strtolower($country)),
			'pinterest' => self::hash_string(strtolower($country)),
			'snapchat'  => self::hash_string(self::normalize_for_snapchat($country)),
		];
	}

	/**
	 * Filter to define if all s2s requests should be sent blocking
	 *
	 * @return bool
	 */
	public static function should_all_s2s_requests_be_sent_blocking() {
		return (bool) apply_filters('pmw_send_all_s2s_requests_blocking', false) || Options::is_http_request_logging_enabled();
	}

	/**
	 * This function takes a number and formats it as a decimal.
	 *
	 * @param mixed $number
	 *         The number to format. Can be a string or a float.
	 *
	 * @param mixed $dp
	 *         The number of decimal places to round to. Default is false, which means no rounding is performed.
	 *
	 * @param bool  $trim_zeros
	 *         Whether to trim trailing zeros and the decimal point. Default is false.
	 *
	 * @return float | null | string
	 *         Returns the formatted decimal number as a string, or false if the input is not a valid number.
	 */
	public static function format_decimal( $number, $dp = false, $trim_zeros = false ) {

		if (function_exists('wc_format_decimal')) {
			return wc_format_decimal($number, $dp, $trim_zeros);
		} else {
			if (is_string($number)) {

				// Remove all spaces
				$number = str_replace(' ', '', $number);

				// Convert multiple dots to one
				// $number = preg_replace('/\.{2,}/', '.', $number);
				$number = preg_replace('/\.(?![^.]+$)|[^0-9.-]/', '', $number);

				// echo $number . PHP_EOL;

				$number = (float) preg_replace('/[^0-9\.\-]/', '', $number);
			}

			if (!is_float($number)) {
				return null;
			}

			if (false !== $dp) {
				$number = round($number, $dp);
			}

			$number = (string) $number;

			if ($trim_zeros) {
				$number = rtrim($number, '0');
				$number = rtrim($number, '.');
			}

			return $number;
		}
	}

	/**
	 * Checks if experimental feature EXPERIMENTAL_PMW is enabled.
	 *
	 * @return bool
	 *        Returns true if EXPERIMENTAL_PMW or PMW_EXPERIMENTS are defined and have a truthy value, otherwise returns false.
	 */
	public static function is_experiment() {
		return
			( defined('EXPERIMENTAL_PMW') && EXPERIMENTAL_PMW )
			|| ( defined('PMW_EXPERIMENTS') && PMW_EXPERIMENTS );
	}

	/**
	 * This function checks if the WooCommerce compatibility declaration function exists.
	 *
	 * @return bool
	 */
	public static function does_the_woocommerce_declare_compatibility_function_exist() {
		return
			class_exists('\Automattic\WooCommerce\Utilities\FeaturesUtil')
			&& method_exists('\Automattic\WooCommerce\Utilities\FeaturesUtil', 'declare_compatibility');
	}

	/**
	 * Declare compatibility with a feature for WooCommerce.
	 *
	 * @param string $feature_id
	 *        The ID of the feature to declare compatibility with.
	 *
	 * @param string $plugin_file
	 *        Optional. The plugin file to specify the compatibility for. Default is PMW_PLUGIN_BASENAME.
	 *
	 * @param bool   $positive_compatibility
	 *        Optional. Whether to declare positive compatibility or not. Default is true.
	 */
	public static function declare_woocommerce_compatibility( $feature_id, $plugin_file = PMW_PLUGIN_BASENAME, $positive_compatibility = true ) {

		if (!self::does_the_woocommerce_declare_compatibility_function_exist()) {
			return;
		}

		\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility($feature_id, $plugin_file, $positive_compatibility);
	}

	/**
	 * Get the version information of the software.
	 *
	 * @return array The version information.
	 *  - 'number' : string : The current version number.
	 *  - 'pro' : bool : Whether the software is a premium version or not.
	 *  - 'eligible_for_updates' : bool : Whether the software can receive updates or not.
	 *  - 'distro' : string : The distribution identifier of the software.
	 *  - 'beta' : bool : Whether the software is a beta version or not.
	 */
	public static function get_version_info() {
		return [
			'number'               => PMW_CURRENT_VERSION,
			'pro'                  => wpm_fs()->is__premium_only(),
			'eligible_for_updates' => wpm_fs()->can_use_premium_code__premium_only(),
			'distro'               => PMW_DISTRO,
			//			'beta'               => PMW_DISTRO === 'fms' ? wpm_fs()->is_beta() : false,
			'beta'                 => self::is_beta(),
			'show'                 => apply_filters('pmw_show_version_info', true),
		];
	}


	/**
	 * This function checks if the application is running in beta mode.
	 *
	 * @return bool
	 *
	 * @see   self::is_experiment()
	 * @see   wpm_fs()->is_beta()
	 *
	 * @since 1.35.1
	 */
	private static function is_beta() {

		if (self::is_experiment()) {
			return true;
		}

		if (PMW_DISTRO === 'fms') {
			return wpm_fs()->is_beta();
		}

		return false;
	}

	public static function is_valid_ipv6_address( $ip ) {
		return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
	}

	/**
	 * Check if the WP_DEBUG constant is defined and set to true or false.
	 *
	 * This function essentially checks whether WordPress debugging mode
	 * is active or not. The WP_DEBUG constant is an in-built WordPress
	 * constant that can be used to trigger the 'debug' mode throughout
	 * WordPress.
	 *
	 * @return bool Returns true if WP_DEBUG is defined and set to true,
	 * otherwise it returns false.
	 *
	 * @since 1.35.1
	 */
	public static function is_wp_debug_mode_active() {
		return defined('WP_DEBUG') && WP_DEBUG;
	}

	/**
	 * Checks if the PMW_DEBUG_CONSTANT is defined and true.
	 *
	 * This function checks if the PHP constant 'PMW_DEBUG' is defined in the system. If it is defined,
	 * this function further checks that the value of the constant is truthy. It essentially determines
	 * if the PMW Debug Mode is active in the environment.
	 *
	 * @return bool Returns true if 'PMW_DEBUG' constant is defined and its value is true, else returns false.
	 *
	 * @since 1.36.0
	 */
	public static function is_pmw_debug_mode_active() {
		return defined('PMW_DEBUG') && PMW_DEBUG;
	}

	/**
	 * Retrieves the file name of the most recent WooCommerce log that starts with a specific source.
	 *
	 * This function fetches all log files, then filters them to retain only the ones
	 * starting with the specified source string. It then returns the file name of the
	 * most recent log matching this criteria.
	 *
	 * If there are no logs, or no logs match the source, an empty string is returned.
	 *
	 * @param string $source The source string that the log file name should start with.
	 * @return string The file name of the most recent log that starts with $source. If no such log exists, returns an empty string.
	 */
	private static function get_file_name_of_most_recent_wc_log( $source ) {

		// return if the class WC_Log_Handler_File does not exist
		if (!class_exists('WC_Log_Handler_File')) {
			return '';
		}

		$logs = WC_Log_Handler_File::get_log_files();

		if (empty($logs)) {
			return '';
		}

		// If $logs array contains a key that starts with $source . '-' then return the latest in the array
		$pmw_logs = array_filter(
			$logs,
			function ( $key ) use ( $source ) {
				return strpos($key, $source . '-') === 0;
			},
			ARRAY_FILTER_USE_KEY
		);

		if (empty($pmw_logs)) {
			return '';
		}

		$last_key = array_key_last($pmw_logs);

		return $pmw_logs[$last_key];
	}

	/**
	 * Get the link to the most recent log file,
	 * for the given slug. The slug must be exactly
	 * the same as the source parameter in the log call.
	 *
	 * @param $source
	 *
	 * @return string|null
	 *
	 * @since 1.36.0
	 */
	public static function get_admin_url_link_to_recent_wc_log( $source ) {

		$file_name = self::get_file_name_of_most_recent_wc_log($source);

		if (empty($file_name)) {
			return null;
		}

		// Extract file ID by removing the hash suffix and .log extension
		// Convert from format: pmw-2025-06-10-2dc3f32ec2d3b5648224688f83d50e51.log
		// To format: pmw-2025-06-10
		$file_id = $file_name;
		
		// Remove .log extension if present
		if (substr($file_id, -4) === '.log') {
			$file_id = substr($file_id, 0, -4);
		}
		
		// Remove hash suffix (32 character hex string preceded by a dash)
		// Pattern: source-YYYY-MM-DD-hash32chars
		if (preg_match('/^(.+-\d{4}-\d{2}-\d{2})-[a-f0-9]{32}$/', $file_id, $matches)) {
			$file_id = $matches[1];
		}

		return admin_url('admin.php?page=wc-status&tab=logs&view=single_file&file_id=' . $file_id);
	}

	public static function get_external_url_to_most_recent_log( $source ) {

		$file_name = self::get_file_name_of_most_recent_wc_log($source);

		if (empty($file_name)) {
			return null;
		}

		return self::get_url_to_log_dir() . $file_name;
	}

	private static function get_url_to_log_dir() {
		// return if WC_LOG_DIR is not defined
		if (!defined('WC_LOG_DIR')) {
			return '';
		}

		$wc_log_dir = substr(trailingslashit(WC_LOG_DIR), strpos(trailingslashit(WC_LOG_DIR), '/wp-content/'));

		$wc_log_dir = get_bloginfo('url') . $wc_log_dir;

		return trailingslashit($wc_log_dir);
	}

	/**
	 * Checks if a scheduled action exists in Action Scheduler.
	 *
	 * This function checks if a scheduled action with a specific hook, arguments, and group exists in Action Scheduler.
	 * If the function 'as_has_scheduled_action' exists, it uses it to check for the scheduled action.
	 * If 'as_has_scheduled_action' does not exist, it uses 'as_next_scheduled_action' to check for the scheduled action.
	 * This is necessary as installs that use plugins with versions of Action Scheduler older than 3.2.1 don't reliably load
	 * the latest version of Action Scheduler which contains the 'as_has_scheduled_action' function.
	 *
	 * Read: https://developer.woo.com/2021/10/12/best-practices-for-deconflicting-different-versions-of-action-scheduler/
	 *
	 * @param string $hook             The hook of the scheduled action to check for.
	 * @param array  $args             Optional. The arguments of the scheduled action to check for. Default is an empty array.
	 * @param string $group            Optional. The group of the scheduled action to check for. Default is an empty string.
	 * @param string $partial_matching Optional. Whether to perform partial matching on the arguments or not. Default is 'off'. Can be 'like' or 'json'.
	 *
	 * @return bool Returns true if a scheduled action with the specified hook, arguments, and group exists, otherwise returns false.
	 */
	public static function pmw_as_has_scheduled_action( $hook, $args = [], $group = '', $partial_matching = 'off' ) {

		// If $partial_matching is true, set it to 'like'
		if ($partial_matching) {
			$partial_matching = 'like';
		}

		return (bool) as_get_scheduled_actions([
			'hook'                  => $hook,
			'args'                  => $args,
			'group'                 => $group,
			'partial_args_matching' => $partial_matching,
			'status'                => self::get_action_scheduler_active_states(),
			'per_page'              => 1,        // to make it as fast as possible
			'orderby'               => 'none',   // to make it as fast as possible
		],
			'ids'                    // to make it as fast as possible
		);
	}

	/**
	 * This function returns the active states for the Action Scheduler.
	 *
	 * It checks if the class 'ActionScheduler_Store' exists. If it does, it returns an array with the constants
	 * 'STATUS_PENDING' and 'STATUS_RUNNING' from the 'ActionScheduler_Store' class.
	 *
	 * If the 'ActionScheduler_Store' class does not exist, it returns an array with the strings 'Pending' and 'In-progress'.
	 *
	 * @return array An array containing the active states for the Action Scheduler.
	 *
	 * @since 1.37.1
	 */
	private static function get_action_scheduler_active_states() {

		if (class_exists('ActionScheduler_Store')) {
			return [
				ActionScheduler_Store::STATUS_PENDING,
				ActionScheduler_Store::STATUS_RUNNING,
			];
		}

		return [
			'Pending',
			'In-progress',
		];
	}

	public static function can_order_modal_be_shown() {
		return Options::is_ga4_data_api_active()
			|| Options::is_order_level_ltv_calculation_active();
	}

	public static function get_script_string_allowed_html() {
		return [
//			'class' => [],
//			'data-cookieconsent' => [],
		];
	}

	/**
	 * Generates an opening script string with its associated attributes.
	 * The attributes may include those from active plugins like Iubenda or Cookiebot
	 * and can be filtered using the 'pmw_opening_script_string_attributes' hook.
	 *
	 * @return string The formatted opening script string with attributes.
	 */
	public static function get_opening_script_string() {

		$script_string = '';

		$attributes = [];

		// if the Iubenda plugin is active, add the Iubenda attributes
		if (Environment::is_iubenda_active()) {
			// add an attribute class and add a class name _iub_cs_skip to it
			$attributes['class'][] = '_iub_cs_skip';
		}

		if (Environment::is_cookiebot_active()) {
			$attributes['data-cookieconsent'] = [ 'ignore' ];
			$attributes['data-uc-allowed']    = [ 'true' ]; // The Cookiebot plugin now seems to also load the Usercentrics CMP script in some cases.
		}

		if (Environment::is_usercentrics_cmp_active()) {
			$attributes['data-uc-allowed'] = [ 'true' ];
		}

		// Elementor burger menus are not working with Cloudflare's Rocket Loader
		// source: https://secure.helpscout.net/conversation/2793909416/3974/
		if (
			Environment::is_elementor_active() &&
			Environment::is_server_behind_cloudflare()
		) {
			$attributes['data-cfasync'] = [ 'false' ];
		}

		$attributes = apply_filters('pmw_opening_script_string_attributes', $attributes);

		// Build the attribute string
		foreach ($attributes as $attribute => $values) {
			$script_string .= ' ' . $attribute . '="' . implode(' ', $values) . '"';
		}

		return $script_string;
	}

	public static function iubenda_script_exception_start() {

		if (!Environment::is_iubenda_active()) {
			return;
		}

		?>

		<!--IUB-COOKIE-BLOCK-SKIP-START-->
		<?php
	}

	public static function iubenda_script_exception_end() {
		if (!Environment::is_iubenda_active()) {
			return;
		}

		?>

		<!--IUB-COOKIE-BLOCK-SKIP-END-->
		<?php
	}

	public static function make_full_url( $url ) {
		// Trim any leading or trailing whitespace
		$url = trim($url);

		// Check if the URL already contains a protocol
		if (preg_match('/^(http:\/\/|https:\/\/)/', $url)) {
			return $url;
		}

		// Remove leading slashes
		$url = ltrim($url, '/');

		// Add 'https://' as default protocol
		return 'https://' . $url;
	}

	/**
	 * Get the current screen object.
	 *
	 * The reason for this function is that the get_current_screen() function is not available in all contexts,
	 * and the properties of the current screen object are not always available.
	 * This function makes sure that accessing the properties of the current screen object is safe.
	 *
	 * @param string $screen The screen property to return.
	 * @return string
	 *
	 * @since 1.43.5
	 */
	public static function pmw_current_screen( $screen ) {

		if (!function_exists('get_current_screen')) {
			return '';
		}

		$current_screen = get_current_screen();

		if (!$current_screen) {
			return '';
		}

		if (!property_exists($current_screen, $screen)) {
			return '';
		}

		return $current_screen->$screen;
	}

	public static function is_url_accessible( $url ) {
		$response = wp_remote_get($url);

		if (is_wp_error($response)) {
			return false;
		}

		$response_code = wp_remote_retrieve_response_code($response);

		if (200 !== $response_code) {
			return false;
		}

		return true;
	}

	public static function get_pmw_distro() {
		return PMW_DISTRO;
	}

	public static function is_pmw_wcm_distro() {
		return 'wcm' === PMW_DISTRO;
	}

	public static function is_pmw_pro_version_active() {

		if (function_exists('wpm_fs')) {
			return wpm_fs()->can_use_premium_code__premium_only();
		}

		if ('woocommerce-pixel-manager/woocommerce-pixel-manager.php' === PMW_PLUGIN_BASENAME) {
			return true;
		}

		return false;
	}

	public static function is_pmw_pro_version_not_active() {
		return !self::is_pmw_pro_version_active();
	}

	public static function is_wcm_distro_free_version() {
		return self::is_pmw_wcm_distro() && self::is_pmw_pro_version_not_active();
	}

	/**
	 * Check if this is a free distribution of the plugin.
	 * Free distributions include:
	 * - WordPress.org free version (wgact.php)
	 * - WooCommerce Marketplace free version (woocommerce-pixel-manager-free.php)
	 * - Any version where Freemius SDK indicates no premium code access
	 *
	 * Note: The first check using Freemius SDK is necessary for local testing
	 * because file names are not correct before preparing the distribution build.
	 *
	 * @return bool True if it's a free distribution, false for pro distribution
	 * @since 1.51.0
	 */
	public static function is_free_plugin_distribution() {

		if (function_exists('wpm_fs') && wpm_fs()->is_premium()) {
			return false;
		}

		// Fallback: Check if it's the WordPress.org free version
		if (str_contains(PMW_PLUGIN_BASENAME, 'wgact.php')) {
			return true;
		}

		// Fallback: Check if it's the WooCommerce Marketplace free version
		if (str_contains(PMW_PLUGIN_BASENAME, 'woocommerce-pixel-manager-free/woocommerce-pixel-manager-free.php')) {
			return true;
		}

		// Otherwise it's a pro distribution
		return false;
	}

	/**
	 * Gets all plugin log files based on a specific source.
	 *
	 * This function searches all log files in the WooCommerce logs directory,
	 * selects those that start with the specified source,
	 * and returns their file paths.
	 *
	 * @param string $source The source log files to search for.
	 *
	 * @return array Array of log file paths.
	 *
	 * @since 1.36.1
	 */
	public static function get_all_plugin_log_file_paths( $source ) {

		// return empty array if the class WC_Log_Handler_File does not exist
		if (!class_exists('WC_Log_Handler_File')) {
			return [];
		}

		$logs = WC_Log_Handler_File::get_log_files();

		if (empty($logs)) {
			return [];
		}

		$logs = array_filter(
			$logs,
			function ( $key ) use ( $source ) {
				return strpos($key, $source . '-') === 0;
			},
			ARRAY_FILTER_USE_KEY
		);

		if (empty($logs)) {
			return [];
		}

		// Return full paths to the log files
		$log_dir = defined('WC_LOG_DIR') ? trailingslashit(WC_LOG_DIR) : '';
		if (empty($log_dir)) {
			return [];
		}

		$log_files = [];
		foreach ($logs as $log_filename) {
			$log_path = $log_dir . $log_filename;
			if (file_exists($log_path) && is_readable($log_path) && filesize($log_path) > 0) {
				$log_files[] = $log_path;
			}
		}

		return $log_files;
	}

	/**
	 * Check if the current page is a cart, checkout, or purchase confirmation page.
	 *
	 * @return bool True if on cart, checkout, or order-received page.
	 */
	public static function is_cart_or_checkout_page() {

		// WooCommerce functions may not be available yet
		if (!function_exists('is_cart') || !function_exists('is_checkout')) {
			return false;
		}

		// is_checkout() returns true for both checkout and order-received (thank you) page
		return is_cart() || is_checkout();
	}
}
