<?php
/**
 * Non-object helpers functions.
 *
 * @package StockSyncWithGoogleSheetForWooCommerce
 * @since   1.2.2
 */

use function Crontrol\Event\run;

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

/**
 * Sanitize array recursively.
 *
 * @param mixed $array The array to sanitize.
 * @return mixed
 */
function ssgsw_sanitize( $array ) { //phpcs:ignore
	foreach ( $array as $key => $value ) {
		if ( is_array($value) ) {
			$array[ $key ] = ssgsw_sanitize($value);
		} else {
			$array[ $key ] = sanitize_text_field($value);
		}
	}

	return $array;
}
/**
 * Retrieves and sanitizes JSON input from the request body.
 *
 * This function reads raw JSON data from the PHP input stream, decodes it into an array,
 * sanitizes the data using the `ssgsw_sanitize()` function, and returns it as an object.
 * If the input is not valid JSON or cannot be decoded, an empty object is returned.
 *
 * @since   1.0.0
 * @package SSGSW
 *
 * @return  object  Returns sanitized input data as an object, or an empty object if no valid input.
 *                  The returned object will have properties corresponding to the keys in the input JSON.
 *
 * @example
 * // Example JSON input: {"name":"John","age":30}
 * $data = ssgsw_get_body();
 * echo $data->name; // Outputs: John (sanitized)
 * echo $data->age;  // Outputs: 30
 *
 * @uses ssgsw_sanitize() To sanitize the decoded input data.
 */
function ssgsw_multistore_get_body_data() {
	$inputs = json_decode(file_get_contents('php://input'), true);

	if ( is_array($inputs) ) {
		return (object) ssgsw_sanitize($inputs);
	}

	return (object) [];
}
/**
 * Checks if the plugin is activated
 *
 * @return bool
 */
 function ssgsw_platinum_activated(){
	if ( ! function_exists('is_plugin_active') ) {
		include_once ABSPATH . 'wp-admin/includes/plugin.php';
	}
	// return true;
	// $plugin_uri = ;
	if (is_plugin_active('multivendor-addon-for-flexstock/multivendor-addon-for-flexstock.php') ) {
		return true;
	}
	return false;
}

/**
 * Checks if the current page is a Dokan seller dashboard.
 *
 * This function utilizes Dokan's built-in `dokan_is_seller_dashboard()` function
 * to determine if the user is currently viewing any part of the vendor dashboard.
 *
 * @since 1.0.0 // You should replace this with the actual version when this function was introduced
 *
 * @return bool Returns `false` if the current page is a Dokan seller dashboard,
 * otherwise returns `false`.
 * (Note: The current logic always returns `false`. This might need
 * to be adjusted based on the desired behavior if the intention
 * is to return `true` when it IS a seller dashboard).
 */
function ssgws_dokan_saller_page() {
	global $wp;
	$current_page = $wp->query_vars;
	$dokan_active_url = false;
	if ( isset( $current_page['flexstock']) || isset($current_page['products'])  ) {
		$dokan_active_url = true;
	}
	return $dokan_active_url;
}

/**
 * Retrieves and sanitizes JSON input from the request body.
 *
 * This function reads raw JSON data from the PHP input stream, decodes it into an array,
 * sanitizes the data using the `ssgsw_sanitize()` function, and returns it as an object.
 * If the input is not valid JSON or cannot be decoded, an empty object is returned.
 *
 * @since   1.0.0
 * @package SSGSW
 *
 * @return  object  Returns sanitized input data as an object, or an empty object if no valid input.
 *                  The returned object will have properties corresponding to the keys in the input JSON.
 *
 * @example
 * // Example JSON input: {"name":"John","age":30}
 * $data = ssgsw_get_body();
 * echo $data->name; // Outputs: John (sanitized)
 * echo $data->age;  // Outputs: 30
 *
 * @uses ssgsw_sanitize() To sanitize the decoded input data.
 */
function ssgsw_get_body() {

	global $ssgsw_request_params;
    if ( !empty( $ssgsw_request_params ) && is_array( $ssgsw_request_params ) ) {
        return (object) ssgsw_sanitize( $ssgsw_request_params );
    }

	$inputs = json_decode(file_get_contents('php://input'), true);

	if ( is_array($inputs) ) {
		return (object) ssgsw_sanitize($inputs);
	}

	return (object) [];
}

/**
 * Checks if the current request is an SSGSW API action.
 *
 * This function retrieves the request body and verifies if the 'ssgsw_api'
 * key exists, which indicates a valid API request from a connected sub-site.
 *
 * @return bool True if the request is a valid SSGSW API call, false otherwise.
 */
function ssgsw_check_action_domain() {
    $body = ssgsw_get_body();
    if($body) {
        if(isset($body->domain_url)) {
			if ($body->domain_url) {
				return true;
			}
        }
    }
    return false;
}
/**
 * Checks if the current request is an SSGSW API action.
 *
 * This function retrieves the request body and verifies if the 'ssgsw_api'
 * key exists, which indicates a valid API request from a connected sub-site.
 *
 * @return bool True if the request is a valid SSGSW API call, false otherwise.
 */
function ssgsw_check_action_api() {
    $body = ssgsw_get_body();
    if($body) {
        if(isset($body->ssgsw_api)) {
            return true;
        }
    }
    return false;
}

/**
 * Retrieves plugin options from an external API request body.
 *
 * This function is designed to be used in an API context. It gets the request body
 * and checks if the 'ssgsw_options' key exists. If so, it returns the value of that
 * key, which contains the options sent from a connected sub-site.
 *
 * @return mixed An array or object of options if found, otherwise an empty array.
 */
function ssgsw_get_external_options() {
    $body = ssgsw_get_body();
    if($body) {
        if(isset($body->ssgsw_colmun)) {
            return $body->ssgsw_colmun;
        }
    }
    return [];
}
/**
 * Retrieves filter values from an external API request body.
 *
 * This function is designed to be used in an API context. It gets the request body
 * and checks if the 'ssgsw_filters' key exists. If it does, the function
 * returns the array of filters sent from the requesting site.
 *
 * @return array An array of filter values if found, otherwise an empty array.
 */
function ssgsw_get_external_filters() {
    $body = ssgsw_get_body();
    if($body) {
        if(isset($body->ssgsw_filters)) {
            return $body->ssgsw_filters;
        }
    }
    return [];
}
/**
 * Determines if the current request is from an admin context or if the request body specifies admin status.
 *
 * This function first checks if the request body is empty. If empty, it falls back to WordPress's 
 * native `is_admin()` check. If the body contains data, it looks for an `is_admin` property in the 
 * request body to determine admin status.
 *
 * @since   1.0.0
 * @package SSGSW
 *
 * @return  bool  Returns true if the request is from an admin context or if the body specifies 
 *                admin status, false otherwise.
 *
 * @example
 * // Example 1: Empty request body falls back to is_admin()
 * $is_admin = ssgsw_is_admin();
 *
 * // Example 2: Request with JSON body: {"is_admin": true}
 * $is_admin = ssgsw_is_admin(); // Returns true
 *
 * @uses ssgsw_get_body() To retrieve and sanitize the request body.
 * @uses is_admin() WordPress function to check admin context when body is empty.
 *
 * @note The function has a typo in the variable name ($is_amdin) which should be corrected to $is_admin
 *       for better code readability and maintenance.
 */
function ssgsw_is_admin($body = []) {
	
    // Get the sanitized request body
    if (!$body) {
        $body = ssgsw_get_body();
    }
	
    // Initialize admin flag
    $is_admin = false;

    if (empty(get_object_vars($body))) {
		if (defined('DOING_CRON') && DOING_CRON) {
			return true; // or false depending on your use case
		}
		 // Check if running as cron
		
		// error_log(print_r('vendor',true));
		if(ssgws_dokan_saller_page()) {
			// error_log(print_r('vendor111',true));
			return false;
		}
        // If body is empty, use other methods.
        if (function_exists('wcfm_is_vendor') && wcfm_is_vendor(get_current_user_id())) {
            return false;
        }
		if ( isset( $_SERVER['HTTP_REFERER'] ) && ! empty( $_SERVER['HTTP_REFERER'] ) ) {
			$referer_sanitized = esc_url_raw(wp_unslash( $_SERVER['HTTP_REFERER'] ));
			// $referer_sanitized = esc_url_raw( $referer_unslashed );
			if ( strpos( $referer_sanitized, '/wp-admin/' ) !== false ) {
				
				return true;
			}
		} else {
			return false;
		}

        // if (is_admin() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) ) {
        //     $is_admin = true;
        // }
    } else {
        if (isset($body->vendor_request)) {
            $vendor_request = $body->vendor_request;
			if ($vendor_request) {
				$is_admin = false;
			} else {
				$is_admin = true;
			}
        } else {
			$is_admin = true;
		}
    }

    return $is_admin;
}

/**
 * Validates the synchronization mode and direction for a specific operation.
 *
 * This function checks if the current synchronization mode is 'live-sync' and
 * if the synchronization direction matches either 'bidirectional' or the
 * specified `$value`. It's used to determine if a particular sync action is
 * permitted based on the plugin's settings.
 *
 * @param string $value The specific synchronization direction to validate against.
 * Defaults to 'bidirectional'.
 * @return bool True if the synchronization mode is 'live-sync' and the direction
 * is either 'bidirectional' or matches the provided `$value`,
 * false otherwise.
 */
function ssgsw_sync_validation_mode($value = 'bidirectional') {
    $sync_mode = ssgsw_get_option('selected_sync_mode', 'live-sync');
    $sync_direction = ssgsw_get_option('live_sync_direction', 'bidirectional');
    if ($sync_mode == 'live-sync') {
		if (!ssgsw_is_license_valid()) {
			if ('sheets-to-woocommerce' === $sync_direction) {
				return true;
			}
		}
        if ('bidirectional' === $sync_direction || $sync_direction === $value) {
            return true;
        }
    }
    return false;
}

/**
 * Retrieves the current user ID from either a token in the request body or the WordPress session.
 *
 * This function attempts to get the user ID in two ways:
 * 1. First checks for a 'token_id' in the sanitized request body (for API/stateless authentication)
 * 2. Falls back to the current logged-in WordPress user ID (for traditional session-based authentication)
 *
 * @since x.x.x (replace with your version number if applicable)
 *
 * @return int|false Returns the user ID if found (either from token or session), 
 *                   or false if no user ID could be determined.
 */
function ssgsw_get_user_id($body = []) {
    // Get the sanitized request body
	if(!$body) {
		$body = ssgsw_get_body();
	}
    if(isset($body->token_id)) {
		return $body->token_id;
	}
	if(function_exists("get_current_user_id")) {
		return get_current_user_id();
	}
    return false;
}



/**
 * Vendor Page URL
 */
function ssgsw_vendor_page_url() {
	$wcfm_page_options = get_option("wcfm_page_options");
	// print_r($wcfm_page_options);
	$dashboard_page_id = !empty($wcfm_page_options['wc_frontend_manager_page_id']) ? $wcfm_page_options['wc_frontend_manager_page_id'] : 0;
	// Get the full page URL.
	$dashboard_page_url = get_permalink($dashboard_page_id);
	//new text.
	$vendor_page_url = $dashboard_page_url.'flexstock/';
	return $vendor_page_url;
}
/**
 * Get an option from the options table
 */
if ( ! function_exists('ssgsw_get_option') ) {
	/**
	 * Get an option from the options table
	 *
	 * @param  string $option_name The option name.
	 * @param  mixed  $default     The default value.
	 * @return mixed
	 */
	function ssgsw_get_option( $option_name, $default = false, $user_id = null) {
		$body = ssgsw_get_body();

		$is_admin = ssgsw_is_admin($body);
		
		if(!$user_id) {
			$user_id = ssgsw_get_user_id($body);
		}
	
		// $is_admin = false;
		$key = SSGSW_PREFIX . $option_name;
		if ($is_admin) {
			$value = get_option(SSGSW_PREFIX . $option_name);
			
		} else {

			$setting_information = ssgsw_get_informations($user_id);
			
			$value = isset($setting_information[$key]) ? $setting_information[$key] : $default;
			
			
		}
		if ( ! $value ) {
			return $default;
		}

		return apply_filters('ssgsw_get_' . $option_name, $value);
	}
}
/**
 * Get SSGSW product information
 *
 * @param int $product_id ID of product.
 *
 * @return mixed
 */
function ssgsw_get_informations_all( $user_id) {
	global $wpdb;
	$results = $wpdb->get_results(
		$wpdb->prepare(
			"SELECT * FROM {$wpdb->prefix}ssgsw_setting WHERE vender_id = %d",
			$user_id
		),
		ARRAY_A
	);
	if (is_array($results) && !empty($results)) {
		return $results[0];
	} else {
		return [];
	}
}
/**
 * Get SSGSW product information
 *
 * @param int $product_id ID of product.
 *
 * @return mixed
 */
function ssgsw_get_informations( $user_id) {
	$results = ssgsw_get_informations_all($user_id);
	if (is_array($results) && !empty($results)) {
		return json_decode($results['setting_option'], true);
	} else {
		return [];
	}
}
/**
 * Update an option in the options table
 */
if ( ! function_exists('ssgsw_update_option') ) {
	/**
	 * Update an option in the options table
	 *
	 * @param  string $option_name The option name.
	 * @param  mixed  $value       The value.
	 * @return bool
	 */
	function ssgsw_update_option( $option_name, $value ) {
		
		$value = apply_filters('ssgsw_update_' . $option_name, $value);

		do_action('ssgsw_before_update_' . $option_name, $value);
		$body = ssgsw_get_body();
		$is_admin = ssgsw_is_admin($body);

		
		$user_id = ssgsw_get_user_id($body);
		$key = SSGSW_PREFIX . $option_name;
		if($is_admin) {
			$updated = update_option(SSGSW_PREFIX . $option_name, $value);
		} else {
			$updated = ssgsw_update_vendor_setting($key, $value, $user_id);
		}
		if ( $updated ) {
			do_action('ssgsw_updated_' . $option_name, $value);
		}
		return $updated;
	}
}


/**
 * Get the Flexstock URL for the active vendor plugin (Dokan or WCFM).
 *
 * This function checks for the active vendor plugin and constructs the
 * appropriate URL for the 'flexstock' endpoint.
 *
 * @return string The constructed URL for the Flexstock page, or an empty string if no supported plugin is active.
 */
function get_vendor_flexstock_url($name = 'products') {
    $flexstock_url = '';

    // Check if Dokan plugin is active
    if ( function_exists( 'dokan_get_navigation_url' ) ) {
        // Use Dokan's native function to get the navigation URL
        $flexstock_url = dokan_get_navigation_url( $name );
    } 
    // If Dokan is not active, check for WCFM
    else if ( class_exists( 'WCFM' ) && function_exists( 'wcfm_get_page_id' ) ) {
		$wcfm_page_options = get_option("wcfm_page_options");
		// print_r($wcfm_page_options);
		$dashboard_page_id = !empty($wcfm_page_options['wc_frontend_manager_page_id']) ? $wcfm_page_options['wc_frontend_manager_page_id'] : 0;
		// Get the full page URL.
		$dashboard_page_url = get_permalink($dashboard_page_id);
		//new text.
		$vendor_page_url = $dashboard_page_url.$name;
		return $vendor_page_url;
    }

    return $flexstock_url;
}
/**
 * Update an option in the vendor table
 */
if ( ! function_exists('ssgsw_update_vendor_setting') ) {
	/**
	 * Update an option in the vendor table
	 *
	 * @param  string $option_name The option name.
	 * @param  mixed  $value       The value.
	 * @return bool
	 */
	function ssgsw_update_vendor_setting( $key, $value, $user_id) {
		global $wpdb;
		$table_name = $wpdb->prefix . 'ssgsw_setting';
		$old_data  = ssgsw_get_informations($user_id);
		
		$new_data = $old_data;
		$new_data[$key] = $value;
		if (empty($old_data)) {
			$insert_data = [
				'vender_id' => $user_id,
				'global_setting' => 0,
				'product_sync' => 0,
				'setting_option' => wp_json_encode($new_data),
			];
			return $wpdb->insert($table_name, $insert_data);
		} else {
			$update_data = [
				'global_setting' => 0,
				'setting_option' => wp_json_encode($new_data),
				'date' => gmdate('Y-m-d H:i:s'),
			];
			$where_condition = [ 'vender_id' => $user_id ];
			return $wpdb->update($table_name, $update_data, $where_condition);
		}
	}
}
/**
 * Determines if the current user has vendor capabilities based on the active multivendor plugin.
 *
 * This function checks if the current user has vendor capabilities by detecting which multivendor
 * plugin is active (Dokan or WCFM) and then verifying the user's role accordingly.
 *
 * - If Dokan is active, it checks for the `dokandar` capability.
 * - If WCFM is active, it checks for the `wcfm_vendor` capability.
 *
 * @since 1.0.0
 *
 * @return bool Returns `true` if the current user has the required vendor capability, `false` otherwise.
 */
function flexstock_can_vendor() {
	$capability = '';

	if ( function_exists( 'dokan' ) ) {
		$capability = 'dokandar';
	}

	if ( class_exists( 'WCFMmp' ) ) {
		$capability = 'wcfm_vendor';
	}
	if ( current_user_can( 'administrator' ) ) {
		return true;
	}
	return current_user_can( $capability );
}

add_action('wp_head', function(){
	$flex_image = SSGSW_PUBLIC . 'images/multi-dashboard.svg';
	echo '<style>

	.dokan-dashboard-wrap {
    max-width: 100% !important; /* Override any max-width */
    width: 100% !important; /* Ensure it takes full width */
    padding-left: 0 !important; /* Remove left padding if any */
    padding-right: 0 !important; /* Remove right padding if any */
}

/* If Dokan content is inside another theme container (e.g., site-content or primary) */
.dokan-dashboard-wrap .dokan-container,
.site-content .dokan-dashboard-wrap, /* Adjust if your theme uses .site-content */
#primary .dokan-dashboard-wrap { /* Adjust if your theme uses #primary */
    max-width: 100% !important;
    width: 100% !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
}

body.dokan-dashboard-page .site-container, /* Example for specific body class and container */
body.dokan-dashboard-page #page {
    max-width: 100% !important;
    padding: 0 !important;
    margin: 0 !important;
}
		.flexstock_badge {
			color: #065f46;
			background: #D1FAE5;
			padding-left: 5px;
			padding-right: 5px;
			font-size: 13px;
			border-radius: 10px;
			font-weight: 600;
			margin-left:5px;
		}
		.fa-custom-flexstock-icon {
			background-image: url(' . esc_url($flex_image) . ');
			background-size: 20px 20px;
			background-repeat: no-repeat;
			background-position: center;
			width: 20px !important;
			height: 20px !important;
			display: inline-block;
			vertical-align: middle;
			margin-bottom:2px;
		}
		.flex-button {
			display:contents;
		}
		.ssgs-welcome__right {
			width: 100%;
		}
		#wcfm_menu .wcfm_menu_item span {
			display: inline-block;
			margin-right: 0px !important;
		}
		.ssgsw-wrapper .profile-section {
			margin-top: 0px !important;
		}
		.ssgsw-wrapper .remove-btn {
			padding: 15px !important;
		}
		.ssgsw-wrapper .filter-row {

			align-items: center !important;
			gap: 12px !important;
			margin-bottom: 10px !important;
		}
		#wcfm-main-contentainer textarea {
			border: none !important;
			-moz-border-radius: none !important;
			-webkit-border-radius: 0px !important;
			border-radius: none !important;
				resize: none !important;
				box-shadow: none !important;
		}

	</style>';
});


/**
 * Loads template file.
 *
 * @param string $template The template file name.
 * @param array  $data     The data to pass to the template.
 * @return void
 */
function flexstock_load_template( $template, $data = [] ) { // phpcs:ignore
	if (!is_array($data)) {
		$data = [];
	}
	if ( file_exists( SSGSW_TEMPLATES . $template . '.php' ) ) {
		include SSGSW_TEMPLATES . $template . '.php';
	}
}

/**
 * Helper function to count products based on additional query conditions.
 *
 * @param string $main_table The main table to run the count query on (default: wp_posts).
 * @param string $additional_conditions The additional conditions to apply to the query (optional).
 *
 * @return int The count of products matching the conditions.
 */

function ssgsw_get_product_count( $main_table, $additional_conditions = '' ) {
    global $wpdb;
    
    // Build query with additional conditions (must be already prepared)
    $query = "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = %s $additional_conditions";
    
    // @phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
    // @phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
    // @phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
    $result = $wpdb->get_var(
        $wpdb->prepare(
            $query,
            'product'
        )
    );
    // @phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
    // @phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery
    // @phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching
    
    return (int) $result;
}
/**
 * Retrieves the product count for the current site and any connected sub-sites.
 *
 * This function builds an initial array with the current site's product count.
 * It then uses the `ssgsw_sub_site_product_count` filter to allow other
 * plugins to add their own site data, including product counts from
 * connected sub-sites.
 *
 * @return array An array of site data, where each element is an array
 * containing the 'url', 'token', and 'product' count.
 */
function ssgsw_get_all_site_count_product($vendor_request = false) {
	$main_url = home_url();
	$product_count = ssgsw_count_all_product();
	$main_array = [
		[
			'url' => $main_url,
			'token' => get_option('ssgsw_token'),
			'product' => intval($product_count),
		]
	];
	if(!$vendor_request) {
		$main_array = apply_filters('ssgsw_sub_site_product_count', $main_array);
	}
	$sheet = new StockSyncWithGoogleSheetForWooCommerce\Sheet();
	$access_token = $sheet->get_token();
	/**
	 * The Spreadsheet
	 */
	$spreadsheet_url = ssgsw_get_option('spreadsheet_url', false);
	$spreadsheet_id = ssgsw_get_option('spreadsheet_id', false);
	$sheet_tab =      ssgsw_get_option('sheet_tab', false);
	$sheet_id =      ssgsw_get_option('sheet_id', false);

	if ( ! $spreadsheet_id ) {
		// Parse Spreadsheet ID from URL using regex.
		if (!empty($spreadsheet_url) && is_string($spreadsheet_url)) {
			preg_match('/spreadsheets\/d\/([a-zA-Z0-9-_]+)/', $spreadsheet_url, $matches);
			$spreadsheet_id = $matches[1] ?? null;
		} else {
			$spreadsheet_id = null;
		}
	}
	$column_object = new \StockSyncWithGoogleSheetForWooCommerce\Column();
	$column = $column_object->get_columns();
    $filters = ssgsw_formating_the_filter_value();
	$new_values = [
		'access_token' => $access_token,
		'spreadsheet_id' => $spreadsheet_id,
		'sheet_tab' => $sheet_tab,
		'ssgsw_filters' => $filters,
        'ssgsw_colmun' => $column,
		'sheet_id' => $sheet_id,
        'ssgsw_api' => true,
	];
	$add_mode = apply_filters('ssgsw_sync_mode', 'multi_to_single');
	$new_values['sync_mode'] = $add_mode;
	foreach ($main_array as &$item) {
		// Merge the new values into the current item
		// The `+` operator or `array_merge` can be used
		$item = $item + $new_values;
		$url_parts = wp_parse_url($item['url']);
        if ($url_parts && isset($url_parts['host'])) {
            $item['domain'] = $url_parts['host'];
        } else {
            $item['domain'] = null; // Or some other default value
        }
	}
	return $main_array;
}

/**
 * Retrieves the total count of products from the current site and all
 * connected sub-sites.
 *
 * This function first calls `ssgsw_get_all_site_count_product()` to get an
 * array of product counts for each site. It then iterates through this array
 * to calculate and return the grand total of all syncable products.
 *
 * @return int The total number of products from all connected sites.
 */
function ssgsw_get_total_syncable_products($all_site_counts) {
    // Initialize a variable to store the total product count.
    $total_products = 0;
    
    // Loop through each site's data and add its product count to the total.
    foreach ($all_site_counts as $site_data) {
        // Ensure the 'product' key exists and its value is a number before adding.
        if (isset($site_data['product']) && is_numeric($site_data['product'])) {
            $total_products += $site_data['product'];
        }
    }
    
    // Return the final sum of all products.
    return $total_products;
}
/**
 * Counts all published products in the WordPress database with optional filters.
 *
 * @return int The count of published products.
 */
function ssgsw_count_all_product() {
    global $wpdb;

    // Get the filter information
	if (ssgsw_check_action_api()) {
		$filter_information = ssgsw_get_external_filters();

	} else {
 		$filter_information = ssgsw_formating_the_filter_value();
	}
   

    $product_type_information = isset($filter_information['post_type']) ? $filter_information['post_type'] : [];
    $status_information = isset($filter_information['post_status']) ? $filter_information['post_status'] : [];
    $product_category = isset($filter_information['category']) ? $filter_information['category'] : [];
	$product_name = isset($filter_information['product_name']) ? $filter_information['product_name'] : [];
    // Initialize the additional conditions string
    $additional_conditions = '';
	if (is_array($product_name) && !empty($product_name)) {
		$equals = isset($product_name['equals']) ? $product_name['equals'] : [];
		$not_equals = isset($product_name['not_equals']) ? $product_name['not_equals'] : [];
	
		// Handle titles that should partially match the provided values (equals)
		if (!empty($equals)) {
			$additional_conditions .= " AND (";
			$equals_conditions = [];
			foreach ($equals as $equal) {
				$equal = sanitize_text_field($equal); // Sanitize input for security
				$equals_conditions[] = $wpdb->prepare(
					"post_title LIKE %s",
					'%' . $wpdb->esc_like($equal) . '%'
				);
			}
			$additional_conditions .= implode(' OR ', $equals_conditions) . ")";
		}
	
		// Handle titles that should NOT partially match the provided values (not equals)
		if (!empty($not_equals)) {
			$additional_conditions .= " AND (";
			$not_equals_conditions = [];
			foreach ($not_equals as $not_equal) {
				$not_equal = sanitize_text_field($not_equal); // Sanitize input for security
				$not_equals_conditions[] = $wpdb->prepare(
					"post_title NOT LIKE %s",
					'%' . $wpdb->esc_like($not_equal) . '%'
				);
			}
			$additional_conditions .= implode(' AND ', $not_equals_conditions) . ")";
		}
	}
	
    // Handle status filtering
    if (is_array($status_information) && !empty($status_information)) {
        $status_equals = isset($status_information['equals']) ? $status_information['equals'] : [];
        $status_not_equals = isset($status_information['not_equals']) ? $status_information['not_equals'] : [];

        // Handle statuses that should be equal to the provided values
        if (!empty($status_equals)) {
            $status_equal_placeholders = implode(',', array_fill(0, count($status_equals), '%s'));
            $additional_conditions .= $wpdb->prepare(
                " AND post_status IN ($status_equal_placeholders)",
                ...$status_equals
            );
        }

        // Handle statuses that should NOT be equal to the provided values
        if (!empty($status_not_equals)) {
            $status_not_equal_placeholders = implode(',', array_fill(0, count($status_not_equals), '%s'));
            $additional_conditions .= $wpdb->prepare(
                " AND post_status NOT IN ($status_not_equal_placeholders)",
                ...$status_not_equals
            );
        }
    }

    // Build product type conditions if present
    if (!empty($product_type_information)) {
        $type_equals = isset($product_type_information['equals']) ? $product_type_information['equals'] : [];
        $type_not_equals = isset($product_type_information['not_equals']) ? $product_type_information['not_equals'] : [];

        if (!empty($type_equals)) {
            $product_type_placeholders = implode(',', array_fill(0, count($type_equals), '%d'));
            $additional_conditions .= $wpdb->prepare(
                " AND ID IN (
                    SELECT object_id FROM $wpdb->term_relationships 
                    WHERE term_taxonomy_id IN ($product_type_placeholders)
                )",
                ...$type_equals
            );
        }

        if (!empty($type_not_equals)) {
            $product_type_exclude_placeholders = implode(',', array_fill(0, count($type_not_equals), '%d'));
            $additional_conditions .= $wpdb->prepare(
                " AND ID NOT IN (
                    SELECT object_id FROM $wpdb->term_relationships 
                    WHERE term_taxonomy_id IN ($product_type_exclude_placeholders)
                )",
                ...$type_not_equals
            );
        }
    }

    // Build category conditions if present
    if (is_array($product_category) && !empty($product_category)) {
        $cat_equals = isset($product_category['equals']) ? $product_category['equals'] : [];
        $cat_not_equals = isset($product_category['not_equals']) ? $product_category['not_equals'] : [];

        if (is_array($cat_equals) && !empty($cat_equals)) {
            $cat_equal = implode(',', array_fill(0, count($cat_equals), '%d'));
            $additional_conditions .= $wpdb->prepare(
                " AND ID IN (
                    SELECT object_id FROM $wpdb->term_relationships 
                    WHERE term_taxonomy_id IN ($cat_equal)
                )",
                ...$cat_equals
            );
        }

        if (is_array($cat_not_equals) && !empty($cat_not_equals)) {
            $cat_equal_not = implode(',', array_fill(0, count($cat_not_equals), '%d'));
            $additional_conditions .= $wpdb->prepare(
                " AND ID NOT IN (
                    SELECT object_id FROM $wpdb->term_relationships 
                    WHERE term_taxonomy_id IN ($cat_equal_not)
                )",
                ...$cat_not_equals
            );
        }
    }
	if(!ssgsw_is_admin()) {
		$main_table = $wpdb->prefix . 'posts';
		$current_user_id = ssgsw_get_user_id();
		$additional_conditions .= " AND $main_table.post_author = $current_user_id";
	} else {
		$new_query = apply_filters( 'ssgsw_sku_checker_filter', false );
		if ($new_query) {
			$additional_conditions .= " AND ID IN (SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_sku' AND meta_value != '')";
		}
	}
    // Get the product count using the helper function
    $product_count = ssgsw_get_product_count($wpdb->posts, $additional_conditions);
    // Optionally apply the limit
    if ($product_count > 500) {
        $limit = apply_filters('ssgsw_product_limit', 500);
        if ($limit) {
            return $limit;
        }
    }

    return $product_count;
}



/**
 * Get the app instance
 */
if ( ! function_exists('ssgsw') ) {
	/**
	 * Get the app instance
	 *
	 * @return \StockSyncWithGoogleSheetForWooCommerce\App
	 */
	function ssgsw() {
		return new \StockSyncWithGoogleSheetForWooCommerce\App();
	}
}
// Function to check if ATUM Inventory Management for WooCommerce plugin is active
function ssgsw_is_atum_plugin_active() {
	// Check if the function exists
	if ( ! function_exists('is_plugin_active') ) {
		include_once ABSPATH . 'wp-admin/includes/plugin.php';
	}

	// Check if ATUM Inventory Management for WooCommerce plugin is active
	if ( is_plugin_active('atum-stock-manager-for-woocommerce/atum-stock-manager-for-woocommerce.php') ) {
		return true;
	} else {
		return false;
	}
}
/**
 * Function to check if a table exists in the WordPress database
 */
function ssgsw_atum_table_exists( $table_name ) {
	global $wpdb;
	$table_exists = $wpdb->get_var($wpdb->prepare(
		'SHOW TABLES LIKE %s',
		$table_name
	));
	return $table_exists === $table_name;
}

/**
 * Function to update or insert ATUM price
 */
function ssgsw_insert_atum_table( $product_id = '11' ) {
	global $wpdb;
	if ( ssgsw_atum_table_exists($wpdb->prefix . 'atum_product_data') ) {
		$exists = $wpdb->get_var($wpdb->prepare(
			"SELECT COUNT(*) FROM {$wpdb->prefix}atum_product_data WHERE product_id = %d",
			$product_id
		));
		if (0 == $exists) { //phpcs:ignore
			 $wpdb->query($wpdb->prepare(
				"INSERT INTO {$wpdb->prefix}atum_product_data (product_id) VALUES (%d)",
				$product_id
			));
		}
	}
}

/**
 * Function update atum price
 */
function ssgsw_update_atum_price( $product_id = '539', $key = '', $new_price = '12' ) {
	if ( is_serialized($new_price) ) {
		$new_price = maybe_unserialize($new_price);
	}
	global $wpdb;
	if ( ssgsw_atum_table_exists($wpdb->prefix . 'atum_product_data') ) {
		$new_key = ssgsw_find_atum_key_from_table($key);
		if ( $new_key ) {
			$wpdb->update( $wpdb->prefix . 'atum_product_data',
				array( $new_key => $new_price ),
				array( 'product_id' => $product_id ),
				array( '%f' ),
				array( '%d' )
			);
		}
	}
}
/**
 * Retrive ssgsw atum prorduct columns information.
 */
function ssgsw_atum_table_column_name() {
	global $wpdb;
	$columns = [];
	if ( ssgsw_atum_table_exists($wpdb->prefix . 'atum_product_data') ) {
		$results = $wpdb->get_results("DESCRIBE {$wpdb->prefix}atum_product_data");
		foreach ( $results as $row ) {
			if ( $row->Field != 'product_id') { //phpcs:ignore
				$columns[ $row->Field . '_from_atum_table' ] = $row->Field . '_from_atum_table';
			}
		}
	}
	return $columns;
}



/**
 * Get all custom meta fields data
 *
 * @return array
 */
if ( ! function_exists( 'ssgsw_get_product_custom_fields' ) ) {
	/**
	 *  Get custom meta fields data.
	 *
	 * @return array
	 */
	function ssgsw_get_product_custom_fields() {
		global $wpdb;

		// Replace 'wp_posts' with your actual posts table name if it's different.
		$postmeta_table = $wpdb->prefix . 'postmeta';

		// Replace 'product' with your actual post type for products.
		$query = $wpdb->prepare("SELECT DISTINCT meta_key, meta_value FROM $postmeta_table INNER JOIN $wpdb->posts ON $wpdb->posts.ID = $postmeta_table.post_id WHERE $wpdb->posts.post_type IN (%s, %s)", 'product', 'product_variation' );// phpcs:ignore
		$results = $wpdb->get_results($query);// phpcs:ignore

		$custom_fields = [];

		foreach ( $results as $result ) {
			$custom_fields[ $result->meta_key ] = $result->meta_key;
		}
		$atum_columns = [];
		if ( ssgsw_is_atum_plugin_active() ) {
			$atum_columns = ssgsw_atum_table_column_name();
		}
		$keys_to_unset = [
			'total_sales',
			'_sale_price',
			'_regular_price',
			'_sku',
			'_stock',
			'_stock_status',
			'_product_attributes',
			'_price',
			'_manage_stock',
			'product_image',
			'product_type',
			'_product_default_quantity',
			'_sold_individually',
			'chosen_product_cat',
			'custom_prefix_for_price',
			'custom_suffix_for_price',
			'Status',
		];
		foreach ( $keys_to_unset as $key ) {
			if ( isset($custom_fields[ $key ]) ) {
				unset($custom_fields[ $key ]);
			}
		}
		$new_custom = $custom_fields + $atum_columns;

		return $new_custom;
	}
}
// ssgsw_get_product_custom_fields();
/**
 * Save meta fields value
 *
 * @param  int   $id The option id.
 * @param  mixed $meta_key       The meta key.
 * @param  mixed $value       The meta value.
 *
 * @return void
 */
function ssgsw_meta_field_value_save( $id, $meta_key, $value ) {
    // Ensure $id is valid
    if ( empty( $id ) || ! is_numeric( $id ) ) {
        return false;
    }

    // Check if value is serialized
    $serialized_check = false;
    if ( is_serialized( $value ) ) {
        $value = maybe_unserialize( $value );
        $serialized_check = true;
    }
    if ( $serialized_check && empty( $value ) ) {
        return false;
    }

	// Validation for Date format
    $date_keywords = [ 'date', 'time', '_at', 'start', 'end', 'dob', 'expiry', 'expires' ];
    $is_date_field = false;
    foreach ( $date_keywords as $keyword ) {
        if ( strpos( strtolower( $meta_key ), $keyword ) !== false ) {
            $is_date_field = true;
            break;
        }
    }

	if ( $is_date_field && ! empty( $value ) && is_string( $value ) ) {
        $date = trim( $value );
        $parsed_date = date_create( $date );
		
        if ( $parsed_date !== false ) {
           $utc_from_sheet = new DateTime( $date, new DateTimeZone('UTC'));
		   $wp_offset = get_option('gmt_offset');
		   $timestamp = ssgsw_fix_utc_with_offset($utc_from_sheet, $wp_offset);
		   $value = $timestamp;
        }
    }

    // 🔹 ACF field handling safely
    if ( function_exists( 'acf_get_field' ) ) {
        $field_object = acf_get_field( $meta_key );
        if ( is_array( $field_object ) && ! empty( $field_object ) ) {
            if ( array_key_exists( 'choices', $field_object ) && is_array( $field_object['choices'] ) ) {
                $choices = $field_object['choices'];
                if ( array_key_exists( $value, $choices ) ) {
                    update_post_meta( $id, $meta_key, $value );
                }
            } else {
                update_post_meta( $id, $meta_key, $value );
            }
        } else {
            update_post_meta( $id, $meta_key, $value );
        }
    } else {
        update_post_meta( $id, $meta_key, $value );
    }

    return true;
}

/**
 * Fix UTC date by adding an integer offset from WordPress.
 *
 * @param DateTime $utc_date A UTC DateTime object.
 * @param int $offset The integer offset from UTC (e.g., 6 for UTC+6).
 * @return int Corrected Unix timestamp.
 */
function ssgsw_fix_utc_with_offset(DateTime $utc_date, $offset) {
    $formatted_offset = sprintf('%+d hours', $offset);
    $utc_date->modify($formatted_offset);
    return $utc_date->getTimestamp(); // strtotime() equivalent
}

/**
 * Check acf fields type
 *
 * @param string $meta_key acf meta key.
 * @return string
 */
function check_ssgsw_file_type( $meta_key ) {
	$all_acf_type = ssgsw_all_type_field_in_acf();
	if ( function_exists('get_field') ) {
		$field_object = acf_get_field($meta_key);
		if ( is_array($field_object) && ! empty($field_object) ) {
			if ( array_key_exists('type', $field_object) ) {
				if ( 'select' === $field_object['type'] ) {
					if ( 1 === $field_object['multiple'] ) {
						return 'not_suported';
					}
				} else if ( in_array($field_object['type'], $all_acf_type) ) {
					return 'not_suported';
				}
			}
		}
	}
	return 'suported';
}


/**
 * Sql reserved keyword check
 *
 * @param string $key key.
 * @return string
 */
function ssgsw_reserved_keyword( $key ) {
	$reserved_keywords = [
		'ADD',
		'ALL',
		'ALTER',
		'AND',
		'AS',
		'ASC',
		'BETWEEN',
		'BY',
		'CHAR',
		'CHECK',
		'COLUMN',
		'CONNECT',
		'CREATE',
		'CURRENT',
		'DECIMAL',
		'DEFAULT',
		'DELETE',
		'DESC',
		'DISTINCT',
		'DROP',
		'ELSE',
		'EXCLUSIVE',
		'EXISTS',
		'FILE',
		'FLOAT',
		'FOR',
		'FROM',
		'GRANT',
		'GROUP',
		'HAVING',
		'IDENTIFIED',
		'IMMEDIATE',
		'IN',
		'INCREMENT',
		'INDEX',
		'INITIAL',
		'INSERT',
		'INTEGER',
		'INTERSECT',
		'INTO',
		'IS',
		'JOIN',
		'LIKE',
		'LOCK',
		'LONG',
		'MAXEXTENTS',
		'MINUS',
		'MLSLABEL',
		'MODE',
		'MODIFY',
		'NOAUDIT',
		'NOCOMPRESS',
		'NOT',
		'NOWAIT',
		'NULL',
		'NUMBER',
		'OF',
		'OFFLINE',
		'ON',
		'ONLINE',
		'OPTION',
		'OR',
		'ORDER',
		'PCTFREE',
		'PRIOR',
		'PRIVILEGES',
		'PUBLIC',
		'RAW',
		'RENAME',
		'RESOURCE',
		'REVOKE',
		'ROW',
		'ROWID',
		'ROWNUM',
		'ROWS',
		'SELECT',
		'SESSION',
		'SET',
		'SMALLINT',
		'START',
		'SYNONYM',
		'SYSDATE',
		'TABLE',
		'THEN',
		'TO',
		'TRIGGER',
		'UID',
		'UNION',
		'UNIQUE',
		'UPDATE',
		'USER',
		'VALIDATE',
		'VALUES',
		'VARCHAR',
		'VIEW',
		'WHENEVER',
		'WHERE',
		'WITH',
		'RANGE',
		'LIMIT',
		'GROUP BY',
		'ORDER BY',
		'WHERE',
		'FROM',
	];
	$value_lower = strtolower($key);
	$keywords_lower = array_map('strtolower', $reserved_keywords);
	if ( in_array($value_lower, $keywords_lower) ) {
		return 'yes';
	} else {
		return 'no';
	}
}
/**
 * Collection of all type fields in acf.
 *
 * @return array
 */
function ssgsw_all_type_field_in_acf() {
	$field_types = array(
		'wysiwyg',
		'image',
		'file',
		'checkbox',
		'post_object',
		'page_link',
		'relationship',
		'taxonomy',
		'user',
		'google_map',
		'date_picker',
		'date_time_picker',
		'time_picker',
		'message',
		'tab',
		'group',
		'repeater',
		'flexible_content',
		'clone',
		'accordion',
		'gallery',
		'block',
		'nav_menu',
		'post_taxonomy',
		'sidebar',
		'widget_area',
		'user_role',
		'true_false',
		'link',
	);
	return $field_types;
}
/**
 * Find out atum table information.
 *
 * @param string $string value of the field.
 *
 * @return array
 */
function ssgsw_check_atum_key_exits( $string ) {
	$substring = '_from_atum_table';
	if ( strpos($string, $substring) !== false ) {
		return true;
	} else {
		return false;
	}
}
function ssgsw_find_atum_key_from_table( $string ) {
	$substring = '_from_atum_table';
	$parts = explode($substring, $string);
	if ( isset($parts[0]) && ! empty($parts[0]) ) {
		return $parts[0];
	}
	return false;
}
/**
 * Check if license is activated and valid.
 *
 * @return bool
 */
function ssgsw_is_license_valid() {
	global $ssgsw_license;

	if ( ! $ssgsw_license ) {
		return false; 
	}

	return $ssgsw_license->is_valid();
}


/**
 * Insert product
 *
 * @param array   $postarr post information.
 * @param boolean $wp_error error code.
 * @param boolean $fire_after_hooks error code.
 */
function ssgsw_wp_insert_post( $postarr, $wp_error = false, $fire_after_hooks = true ) {
	global $wpdb;

	// Capture original pre-sanitized array for passing into filters.
	$unsanitized_postarr = $postarr;

	$user_id = ssgsw_get_user_id();

	$defaults = array(
		'post_author'           => $user_id,
		'post_content'          => '',
		'post_content_filtered' => '',
		'post_title'            => '',
		'post_excerpt'          => '',
		'post_status'           => 'draft',
		'post_type'             => 'post',
		'comment_status'        => '',
		'ping_status'           => '',
		'post_password'         => '',
		'to_ping'               => '',
		'pinged'                => '',
		'post_parent'           => 0,
		'menu_order'            => 0,
		'guid'                  => '',
		'import_id'             => 0,
		'context'               => '',
		'post_date'             => '',
		'post_date_gmt'         => '',
	);

	$postarr = wp_parse_args( $postarr, $defaults );

	unset( $postarr['filter'] );

	$postarr = sanitize_post( $postarr, 'db' );

	// Are we updating or creating?
	$post_id = 0;
	$update  = false;
	$guid    = $postarr['guid'];

	if ( ! empty( $postarr['ID'] ) ) {
		$update = true;

		// Get the post ID and GUID.
		$post_id     = $postarr['ID'];
		$post_before = get_post( $post_id );

		if ( is_null( $post_before ) ) {
			if ( $wp_error ) {
				return new WP_Error( 'invalid_post', __( 'Invalid post ID.','stock-sync-with-google-sheet-for-woocommerce' ) );
			}
			return 0;
		}

		$guid            = get_post_field( 'guid', $post_id );
		$previous_status = get_post_field( 'post_status', $post_id );
	} else {
		$previous_status = 'new';
		$post_before     = null;
	}

	$post_type = empty( $postarr['post_type'] ) ? 'post' : $postarr['post_type'];

	$post_title   = $postarr['post_title'];
	$post_content = $postarr['post_content'];
	$post_excerpt = $postarr['post_excerpt'];

	if ( isset( $postarr['post_name'] ) ) {
		$post_name = $postarr['post_name'];
	} elseif ( $update ) {
		// For an update, don't modify the post_name if it wasn't supplied as an argument.
		$post_name = $post_before->post_name;
	}

	$maybe_empty = 'attachment' !== $post_type
		&& ! $post_content && ! $post_title && ! $post_excerpt
		&& post_type_supports( $post_type, 'editor' )
		&& post_type_supports( $post_type, 'title' )
		&& post_type_supports( $post_type, 'excerpt' );

	/**
	 * Filters whether the post should be considered "empty".
	 *
	 * The post is considered "empty" if both:
	 * 1. The post type supports the title, editor, and excerpt fields
	 * 2. The title, editor, and excerpt fields are all empty
	 *
	 * Returning a truthy value from the filter will effectively short-circuit
	 * the new post being inserted and return 0. If $wp_error is true, a WP_Error
	 * will be returned instead.
	 *
	 * @since 3.3.0
	 *
	 * @param bool  $maybe_empty Whether the post should be considered "empty".
	 * @param array $postarr     Array of post data.
	 */
	if ( apply_filters( 'wp_insert_post_empty_content', $maybe_empty, $postarr ) ) {
		if ( $wp_error ) {
			return new WP_Error( 'empty_content', __( 'Content, title, and excerpt are empty.','stock-sync-with-google-sheet-for-woocommerce' ) );
		} else {
			return 0;
		}
	}

	$post_status = empty( $postarr['post_status'] ) ? 'draft' : $postarr['post_status'];

	if ( 'attachment' === $post_type && ! in_array( $post_status, array( 'inherit', 'private', 'trash', 'auto-draft' ), true ) ) {
		$post_status = 'inherit';
	}

	if ( ! empty( $postarr['post_category'] ) ) {
		// Filter out empty terms.
		$post_category = array_filter( $postarr['post_category'] );
	} elseif ( $update && ! isset( $postarr['post_category'] ) ) {
		$post_category = $post_before->post_category;
	}

	// Make sure we set a valid category.
	if ( empty( $post_category ) || 0 === count( $post_category ) || ! is_array( $post_category ) ) {
		// 'post' requires at least one category.
		if ( 'post' === $post_type && 'auto-draft' !== $post_status ) {
			$post_category = array( get_option( 'default_category' ) );
		} else {
			$post_category = array();
		}
	}

	/*
	 * Don't allow contributors to set the post slug for pending review posts.
	 *
	 * For new posts check the primitive capability, for updates check the meta capability.
	 */
	if ( 'pending' === $post_status ) {
		$post_type_object = get_post_type_object( $post_type );

		if ( ! $update && $post_type_object && ! current_user_can( $post_type_object->cap->publish_posts ) ) {
			$post_name = '';
		} elseif ( $update && ! current_user_can( 'publish_post', $post_id ) ) {
			$post_name = '';
		}
	}

	/*
	 * Create a valid post name. Drafts and pending posts are allowed to have
	 * an empty post name.
	 */
	if ( empty( $post_name ) ) {
		if ( ! in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ), true ) ) {
			$post_name = sanitize_title( $post_title );
		} else {
			$post_name = '';
		}
	} else {
		// On updates, we need to check to see if it's using the old, fixed sanitization context.
		$check_name = sanitize_title( $post_name, '', 'old-save' );

		if ( $update
			&& strtolower( urlencode( $post_name ) ) === $check_name
			&& get_post_field( 'post_name', $post_id ) === $check_name
		) {
			$post_name = $check_name;
		} else { // New post, or slug has changed.
			$post_name = sanitize_title( $post_name );
		}
	}

	/*
	 * Resolve the post date from any provided post date or post date GMT strings;
	 * if none are provided, the date will be set to now.
	 */
	$post_date = wp_resolve_post_date( $postarr['post_date'], $postarr['post_date_gmt'] );

	if ( ! $post_date ) {
		if ( $wp_error ) {
			return new WP_Error( 'invalid_date', __( 'Invalid date.','stock-sync-with-google-sheet-for-woocommerce' ) );
		} else {
			return 0;
		}
	}

	if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' === $postarr['post_date_gmt'] ) {
		if ( ! in_array( $post_status, get_post_stati( array( 'date_floating' => true ) ), true ) ) {
			$post_date_gmt = get_gmt_from_date( $post_date );
		} else {
			$post_date_gmt = '0000-00-00 00:00:00';
		}
	} else {
		$post_date_gmt = $postarr['post_date_gmt'];
	}

	if ( $update || '0000-00-00 00:00:00' === $post_date ) {
		$post_modified     = current_time( 'mysql' );
		$post_modified_gmt = current_time( 'mysql', 1 );
	} else {
		$post_modified     = $post_date;
		$post_modified_gmt = $post_date_gmt;
	}

	if ( 'attachment' !== $post_type ) {
		$now = gmdate( 'Y-m-d H:i:s' );

		if ( 'publish' === $post_status ) {
			if ( strtotime( $post_date_gmt ) - strtotime( $now ) >= MINUTE_IN_SECONDS ) {
				$post_status = 'future';
			}
		} elseif ( 'future' === $post_status ) {
			if ( strtotime( $post_date_gmt ) - strtotime( $now ) < MINUTE_IN_SECONDS ) {
				$post_status = 'publish';
			}
		}
	}

	// Comment status.
	if ( empty( $postarr['comment_status'] ) ) {
		if ( $update ) {
			$comment_status = 'closed';
		} else {
			$comment_status = get_default_comment_status( $post_type );
		}
	} else {
		$comment_status = $postarr['comment_status'];
	}

	// These variables are needed by compact() later.
	$post_content_filtered = $postarr['post_content_filtered'];
	$post_author           = isset( $postarr['post_author'] ) ? $postarr['post_author'] : $user_id;
	$ping_status           = empty( $postarr['ping_status'] ) ? get_default_comment_status( $post_type, 'pingback' ) : $postarr['ping_status'];
	$to_ping               = isset( $postarr['to_ping'] ) ? sanitize_trackback_urls( $postarr['to_ping'] ) : '';
	$pinged                = isset( $postarr['pinged'] ) ? $postarr['pinged'] : '';
	$import_id             = isset( $postarr['import_id'] ) ? $postarr['import_id'] : 0;

	/*
	 * The 'wp_insert_post_parent' filter expects all variables to be present.
	 * Previously, these variables would have already been extracted
	 */
	if ( isset( $postarr['menu_order'] ) ) {
		$menu_order = (int) $postarr['menu_order'];
	} else {
		$menu_order = 0;
	}

	$post_password = isset( $postarr['post_password'] ) ? $postarr['post_password'] : '';
	if ( 'private' === $post_status ) {
		$post_password = '';
	}

	if ( isset( $postarr['post_parent'] ) ) {
		$post_parent = (int) $postarr['post_parent'];
	} else {
		$post_parent = 0;
	}

	$new_postarr = array_merge(
		array(
			'ID' => $post_id,
		),
		compact( array_diff( array_keys( $defaults ), array( 'context', 'filter' ) ) )
	);

	/**
	 * Filters the post parent -- used to check for and prevent hierarchy loops.
	 *
	 * @since 3.1.0
	 *
	 * @param int   $post_parent Post parent ID.
	 * @param int   $post_id     Post ID.
	 * @param array $new_postarr Array of parsed post data.
	 * @param array $postarr     Array of sanitized, but otherwise unmodified post data.
	 */
	$post_parent = apply_filters( 'wp_insert_post_parent', $post_parent, $post_id, $new_postarr, $postarr );

	/*
	 * If the post is being untrashed and it has a desired slug stored in post meta,
	 * reassign it.
	 */
	if ( 'trash' === $previous_status && 'trash' !== $post_status ) {
		$desired_post_slug = get_post_meta( $post_id, '_wp_desired_post_slug', true );

		if ( $desired_post_slug ) {
			delete_post_meta( $post_id, '_wp_desired_post_slug' );
			$post_name = $desired_post_slug;
		}
	}

	// If a trashed post has the desired slug, change it and let this post have it.
	if ( 'trash' !== $post_status && $post_name ) {
		/**
		 * Filters whether or not to add a `__trashed` suffix to trashed posts that match the name of the updated post.
		 *
		 * @since 5.4.0
		 *
		 * @param bool   $add_trashed_suffix Whether to attempt to add the suffix.
		 * @param string $post_name          The name of the post being updated.
		 * @param int    $post_id            Post ID.
		 */
		$add_trashed_suffix = apply_filters( 'add_trashed_suffix_to_trashed_posts', true, $post_name, $post_id );

		if ( $add_trashed_suffix ) {
			wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_id );
		}
	}

	// When trashing an existing post, change its slug to allow non-trashed posts to use it.
	if ( 'trash' === $post_status && 'trash' !== $previous_status && 'new' !== $previous_status ) {
		$post_name = wp_add_trashed_suffix_to_post_name_for_post( $post_id );
	}

	$post_name = wp_unique_post_slug( $post_name, $post_id, $post_status, $post_type, $post_parent );

	// Don't unslash.
	$post_mime_type = isset( $postarr['post_mime_type'] ) ? $postarr['post_mime_type'] : '';

	// Expected_slashed (everything!).
	$data = compact(
		'post_author',
		'post_date',
		'post_date_gmt',
		'post_content',
		'post_content_filtered',
		'post_title',
		'post_excerpt',
		'post_status',
		'post_type',
		'comment_status',
		'ping_status',
		'post_password',
		'post_name',
		'to_ping',
		'pinged',
		'post_modified',
		'post_modified_gmt',
		'post_parent',
		'menu_order',
		'post_mime_type',
		'guid'
	);

	$emoji_fields = array( 'post_title', 'post_content', 'post_excerpt' );

	foreach ( $emoji_fields as $emoji_field ) {
		if ( isset( $data[ $emoji_field ] ) ) {
			$charset = $wpdb->get_col_charset( $wpdb->posts, $emoji_field );

			if ( 'utf8' === $charset ) {
				$data[ $emoji_field ] = wp_encode_emoji( $data[ $emoji_field ] );
			}
		}
	}

	if ( 'attachment' === $post_type ) {
		/**
		 * Filters attachment post data before it is updated in or added to the database.
		 *
		 * @since 3.9.0
		 * @since 5.4.1 The `$unsanitized_postarr` parameter was added.
		 * @since 6.0.0 The `$update` parameter was added.
		 *
		 * @param array $data                An array of slashed, sanitized, and processed attachment post data.
		 * @param array $postarr             An array of slashed and sanitized attachment post data, but not processed.
		 * @param array $unsanitized_postarr An array of slashed yet *unsanitized* and unprocessed attachment post data
		 *                                   as originally passed to wp_insert_post().
		 * @param bool  $update              Whether this is an existing attachment post being updated.
		 */
		$data = apply_filters( 'wp_insert_attachment_data', $data, $postarr, $unsanitized_postarr, $update );
	} else {
		/**
		 * Filters slashed post data just before it is inserted into the database.
		 *
		 * @since 2.7.0
		 * @since 5.4.1 The `$unsanitized_postarr` parameter was added.
		 * @since 6.0.0 The `$update` parameter was added.
		 *
		 * @param array $data                An array of slashed, sanitized, and processed post data.
		 * @param array $postarr             An array of sanitized (and slashed) but otherwise unmodified post data.
		 * @param array $unsanitized_postarr An array of slashed yet *unsanitized* and unprocessed post data as
		 *                                   originally passed to wp_insert_post().
		 * @param bool  $update              Whether this is an existing post being updated.
		 */
		$data = apply_filters( 'wp_insert_post_data', $data, $postarr, $unsanitized_postarr, $update );
	}

	$data  = wp_unslash( $data );
	$where = array( 'ID' => $post_id );

	if ( $update ) {
		/**
		 * Fires immediately before an existing post is updated in the database.
		 *
		 * @since 2.5.0
		 *
		 * @param int   $post_id Post ID.
		 * @param array $data    Array of unslashed post data.
		 */
		do_action( 'pre_post_update', $post_id, $data );

		if ( false === $wpdb->update( $wpdb->posts, $data, $where ) ) {
			if ( $wp_error ) {
				if ( 'attachment' === $post_type ) {
					$message = __( 'Could not update attachment in the database.','stock-sync-with-google-sheet-for-woocommerce' );
				} else {
					$message = __( 'Could not update post in the database.','stock-sync-with-google-sheet-for-woocommerce' );
				}

				return new WP_Error( 'db_update_error', $message, $wpdb->last_error );
			} else {
				return 0;
			}
		}
	} else {
		// If there is a suggested ID, use it if not already present.
		if ( ! empty( $import_id ) ) {
			$import_id = (int) $import_id;

			if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id ) ) ) {
				$data['ID'] = $import_id;
			}
		}

		if ( false === $wpdb->insert( $wpdb->posts, $data ) ) {
			if ( $wp_error ) {
				if ( 'attachment' === $post_type ) {
					$message = __( 'Could not insert attachment into the database.','stock-sync-with-google-sheet-for-woocommerce' );
				} else {
					$message = __( 'Could not insert post into the database.','stock-sync-with-google-sheet-for-woocommerce' );
				}

				return new WP_Error( 'db_insert_error', $message, $wpdb->last_error );
			} else {
				return 0;
			}
		}

		$post_id = (int) $wpdb->insert_id;

		// Use the newly generated $post_id.
		$where = array( 'ID' => $post_id );
	}

	if ( empty( $data['post_name'] ) && ! in_array( $data['post_status'], array( 'draft', 'pending', 'auto-draft' ), true ) ) {
		$data['post_name'] = wp_unique_post_slug( sanitize_title( $data['post_title'], $post_id ), $post_id, $data['post_status'], $post_type, $post_parent );

		$wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ), $where );
		clean_post_cache( $post_id );
	}

	if ( is_object_in_taxonomy( $post_type, 'category' ) ) {
		wp_set_post_categories( $post_id, $post_category );
	}

	if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $post_type, 'post_tag' ) ) {
		wp_set_post_tags( $post_id, $postarr['tags_input'] );
	}

	// Add default term for all associated custom taxonomies.
	if ( 'auto-draft' !== $post_status ) {
		foreach ( get_object_taxonomies( $post_type, 'object' ) as $taxonomy => $tax_object ) {

			if ( ! empty( $tax_object->default_term ) ) {

				// Filter out empty terms.
				if ( isset( $postarr['tax_input'][ $taxonomy ] ) && is_array( $postarr['tax_input'][ $taxonomy ] ) ) {
					$postarr['tax_input'][ $taxonomy ] = array_filter( $postarr['tax_input'][ $taxonomy ] );
				}

				// Passed custom taxonomy list overwrites the existing list if not empty.
				$terms = wp_get_object_terms( $post_id, $taxonomy, array( 'fields' => 'ids' ) );
				if ( ! empty( $terms ) && empty( $postarr['tax_input'][ $taxonomy ] ) ) {
					$postarr['tax_input'][ $taxonomy ] = $terms;
				}

				if ( empty( $postarr['tax_input'][ $taxonomy ] ) ) {
					$default_term_id = get_option( 'default_term_' . $taxonomy );
					if ( ! empty( $default_term_id ) ) {
						$postarr['tax_input'][ $taxonomy ] = array( (int) $default_term_id );
					}
				}
			}
		}
	}

	// New-style support for all custom taxonomies.
	if ( ! empty( $postarr['tax_input'] ) ) {
		foreach ( $postarr['tax_input'] as $taxonomy => $tags ) {
			$taxonomy_obj = get_taxonomy( $taxonomy );

			if ( ! $taxonomy_obj ) {
				/* translators: %s: Taxonomy name. */
				_doing_it_wrong( __FUNCTION__, sprintf( esc_html__( 'Invalid taxonomy: %s.', 'stock-sync-with-google-sheet-for-woocommerce' ), esc_html( $taxonomy ) ), '4.4.0' );
				continue;
			}
			// array = hierarchical, string = non-hierarchical.
			if ( is_array( $tags ) ) {
				$tags = array_filter( $tags );
			}

			if ( current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
				wp_set_post_terms( $post_id, $tags, $taxonomy );
			}
		}
	}

	if ( ! empty( $postarr['meta_input'] ) ) {
		foreach ( $postarr['meta_input'] as $field => $value ) {
			update_post_meta( $post_id, $field, $value );
		}
	}

	$current_guid = get_post_field( 'guid', $post_id );

	// Set GUID.
	if ( ! $update && '' === $current_guid ) {
		$wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post_id ) ), $where );
	}

	if ( 'attachment' === $postarr['post_type'] ) {
		if ( ! empty( $postarr['file'] ) ) {
			update_attached_file( $post_id, $postarr['file'] );
		}

		if ( ! empty( $postarr['context'] ) ) {
			add_post_meta( $post_id, '_wp_attachment_context', $postarr['context'], true );
		}
	}

	// Set or remove featured image.
	if ( isset( $postarr['_thumbnail_id'] ) ) {
		$thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ) || 'revision' === $post_type; //phpcs:ignore

		if ( ! $thumbnail_support && 'attachment' === $post_type && $post_mime_type ) {
			if ( wp_attachment_is( 'audio', $post_id ) ) {
				$thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
			} elseif ( wp_attachment_is( 'video', $post_id ) ) {
				$thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
			}
		}

		if ( $thumbnail_support ) {
			$thumbnail_id = (int) $postarr['_thumbnail_id'];
			if ( -1 === $thumbnail_id ) {
				delete_post_thumbnail( $post_id );
			} else {
				set_post_thumbnail( $post_id, $thumbnail_id );
			}
		}
	}

	clean_post_cache( $post_id );

	$post = get_post( $post_id );

	if ( ! empty( $postarr['page_template'] ) ) {
		$post->page_template = $postarr['page_template'];
		$page_templates      = wp_get_theme()->get_page_templates( $post );

		if ( 'default' !== $postarr['page_template'] && ! isset( $page_templates[ $postarr['page_template'] ] ) ) {
			if ( $wp_error ) {
				return new WP_Error( 'invalid_page_template', __( 'Invalid page template.','stock-sync-with-google-sheet-for-woocommerce' ) );
			}

			update_post_meta( $post_id, '_wp_page_template', 'default' );
		} else {
			update_post_meta( $post_id, '_wp_page_template', $postarr['page_template'] );
		}
	}

	if ( 'attachment' !== $postarr['post_type'] ) {
		wp_transition_post_status( $data['post_status'], $previous_status, $post );
	} else {
		if ( $update ) {
			/**
			 * Fires once an existing attachment has been updated.
			 *
			 * @since 2.0.0
			 *
			 * @param int $post_id Attachment ID.
			 */

			$post_after = get_post( $post_id );

			/**
			 * Fires once an existing attachment has been updated.
			 *
			 * @since 4.4.0
			 *
			 * @param int     $post_id      Post ID.
			 * @param WP_Post $post_after   Post object following the update.
			 * @param WP_Post $post_before  Post object before the update.
			 */

		} else {

			/**
			 * Fires once an attachment has been added.
			 *
			 * @since 2.0.0
			 *
			 * @param int $post_id Attachment ID.
			 */
			do_action('ssgsw_attachment_add', $post_id);

		}

		return $post_id;
	}

	if ( $update ) {
		/**
		 * Fires once an existing post has been updated.
		 *
		 * The dynamic portion of the hook name, `$post->post_type`, refers to
		 * the post type slug.
		 *
		 * Possible hook names include:
		 *
		 *  - `edit_post_post`
		 *  - `edit_post_page`
		 *
		 * @since 5.1.0
		 *
		 * @param int     $post_id Post ID.
		 * @param WP_Post $post    Post object.
		 */

		/**
		 * Fires once an existing post has been updated.
		 *
		 * @since 1.2.0
		 *
		 * @param int     $post_id Post ID.
		 * @param WP_Post $post    Post object.
		 */

		$post_after = get_post( $post_id );

		/**
		 * Fires once an existing post has been updated.
		 *
		 * @since 3.0.0
		 *
		 * @param int     $post_id      Post ID.
		 * @param WP_Post $post_after   Post object following the update.
		 * @param WP_Post $post_before  Post object before the update.
		 */
	}

	/**
	 * Fires once a post has been saved.
	 *
	 * The dynamic portion of the hook name, `$post->post_type`, refers to
	 * the post type slug.
	 *
	 * Possible hook names include:
	 *
	 *  - `save_post_post`
	 *  - `save_post_page`
	 *
	 * @since 3.7.0
	 *
	 * @param int     $post_id Post ID.
	 * @param WP_Post $post    Post object.
	 * @param bool    $update  Whether this is an existing post being updated.
	 */
	do_action( 'save_post', $post_id, $post, $update );
	/**
	 * Fires once a post has been saved.
	 *
	 * @since 1.5.0
	 *
	 * @param int     $post_id Post ID.
	 * @param WP_Post $post    Post object.
	 * @param bool    $update  Whether this is an existing post being updated.
	 */

	/**
	 * Fires once a post has been saved.
	 *
	 * @since 2.0.0
	 *
	 * @param int     $post_id Post ID.
	 * @param WP_Post $post    Post object.
	 * @param bool    $update  Whether this is an existing post being updated.
	 */

	if ( $fire_after_hooks ) {
		wp_after_insert_post( $post, $update, $post_before );
	}

	return $post_id;
}

/**
 * Inserts an attachment.
 *
 * If you set the 'ID' in the $args parameter, it will mean that you are
 * updating and attempt to update the attachment. You can also set the
 * attachment name or title by setting the key 'post_name' or 'post_title'.
 *
 * You can set the dates for the attachment manually by setting the 'post_date'
 * and 'post_date_gmt' keys' values.
 *
 * By default, the comments will use the default settings for whether the
 * comments are allowed. You can close them manually or keep them open by
 * setting the value for the 'comment_status' key.
 *
 * @since 2.0.0
 * @since 4.7.0 Added the `$wp_error` parameter to allow a WP_Error to be returned on failure.
 * @since 5.6.0 Added the `$fire_after_hooks` parameter.
 *
 * @see wp_insert_post()
 *
 * @param string|array $args             Arguments for inserting an attachment.
 * @param string|false $file             Optional. Filename. Default false.
 * @param int          $parent_post_id   Optional. Parent post ID or 0 for no parent. Default 0.
 * @param bool         $wp_error         Optional. Whether to return a WP_Error on failure. Default false.
 * @param bool         $fire_after_hooks Optional. Whether to fire the after insert hooks. Default true.
 * @return int|WP_Error The attachment ID on success. The value 0 or WP_Error on failure.
 */
function ssgsw_wp_insert_attachment( $args, $file = false, $parent_post_id = 0, $wp_error = false, $fire_after_hooks = true ) {
	$defaults = array(
		'file'        => $file,
		'post_parent' => 0,
	);

	$data = wp_parse_args( $args, $defaults );

	if ( ! empty( $parent_post_id ) ) {
		$data['post_parent'] = $parent_post_id;
	}

	$data['post_type'] = 'attachment';

	return ssgsw_wp_insert_post( $data, $wp_error, $fire_after_hooks );
}


/**
 * Save a large dataset (up to 4GB) into a single transient.
 *
 * @param array  $data The dataset to store (e.g., 40K product info).
 * @param string $key The transient key.
 * @param int    $expiration Expiration time in seconds (default: 12 hours).
 * @return bool True if saved successfully, false otherwise.
 */
function ssgsw_save_sheet_data_to_transient( $data, $key = 'ssgsw_temp_sheet_info', $expiration = 43200 ) {
	if ( ! is_array($data) || empty($data) ) {
		return false;
	}
	$serialized_data = serialize($data);
	return set_transient($key, $serialized_data, $expiration);
}
/**
 * Retrieve large-scale data from a single transient.
 *
 * @param string $key The transient key.
 * @return array|false The dataset as an array, or false if the transient is missing/expired.
 */
function ssgsw_get_sheet_data_from_transient( $key = 'ssgsw_temp_sheet_info' ) {
	$serialized_data = get_transient($key);

	if ( $serialized_data === false ) {
		return [];
	}

	// Unserialize the data to restore the original array.
	return unserialize($serialized_data);
}

/**
 * Insert SSGSW product information
 *
 * @param int    $product_id ID of product.
 * @param object $value Key of the product.
 *
 * @return mixed
 */
function ssgsw_insert_update_product_information( $product_id, $value = [], $tag = 'WordPress' ) {

	global $wpdb;
	$table_name = $wpdb->prefix . 'ssgsw_products';

	if ( is_object($value) ) {
		$value = (array) $value;
	}
	if ( is_array($value) ) {
		$value = serialize($value);
	}

	$check_data = ssgsw_get_product_information_by_id($product_id);

	if ( is_array($check_data) && ! empty($check_data) ) {
		$id = $check_data[0]['id'];
		$current_product_information = $check_data[0]['product_current_info'];

		$current_unserialized = unserialize($current_product_information);
		$new_unserialized = unserialize($value);
		// $merged_data = array_merge($current_unserialized, $new_unserialized);

		if ( $current_unserialized !== $new_unserialized ) {

			$update_data = [
				'product_id' => $product_id,
				'product_current_info' => $value,
				'product_info_previous' => $current_product_information,
				'product_info_previous_2' => $check_data[0]['product_info_previous'],
				'current_tag' => $tag,
				'previous_tag' => $check_data[0]['current_tag'],
				'previous_tag_2' => $check_data[0]['previous_tag'],
				'date' => gmdate('Y-m-d H:i:s'),
			];
			if ( 'Sheet' === $tag ) {
				$update_data['product_information'] = $value;
			}
			$where_condition = [ 'id' => $id ];
			$updated = $wpdb->update($table_name, $update_data, $where_condition);

			return $updated;
		} else {
			return 0;
		}
	} else {

		$insert_data = [
			'product_id' => $product_id,
			'product_current_info' => $value,
			'product_info_previous' => '',
			'product_info_previous_2' => '',
			'current_tag' => $tag,
			'previous_tag' => '',
			'previous_tag_2' => '',
		];
		if ( 'Sheet' === $tag ) {
			$update_data['product_information'] = $value;
		}

		$inserted = $wpdb->insert($table_name, $insert_data);
		return $inserted;
	}
}


/**
 * Get Ult version number
 */
function ssgsw_get_ult_version() {
	$ult_version = 'stock-sync-with-google-sheet-for-woocommerce-ultimate/stock-sync-with-google-sheet-for-woocommerce-ultimate.php';
	if ( file_exists( WP_PLUGIN_DIR . '/' . $ult_version ) ) {
		$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $ult_version );
		if ( is_plugin_active( $ult_version ) ) {
			if ( $plugin_data ) {
				$plugin_version = $plugin_data['Version'];
				if ( $plugin_version < '2.0.6' ) {
					return false;
				} else {
					return true;
				}
			}
		}
	}
	return true;
}
/**
 * Get SSGSW product information
 *
 * @param int $product_id ID of product.
 *
 * @return mixed
 */
function ssgsw_get_product_information_by_id( $product_id ) {
	global $wpdb;
	$product_id = sanitize_text_field( $product_id );
	$results = $wpdb->get_results(
		$wpdb->prepare(
			"SELECT * FROM {$wpdb->prefix}ssgsw_products WHERE product_id = %d",
			$product_id
		),
		ARRAY_A
	);
	return $results;
}
/**
 * Get parent id by child id
 *
 * @return integer
 */
function ssgsw_get_parent_id_by_child( $child_id ) {
	global $wpdb;
	$parent_id = $wpdb->get_var($wpdb->prepare(
		"SELECT post_parent FROM {$wpdb->prefix}posts 
         WHERE ID = %d AND (post_type = 'product' OR post_type = 'product_variation')",
		$child_id
	));
	return $parent_id;
}

/**
 * Retrieves the child product IDs for a given parent product ID, ordered by ascending ID.
 *
 * This function fetches the IDs of all child products (including variations)
 * that belong to a specified parent product, sorted in ascending order.
 *
 * @param int $product_id The parent product ID.
 *
 * @return array The IDs of the child products, or an empty array if none are found.
 */
function ssgsw_get_children_ids( $product_id ) {
	global $wpdb;
	$statuses = ssgsw_status_formating_funcions();
	$status_placeholders = implode(',', array_fill(0, count($statuses), '%s'));
	$child_product_ids = $wpdb->get_col($wpdb->prepare(
		"SELECT ID
        FROM {$wpdb->posts}
        WHERE post_parent = %d
        AND (post_type = 'product' OR post_type = 'product_variation')
        AND post_status IN ($status_placeholders)
        ORDER BY ID ASC",
		array_merge([ $product_id ], $statuses)
	));
	return $child_product_ids;
}



/**
 * Function to get the nearest smaller ID from an array compared to a target ID.
 * The array is processed in descending order to find the closest smaller ID.
 *
 * @param array $array The array of product IDs.
 * @param int   $target_id The target ID to compare against.
 * @return int|null The nearest smaller ID, or null if none found.
 */
function ssgsw_get_nearest_smaller_id( $array, $target_id ) {
	// Sort the array in descending order.
	$nearest_id = null;
	if ( is_array($array) && ! empty($array) ) {
		rsort($array);
		foreach ( $array as $id ) {
			if ( $id < $target_id ) {
				$nearest_id = $id;
				break;
			}
		}
	}
	return $nearest_id;
}

/**
 * Find the range of a row in Google Sheets with cross-checking.
 *
 * This function attempts to find the row index of a specific child ID in a Google Sheets array.
 * It first checks for a previously saved temporary index number, cross-checks the corresponding row,
 * and if not matched, searches within a 50-row range.
 *
 * @param mixed $child_id The child product ID to search for.
 * @param array $sheets_info The Google Sheets data array to search in.
 *
 * @return mixed The found row index (1-based) or false if not found.
 */
function ssgsw_find_out_range_with_cross_check( $child_id, $sheets_info = [] ) {
	return false;
}

/**
 * Format and retrieve the index number for a given product ID from a multi-dimensional array.
 *
 * This function processes a multi-dimensional array to extract the first value of each sub-array
 * and creates a single-dimensional array. It then checks if the given product ID exists as a key
 * in the formatted array and returns the corresponding value.
 *
 * @param int   $product_id The product ID (index position) to search for in the flattened array.
 * @param array $sheets_info A multi-dimensional array containing product information.
 *                           Each sub-array is expected to have the relevant data in the first index (0).
 * @return mixed|null Returns the formatted index number (first value of the sub-array) if found;
 *                    otherwise, returns null. If the input array is empty, returns null.
 *
 * Example Usage:
 * $sheets_info = [
 *     [1001, 'Product A'],
 *     [1002, 'Product B'],
 *     [1003, 'Product C'],
 * ];
 * $product_id = 1; // Index position to retrieve.
 * $result = ssgsw_format_index_number($product_id, $sheets_info);
 * // Output: 1002 (value from index 1 in the flattened array).
 */
function ssgsw_format_index_number( $sheets_info = [] ) {
    if ( !is_array($sheets_info) || empty($sheets_info) ) {
        return [];
    }
    // Find the column index for "Store Name"
    $store_name_index = -1;
    if (isset($sheets_info[0]) && is_array($sheets_info[0])) {
        foreach ($sheets_info[0] as $index => $header) {
            if ('Store Name' === trim($header)) {
                $store_name_index = $index;
                break;
            }
        }
    }

    if ($store_name_index === -1) {
        // If "Store Name" column is not found, use current domain and ID.
        $current_domain = ssgsw_current_site_domain(); 
        $unique_keys = array_map(function ($subArray) use ($current_domain) {
            $product_id = isset($subArray[0]) ? $subArray[0] : null;
            if ($product_id) {
                // Create key with ID and domain without hashing.
                return $product_id . '_' . $current_domain;
            }
            return null;
        }, $sheets_info);

        return array_flip(array_filter($unique_keys));
    } else {
        // If "Store Name" column is found, use its value with the ID.
        $unique_keys = array_map(function ($subArray) use ($store_name_index) {
            $product_id = isset($subArray[0]) ? $subArray[0] : null;
            $store_name = isset($subArray[$store_name_index]) ? $subArray[$store_name_index] : null;

            if ($product_id && $store_name) {
                // Create key with ID and store name from the sheet without hashing.
                return $product_id . '_' . $store_name;
            }
            return null;
        }, $sheets_info);

        return array_flip(array_filter($unique_keys));
    }
}

/**
 * Retrieves the current site's domain name.
 *
 * This function uses WordPress's built-in `home_url()` to get the site's URL
 * and then extracts just the host (domain name) using `parse_url()`.
 * This is useful for creating unique identifiers for multisite setups or
 * for distinguishing data from different sources.
 *
 * @since 1.0.0
 *
 * @return string|false The domain name of the current site, or false on failure.
 */
function ssgsw_current_site_domain() {
    return wp_parse_url(home_url(), PHP_URL_HOST);
}

/**
 * Finds and returns the sheet index number for a given product ID.
 * If the product ID exists in the provided sheet information, it increments the value by 1.
 * If the product ID is not found, returns null.
 *
 * @param int   $product_id The ID of the product to find the sheet index number for.
 * @param array $sheets_info The array containing sheet information where keys are product IDs and values are index numbers.
 *
 * @return int|null The incremented index number if the product ID exists in the sheet information, or null if not.
 */
function ssgsw_findout_sheet_index_number( $product_id, $sheets_info ) {
	return isset($sheets_info[ $product_id ]) ? $sheets_info[ $product_id ] + 1 : null;
}

/**
 * Get the temporary index number for a product.
 *
 * This function retrieves a temporary index number for a given product ID from stored options.
 * If the product ID does not have an index number, it attempts to find the closest related product
 * (either child or parent) and returns the index number based on various conditions, including if
 * the product is variable or simple.
 *
 * @param mixed  $product_id The ID of the product to find the index for.
 * @param bool   $parent Whether the product is a parent product.
 * @param string $orginal_type The type of the product (e.g., 'Variable', 'Simple'). Default is 'Variable'.
 *
 * @return array An array containing:
 *               - 'index_number': The index number of the product or nearest related product.
 *               - 'increment': Whether the index number should be incremented (true/false).
 *               - 'product_id': The ID of the product that provided the index number.
 *               - (optional) 'sibling_plus': A flag indicating if sibling increment logic was used.
 */
function ssgsw_get_temp_index_number_in_options( $product_id, $parent = false, $orginal_type = 'Variable', $sheets_info = [] ) {
	$get_option = $sheets_info;
	$index_number_exits = ssgsw_get_index_number_option($product_id, $get_option);
	if ( $index_number_exits ) {
		return [
			'index_number' => $index_number_exits,
			'increment' => false,
			'product_id' => $product_id,
		];
	} else {
		if ( $parent ) {
			if ( 'Variable' === $orginal_type ) {
				$child_product_ids = ssgsw_get_children_ids($product_id);
				if ( is_array( $child_product_ids ) && ! empty( $child_product_ids ) ) {
					$smallest_id = min($child_product_ids);
					$existing_number = ssgsw_get_index_number_option($smallest_id, $get_option);
					if ( $existing_number ) {
						return [
							'index_number' => $existing_number,
							'increment' => 1,
							'product_id' => $smallest_id,
						];
					} else {
						return [
							'index_number' => false,
							'increment' => false,
						];
					}
				} else {
					$nearest_product_id = ssgsw_get_nearest_parent_product_id_wp($product_id);
					$index_number_exits = ssgsw_get_index_number_option($nearest_product_id, $get_option);
					if ( $index_number_exits ) {
						return [
							'index_number' => $index_number_exits,
							'increment' => 1,
							'product_id' => $nearest_product_id,
						];
					} else {
						return [
							'index_number' => false,
							'increment' => false,
						];
					}
				}
			} else {
				$nearest_product_id = ssgsw_get_nearest_parent_product_id_wp($product_id);
				$index_number_exits = ssgsw_get_index_number_option($nearest_product_id, $get_option);
				if ( $index_number_exits ) {
					return [
						'index_number' => $index_number_exits,
						'increment' => 1,
						'product_id' => $nearest_product_id,
					];
				} else {
					return [
						'index_number' => false,
						'increment' => false,
					];
				}
			}
		} else {
			$get_parent_id = ssgsw_get_parent_id_by_child($product_id);
			if ( $get_parent_id ) {
				$child_product_ids = ssgsw_get_children_ids($get_parent_id);
				if ( is_array( $child_product_ids ) && ! empty( $child_product_ids ) ) {
					$subling_id = ssgsw_get_nearest_smaller_id($child_product_ids, $product_id);
					if ( $subling_id ) {
						$existing_number = ssgsw_get_index_number_option($subling_id, $get_option);
						if ( $existing_number ) {
							return [
								'index_number' => $existing_number,
								'increment' => 1,
								'product_id' => $subling_id,
								'sibling_plus' => 1,
							];
						} else {
							$existing_number = ssgsw_get_index_number_option($get_parent_id, $get_option);
							if ( $existing_number ) {
								return [
									'index_number' => $existing_number,
									'increment' => 1,
									'product_id' => $get_parent_id,
									'sibling_plus' => 1,
								];
							} else {
								return [
									'index_number' => false,
									'increment' => false,
								];
							}
						}
					} else {
						$existing_number = ssgsw_get_index_number_option($get_parent_id, $get_option);
						if ( $existing_number ) {
							return [
								'index_number' => $existing_number,
								'increment' => 1,
								'product_id' => $get_parent_id,
								'sibling_plus' => 1,
							];
						} else {
							return [
								'index_number' => false,
								'increment' => false,
							];
						}
					}
				} else {
					$existing_number = ssgsw_get_index_number_option($get_parent_id, $get_option );
					if ( $existing_number ) {
						return [
							'index_number' => $existing_number,
							'increment' => 1,
							'product_id' => $get_parent_id,
							'sibling_plus' => 1,
						];
					} else {
						return [
							'index_number' => false,
							'increment' => false,
						];
					}
				}
			} else {
				return [
					'index_number' => false,
					'increment' => false,
				];

			}
		}
	}
}
/**
 * Check the post status of a specific post or product in WordPress.
 *
 * This function retrieves the `post_status` from the `wp_posts` table
 * for the given post ID. It is designed to work for posts, pages, or custom post types
 * (e.g., WooCommerce products).
 *
 * @param int $product_id The ID of the post or product to check.
 * @return string|null The post status (e.g., 'publish', 'draft', 'pending'),
 *                     or null if the post is not found.
 */
function ssgsw_check_post_status( $product_id ) {
	global $wpdb;
	// Query to retrieve the post_status for the specified ID.
	$query = $wpdb->get_var($wpdb->prepare(
		"SELECT post_status FROM {$wpdb->prefix}posts WHERE ID = %d",
		$product_id
	));

	// Return the post status or null if the post is not found.
	return $query;
}


 /**
  * Get the index number for a product from index data.
  *
  * This function retrieves the index number for a given product ID from the provided index data array.
  *
  * @param mixed $product_id The product ID to search for.
  * @param array $index_data The array containing index data for products.
  *
  * @return mixed The index number for the product or false if not found.
  */
function ssgsw_get_index_number_option( $product_id, $index_data ) {
	$domain = $product_id.'_'. ssgsw_current_site_domain();
	return isset($index_data[ $domain ]) ? $index_data[ $domain ] + 1 : false;
}
/**
 * Extract the number of rows
 *
 * @return integer
 */
function ssgsw_extract_row_number( $updated_range ) {
	if ( preg_match('/!(?:[A-Z]+)(\d+):/', $updated_range, $matches) ) {
		return (int) $matches[1];
	}
	return false;
}

/**
 * Get categories for a product by ID and return them as a formatted string.
 *
 * @param int $product_id The ID of the product.
 * @return string The categories in the format "term_id:term_name, ...".
 */
function ssgsw_get_product_categories( $child_id ) {
	global $wpdb;
	$parent_id = ssgsw_get_parent_product_id($child_id);
	$categories = null;
	if ( $parent_id ) {
		$categories = $wpdb->get_var($wpdb->prepare(
			"SELECT GROUP_CONCAT(CONCAT_WS(':', t.term_id, t.name) SEPARATOR ', ') AS categories
			 FROM {$wpdb->terms} AS t
			 INNER JOIN {$wpdb->term_taxonomy} AS tt
				ON t.term_id = tt.term_id
			 INNER JOIN {$wpdb->term_relationships} AS tr
				ON tt.term_taxonomy_id = tr.term_taxonomy_id
			 WHERE tt.taxonomy = 'product_cat'
			   AND tr.object_id = %d;",
			$parent_id
		));
	}

	return $categories ? $categories : null;
}


/**
 * Retrieve a list of distinct attribute meta keys for products from the database.
 *
 * This function queries the WordPress postmeta table to fetch distinct `meta_key` values 
 * that match the pattern `attribute_%`, which is commonly used for product attributes 
 * such as `attribute_color`, `attribute_size`, etc. The query is safely executed using 
 * the `$wpdb->prepare()` method to prevent SQL injection.
 *
 * @global wpdb $wpdb WordPress database object used to interact with the database.
 *
 * @return array An array of objects, each containing a `meta_key` property representing 
 *               a distinct attribute meta key. Returns an empty array if no matching 
 *               attributes are found.
 */
function ssgsw_get_product_attribute_keys() {
    global $wpdb;
	$show_attributes  = true === wp_validate_boolean( ssgsw_get_option('show_attributes', false) );
	$separate_disable  = true === wp_validate_boolean( ssgsw_get_option('separate_attributes', false) );
	$results = [];
	if ($show_attributes && !$separate_disable) {
		$results = $wpdb->get_results($wpdb->prepare(
			"SELECT DISTINCT meta_key 
			FROM {$wpdb->prefix}postmeta 
			WHERE meta_key LIKE %s",
			'attribute_%'
		));
	}
    // Ensure results are returned as an array
    return is_array($results) && !empty($results) ? $results : [];
}


/**
 * Get all product statuses in WooCommerce.
 *
 * This function retrieves all statuses associated with products in WooCommerce,
 * including custom statuses, such as trash, draft, and publish.
 *
 * @global wpdb $wpdb WordPress database object.
 * @return array List of all unique product statuses.
 */
function ssgsw_get_all_product_statuses($ucfirst_skip = true) {
	global $wpdb;

	// Fetch distinct post statuses from the posts table.
	$results = $wpdb->get_col(
		$wpdb->prepare(
			"SELECT DISTINCT post_status 
             FROM {$wpdb->posts} 
             WHERE post_type = %s
               AND post_status != %s;",
			'product',
			'importing'
		)
	);

	// Define default statuses.
	$default_value = [
		'draft',
		'publish',
		'trash',
		'pending',
	];

	// Ensure $results is an array and merge with default values
	$results = is_array($results) ? $results : [];
	$results = array_unique(array_merge($default_value, $results));

	// Remove empty values from the results array.
	$results = array_filter($results, function ( $value ) {
		return ! empty($value);
	});
	if ($ucfirst_skip) {
		$results = array_map(function ($value) {
			if ($value === 'publish') {
				return 'Published';
			} elseif ($value === 'pending') {
				return 'Pending Review';
			}
			return ucfirst($value);
		}, $results);
	}
	// Apply any custom filters.
	$status = apply_filters('ssgsw_product_status_filter', $results);

	return $status;
}
/**
 * Retrieves all product type taxonomy IDs and their associated slugs.
 *
 * This function queries the WordPress database to get the term_taxonomy_id and the associated slug 
 * for all terms in the 'product_type' taxonomy. It uses a secure query with $wpdb->prepare to 
 * prevent SQL injection vulnerabilities.
 *
 * @return array An associative array where the keys are term_taxonomy_ids and the values are slugs.
 *               Example: [123 => 'simple', 456 => 'variable']
 */
function ssgsw_get_all_product_type_taxonomy_ids() {
    global $wpdb;
    
    // Prepare and execute the query to fetch term_taxonomy_id and slug for 'product_type' taxonomy.
    $results = $wpdb->get_results(
        $wpdb->prepare(
            "SELECT tt.term_taxonomy_id, t.slug
            FROM {$wpdb->term_taxonomy} tt
            INNER JOIN {$wpdb->terms} t ON tt.term_id = t.term_id
            WHERE tt.taxonomy = %s", 
            'product_type'
        ), 
        ARRAY_A
    );

    $taxonomy_ids = [];
	if(is_array($results) && !empty($results)) {
		foreach ($results as $row) {
			$taxonomy_ids[$row['term_taxonomy_id']] = ucfirst($row['slug']);
		}
	}
   

    return $taxonomy_ids;
}

/**
 * Retrieves the product type taxonomy term ID for a given product ID.
 *
 * This function fetches the WooCommerce product type taxonomy term ID, 
 * which represents the type of product (e.g., Simple, Variable, Grouped).
 *
 * @param int $product_id The ID of the WooCommerce product.
 * @return int|null Returns the product type term ID if found, or null if no type is assigned.
 */
function ssgsw_get_filter_value_convarter($product_id) {
    $terms = wp_get_post_terms($product_id, 'product_type');
	$post_status = get_post_status($product_id);
	$categoy = wp_get_post_terms($product_id, 'product_cat', ['fields' => 'ids']);
    $cat = !empty($categoy) ? $categoy : [];
    $product_type = !empty($terms) ? $terms[0]->term_id : [];
	return [
		'post_type' => $product_type,
		'post_status' => $post_status,
		'category'     => $cat

	];

}



/**
 * Retrieve the filters for products, including default filters.
 *
 * This function retrieves the filters used for querying products. If no filters
 * are found in the stored options, it returns a default set of filters where 
 * the "status" field is filtered for products with a "Published" status.
 *
 * @return array An array of filters, each with 'field', 'operator', and 'value' keys.
 *               Default is a filter with 'status' set to 'Published'.
 */
function ssgsw_all_filters_data( $status = false, $user_id = null ) {
    $default_filters = [
        ['field' => 'post_status', 'operator' => 'equals', 'value' => 'Published'],
    ];
	if(!ssgsw_is_license_valid()) {
		return $default_filters;
	}
	if ( ! $status ) {
		$get_filter_active = ssgsw_get_option('filter_active', false, $user_id);
		if ( ! $get_filter_active ) {
			return $default_filters;
		}
	}
    $get_option = ssgsw_get_option('all_product_filter_info', $default_filters, $user_id);

    return $get_option;
}
// print_r(ssgsw_all_filters_data());
/**
 * Format and Retrieve Product Statuses for the Plugin.
 *
 * This function retrieves all product statuses and formats them based on licensing validation and saved options.
 * It ensures that the returned data adheres to the plugin's configuration.
 *
 * @return array List of product statuses if the license is valid and options are saved,
 *               otherwise returns default statuses.
 */
function ssgsw_status_formating_funcions() {
	$status = ssgsw_get_all_product_statuses(false);
	$checked_value = ssgsw_get_option('show_product_status_data') ?? [];
	$format_value = function ($value) {
		if ($value === 'Pending Review') {
			return 'pending';
		} elseif ($value === 'Published') {
			return 'publish';
		}
		return strtolower($value);
	};

	$data_to_process = ( is_array( $checked_value ) && ! empty( $checked_value ) ) ? $checked_value : $status;
	return array_map($format_value, $data_to_process);
}

/**
 * Formats filter conditions by grouping them based on field type and operator.
 *
 * This function retrieves filter conditions using `ssgsw_all_filters_data()`, then categorizes 
 * them into two groups under each field:
 * - `equals`: Contains values where the operator is "equals".
 * - `not_equals`: Contains values where the operator is not "equals" (e.g., "not equals").
 *
 * @return array An associative array where:
 *               - The keys represent the field names.
 *               - Each field contains an array with:
 *                 - 'equals' (array of values matching "equals").
 *                 - 'not_equals' (array of values matching "not equals").
 *
 * Example Output:
 * ```
 * Array (
 *     [post_type] => Array (
 *         [equals] => Array ( [0] => external )
 *         [not_equals] => Array ( [0] => simple )
 *     )
 *     [post_status] => Array (
 *         [equals] => Array ( [0] => Draft )
 *         [not_equals] => Array ()
 *     )
 *     [category] => Array (
 *         [equals] => Array ( [0] => 34 )
 *         [not_equals] => Array ( [0] => 12 )
 *     )
 * )
 * ```
 */
function ssgsw_formating_the_filter_value( $user_id = null) {
    $conditions = ssgsw_all_filters_data(); // Retrieve all filter conditions
	
    $groupedConditions = [];

    // Process each condition
    foreach ($conditions as $condition) {
        $field = $condition['field'];
        $operator = strtolower($condition['operator']); // Normalize operator case
        $value = $condition['value'];
		if ( $field == 'post_status') {
			if ($value === 'Pending Review') {
				$value = 'pending';
			} elseif ($value === 'Published') {
				$value = 'publish';
			}
			$value = strtolower($value);
		}
        // Initialize the field grouping if not set
        if (!isset($groupedConditions[$field])) {
            $groupedConditions[$field] = [
                'equals' => [],
                'not_equals' => []
            ];
        }

        // Group values based on the operator type
        if ($operator === 'equals') {
            $groupedConditions[$field]['equals'][] = $value;
        } else {
            $groupedConditions[$field]['not_equals'][] = $value;
        }
    }

    return $groupedConditions; // Return the structured filter data
}


/**
 * Batch update the post status for multiple posts or products in WordPress.
 *
 * This function updates the `post_status` field in the `wp_posts` table
 * for a list of specified post IDs, using a single SQL query for efficiency.
 *
 * @param string $new_status The new post status to set (e.g., 'publish', 'draft').
 * @param array  $child_ids An array of post IDs to update.
 * @return bool True if the query executes successfully, false otherwise.
 */
function ssgsw_batch_update_post_status( $new_status, $child_ids ) {
	global $wpdb;
	if ( empty($child_ids) ) {
		return false;
	}
	$placeholders = implode(',', array_fill(0, count($child_ids), '%d'));
	$query = $wpdb->query($wpdb->prepare(
		"UPDATE {$wpdb->posts} SET post_status = %s WHERE ID IN ($placeholders)",
		array_merge(array( $new_status ), $child_ids)
	));
	return $query !== false;
}

/**
 * Get the parent product ID of a given variation ID in WooCommerce.
 *
 * This function retrieves the parent product ID associated with a child product variation.
 *
 * @param int $variation_id The ID of the variation product.
 * @return int|null The parent product ID if found, or null if not found.
 */
function ssgsw_get_parent_product_id( $variation_id ) {
	global $wpdb;
	$parent_id = $wpdb->get_var($wpdb->prepare(
		"SELECT post_parent
         FROM {$wpdb->posts}
         WHERE ID = %d
           AND post_type = 'product_variation';",
		$variation_id
	));
	return $parent_id ? intval( $parent_id ) : null;
}

/**
 * Retrieves the nearest parent product ID greater than the given product ID.
 *
 * This function searches for the closest parent product in the database
 * with a product ID that is greater than the specified product ID.
 *
 * @param string $product_id The unique identifier for the product.
 *
 * @return string|null The product ID of the nearest parent product, or null if not found.
 */
function ssgsw_get_nearest_parent_product_id_wp( $product_id ) {
	global $wpdb;
	$nearest_product_id = $wpdb->get_var($wpdb->prepare(
		"SELECT ID FROM {$wpdb->prefix}posts 
         WHERE ID > %d
         AND post_type = %s
         ORDER BY ID ASC
         LIMIT 1",
		$product_id,  // First argument should be product ID
		'product'     // Second argument should be post_type
	));

	// Check if a product ID was found, otherwise return null.
	return $nearest_product_id ? $nearest_product_id : null;
}

/**
 * Check if a sync process is currently in progress.
 *
 * This function checks the transient 'ssgsw_sync_in_progress' to determine
 * if a sync process is active. The transient serves as a flag that prevents
 * certain actions (like product creation) while a sync is happening.
 *
 * @return bool True if sync is in progress, false otherwise.
 */
function ssgsw_is_sync_in_progress() {
	return get_transient('ssgsw_sync_in_progress_state') ? true : false;
}
/**
 * Prevent product creation or update if a sync is in progress.
 *
 * This function checks if the 'ssgsw_sync_in_progress' transient is set,
 * and if so, prevents the product from being created or updated by returning an error.
 *
 * @param int $post_id The post ID of the product.
 */
function ssgsw_prevent_product_creation_during_sync( $post_id ) {
	// Check if a sync is in progress.
	if ( ssgsw_is_sync_in_progress() ) {
		// Prevent product saving by aborting the process.
		wp_die(esc_html__('Product creation is disabled during sync to sheet.', 'stock-sync-with-google-sheet-for-woocommerce'));
		exit;
	}
}

// Hook into the product creation and update process
// add_action('woocommerce_process_product_meta', 'ssgsw_prevent_product_creation_during_sync', 10, 1);


/**
 * Start the sync process by setting a transient.
 *
 * This function initiates the sync process by setting a transient 'ssgsw_sync_in_progress'.
 * While this transient is active (for 10 minutes by default), actions such as product creation
 * can be blocked to prevent conflicts during the sync.
 *
 * @return void
 */
function ssgsw_start_sync() {
	ssgsw_end_sync();
	set_transient('ssgsw_sync_in_progress_state', true, 10 * MINUTE_IN_SECONDS);
}

/**
 * End the sync process by removing the transient.
 *
 * This function concludes the sync process by deleting the 'ssgsw_sync_in_progress' transient.
 * Once the transient is deleted, blocked actions like product creation can be resumed.
 *
 * @return void
 */
function ssgsw_end_sync() {
	// Remove the transient to mark the end of the sync process.
	delete_transient('ssgsw_sync_in_progress_state');
}



// Hook into plugin upgrade event
add_action('upgrader_process_complete', 'ssgs_plugin_upgrade_handler', 10, 2);

/**
 * Handles plugin upgrade events and updates a custom option.
 *
 * This function is triggered after a plugin upgrade is completed. It checks if the plugin
 * 'FlexStock' was updated. If so, it sets the
 * 'ssgsw_license_sync' option to true, which is used to trigger subsequent actions, such as
 * redirection or data syncing.
 *
 * @param object $upgrader_object The upgrader object containing information about the upgrade process.
 * @param array  $options An array of options that define the upgrade action, including plugin paths.
 *
 * @return void
 */
function ssgs_plugin_upgrade_handler( $upgrader_object, $options ) {
	if ( $options['action'] === 'update' && $options['type'] === 'plugin' ) {
		if(isset($options['plugins']) && !empty($options['plugins'])) {
			if ( in_array('stock-sync-with-google-sheet-for-woocommerce/stock-sync-with-google-sheet-for-woocommerce.php', $options['plugins']) ) {
				update_option('ssgsw_license_sync', true);
			}
		}
		
	}
}


/**
 * Formats and appends data by comparing sheet headers with new data.
 * The function matches existing headers with new data, fills any empty slots
 * in the sheet headers with unmatched items from new data, and appends any
 * remaining unmatched new data.
 *
 * @param array $sheet_headers The existing sheet headers.
 * @param array $new_data The new data to be added or matched.
 * 
 * @return array The formatted data with matched headers and appended new data.
 */
function ssgsw_format_headers_with_sheet_header($sheet_headers, $colmuns) {
	$format_column = array_flip(array_map(function ($value) {
        return $value['label'];
    }, $colmuns));
    $formatted_data = [];
    $column_lookup = $format_column;
    foreach ($sheet_headers as $header) {
        if (isset($column_lookup[$header])) {
            $formatted_data[] = $header;
            unset($column_lookup[$header]); 
        }
    }
    $formatted_data = array_merge($formatted_data, array_keys($column_lookup));
	$keys = [];
	if($formatted_data) {
		foreach($formatted_data as $key => $value) {
			$keys[$key] = $format_column[$value];
		}
	}
	foreach ( $keys as &$value ) {
		$value = str_replace([' ', ',', '.', '?' ], '_', $value);
	}
    return [$keys, $formatted_data];
}

/**
 * Check if all specified conditions are met based on the provided data and values.
 *
 * This function evaluates whether all conditions, including "equals" and "not_equals",
 * are satisfied for the given set of data. It works with both single values and arrays.
 * 
 * It compares the conditions (like 'post_type', 'post_status', 'category') with the values provided.
 * If the value of a specific condition matches the "equals" condition and does not match the "not_equals" condition, it will return true.
 * If any condition fails, the function immediately returns false.
 * 
 * @param array $data The conditions to check. Contains keys like 'post_type', 'post_status', and 'category'.
 *                     Each key contains an array with 'equals' and 'not_equals' subarrays.
 * @param array $values The values to compare against. Each key corresponds to a condition in the `$data` array.
 * 
 * @return bool Returns `true` if all conditions are met, `false` if any condition fails.
 *
 * @example
 * $conditions = [
 *     'post_type' => [
 *         'equals' => [2],
 *         'not_equals' => []
 *     ],
 *     'post_status' => [
 *         'equals' => [],
 *         'not_equals' => ['draft']
 *     ],
 *     'category' => [
 *         'equals' => [197],
 *         'not_equals' => []
 *     ]
 * ];
 *
 * $post = [
 *     'post_type' => 2,
 *     'post_status' => 'publish',
 *     'category' => [5, 197]
 * ];
 *
 * $result = check_conditions($conditions, $post);
 * var_dump($result); // true if conditions are met, false otherwise
 */
function ssgsw_check_filter_conditions($condition_data, $product_id) {
	$values = ssgsw_get_filter_value_convarter($product_id);
	
    foreach ($condition_data as $key => $conditions) {

        if (!empty($conditions['equals']) && isset($values[$key])) {

            if (is_array($values[$key])) {
                $match = !empty(array_intersect($conditions['equals'], $values[$key]));
            } else {
                $match = in_array($values[$key], $conditions['equals']);
            }

            if (!$match) return false;
        }

 
        if (!empty($conditions['not_equals']) && isset($values[$key])) {
      
            if (is_array($values[$key])) {
                $match = !empty(array_intersect($conditions['not_equals'], $values[$key]));
            } else {
                $match = in_array($values[$key], $conditions['not_equals']);
            }

            if ($match) return false;
        }
    }

    return true;
}


/**
 * Conditionally decode a JSON string and return an array.
 *
 * @param string $json The JSON string to decode.
 * @return array Returns an associative array if valid JSON, or an empty array if invalid.
 */
function ssgsw_conditional_json_decode($product_id) {
	
}


/**
 * Checks if the WC Multivendor Marketplace plugin is active.
 * 
 * This function verifies whether the WC Multivendor Marketplace plugin 
 * is installed and activated in the WordPress installation.
 * 
 * @since 1.0.0
 * 
 * @return bool Returns true if the plugin is active, false otherwise.
 * 
 * @throws Exception If the WordPress plugin API is not available.
 */
function ssgsw_is_vendor_active() {
    if (!function_exists('is_plugin_active')) {
        if (!file_exists(ABSPATH . 'wp-admin/includes/plugin.php')) {
            throw new Exception('WordPress plugin API is not available');
        }
        include_once(ABSPATH . 'wp-admin/includes/plugin.php');
    }
    if ( is_plugin_active('wc-multivendor-marketplace/wc-multivendor-marketplace.php')) {
        return is_plugin_active('wc-multivendor-marketplace/wc-multivendor-marketplace.php');
    }
    if ( is_plugin_active('dokan-lite/dokan.php')) {
        return is_plugin_active('dokan-lite/dokan.php');
    }
	return false;
}

/**
 * Check if a specific page contains the [wc_frontend_manager] shortcode
 * 
 * @param int $page_id The page ID to check
 * @return bool True if the page contains the shortcode, false otherwise
 */
function ssgsw_has_wcfm_shortcode( $page_id ) {
    global $wpdb;
    
    // Validate input as numeric ID
    $page_id = absint( $page_id );
    if ( ! $page_id ) {
        return false;
    }
    
    // Return true if matching page exists
    return (bool) $wpdb->get_var( $wpdb->prepare(
        "SELECT ID FROM {$wpdb->posts} 
        WHERE ID = %d 
        AND post_content LIKE %s
        AND post_type = 'page' 
        AND post_status = 'publish' 
        LIMIT 1",
        $page_id,
        '%[wc_frontend_manager]%'
    ) );
}

/**
 * Updates the product_sync flag for all existing records in the SSGSW settings table
 * 
 * This function first checks if the ssgsw_setting table contains any records,
 * then updates the product_sync column to 1 (true) for all rows if records exist.
 * 
 * @since 1.0.0
 * 
 * @global wpdb $wpdb WordPress database abstraction object
 * 
 * @return array Returns an associative array containing:
 *               - 'success' (bool): Whether the operation completed successfully
 *               - 'rows_affected' (int): Number of rows updated (0 if none)
 *               - 'message' (string): Human-readable result message
 *               - 'error' (string|null): Error message if operation failed
 */
function ssgsw_update_product_sync_option() {
	global $wpdb;
	$has_values = $wpdb->get_var(
        $wpdb->prepare(
            "SELECT COUNT(*) FROM {$wpdb->prefix}ssgsw_setting"
        )
    );
	if($has_values) {
		$wpdb->query("UPDATE {$wpdb->prefix}ssgsw_setting SET product_sync = 1");
	}
}
/**
 * Checks if the current WordPress admin page is the custom 'ssgsw-admin' page.
 *
 * This function is crucial for conditionally loading scripts, styles, or specific
 * logic that should only execute on your plugin's dedicated administration page.
 * It leverages WordPress's global variables to accurately identify the page.
 *
 * @return bool True if the current page is 'ssgsw-admin', false otherwise.
 */
function is_ssgsw_admin_page() {
    global $pagenow;

    // First, check if we are in the admin area
    if ( is_admin() ) {
        // Then, check if the current file is 'admin.php'
        // This is where custom admin pages registered with add_menu_page or add_submenu_page often load
        if ( 'admin.php' === $pagenow ) {
            // Finally, check the 'page' query parameter
            if ( isset( $_GET['page'] ) && 'ssgsw-admin' === $_GET['page'] ) {
                return true;
            }
        }
    }
    return false;
}
/**
 * Check if the multi-vendor license is valid.
 *
 * Verifies the global license object exists and checks its validity status.
 * This function should be used to validate license status before performing
 * any premium operations.
 *
 * @since 1.0.0
 * 
 * @global object $ssgsw_multivendor Global license object instance.
 * 
 * @return bool Returns:
 *              - false if the global license object doesn't exist
 *              - The result of is_valid() call on the license object
 * 
 * @example
 * if (ssgsw_multi_vendor_license_valid()) {
 *     // License is valid, proceed with premium features
 * }
 */

 function ssgsw_multi_vendor_license_valid() {
    global $ssgsw_multivendor;
	if ( ! $ssgsw_multivendor ) {
		return false;
	}

	return $ssgsw_multivendor->is_valid();
}
/**
 * Update an option in the vendor table
 */
if ( ! function_exists('ssgsw_get_product_sync_user_id') ) {
	/**
	 * Update an option in the vendor table
	 *
	 * @param  string $option_name The option name.
	 * @param  mixed  $value       The value.
	 * @return bool
	 */
	function ssgsw_get_product_sync_user_id($user_id) {
		$old_data  = ssgsw_get_informations_all($user_id);
		if(!empty($old_data)) {
			$product_sync = $old_data['product_sync'];
			return $product_sync;
		}
		return false;
	}
}
/**
 * Update an option in the vendor table
 */
if ( ! function_exists('ssgsw_update_product_sync_user_id') ) {
	/**
	 * Update an option in the vendor table
	 *
	 * @param  string $option_name The option name.
	 * @param  mixed  $value       The value.
	 * @return bool
	 */
	function ssgsw_update_product_sync_user_id() {
		global $wpdb;
		$user_id = ssgsw_get_user_id();
		$old_data  = ssgsw_get_informations_all($user_id);
		if(!empty($old_data)) {
			$product_sync = $old_data['product_sync'];
			if ($product_sync) {
				$table_name = $wpdb->prefix . 'ssgsw_setting';
				$update_data = [
					'product_sync' => 0
				];
				$where_condition = [ 'vender_id' => $user_id ];
				$wpdb->update($table_name, $update_data, $where_condition);
			}
		}
	}
}

/**
 * Sends a POST request to a custom sales update endpoint on the same WordPress site.
 *
 * This function is designed to send product-related data, including a token ID and a vendor request flag,
 * to a predefined REST API endpoint. It retrieves an access token from WordPress options for authentication
 * in the request headers.
 *
 * @param int    $product_id     The ID of the product for which the sales update is being sent.
 * @param string $tokenId        A unique token ID associated with the update.
 * @param bool   $vendor_request Indicates whether the request originates from a vendor.
 * @return bool True on successful request (HTTP 200 OK response), false otherwise (e.g., missing token, WP_Error, non-200 response).
 */

function ssgsw_sales_update_post_request( $product_id, $tokenId, $vendor_request, $function_id = false ) {
    // $access_token = get_option( 'ssgsw_token' );
    // if ( ! $access_token ) {
    //     return;
    // }
    // Prepare your parameters (body of the POST request)
    $params = array(
        'product_id'     => $product_id,
        'token_id'       => $tokenId,
        'vendor_request' => $vendor_request,
        'function_id'    => $function_id,
    );
    
    // Define your endpoint URL on your own shop
    $request = new WP_REST_Request( 'POST', '/ssgsw/v1/sale_update' );
    $request->set_body_params( $params );

    // Get the API server instance
    $server = rest_get_server();

    // Call the API handler function directly
    $response = $server->dispatch( $request );
    // Check if the response is a WP_Error object
    if ( is_wp_error( $response ) ) {
        return false;
    } 
    if(is_array($response) || !is_object($response)) {
		return false;
	}
    // Get the status code from the object directly
    $response_code = $response->get_status();
    
    if ( $response_code !== 200 ) {
        return false;
    }

    // You can also get the data if needed
    // $data = $response->get_data();

    return true;
}

/**
 * Checks if a given user ID is a vendor in either Dokan or WCFM.
 * Users with 'manage_options' capability (typically Administrators and Super Admins)
 * are always considered not to be marketplace vendors.
 *
 * @param int $user_id The ID of the user to check.
 * @return bool True if the user is a vendor in Dokan or WCFM (and is NOT an Administrator/Super Admin), false otherwise.
 */
function sssgsw_is_any_marketplace_vendor( $user_id ) {
	$check_vendor_setup = ssgsw_get_informations_all($user_id);
	if ( is_array($check_vendor_setup) && empty($check_vendor_setup)) {
		return false;
	}
    // Check for Dokan vendor
    if ( function_exists( 'dokan_is_user_seller' ) && dokan_is_user_seller( $user_id ) ) {
        return true;
    }

    // Check for WCFM vendor
    if ( function_exists( 'wcfm_is_vendor' ) && wcfm_is_vendor( $user_id ) ) {
        return true;
    }

    return false; // Not a vendor in either detected marketplace, and not an Administrator/Super Admin.
}


/**
 * Checks if a vendor's setup information exists for a given user ID.
 *
 * This function retrieves vendor information and determines if the setup
 * is complete (i.e., information exists and is not an empty array).
 *
 * @param int $user_id The ID of the user (vendor) to check.
 * @return bool True if vendor setup information exists and is not empty, false otherwise.
 */
function ssgsw_vendor_setup_exits($user_id) {
    $check_vendor_setup = ssgsw_get_informations_all($user_id);

    // If the retrieved information is an array and it's empty, it means setup doesn't exist.
    if ( is_array($check_vendor_setup) && empty($check_vendor_setup)) {
        return false;
    }

    // Otherwise, assume setup exists (either it's not an array or it's a non-empty array).
    return true;
}


/**
 * Check if the multi-vendor license is valid.
 *
 * Verifies the global license object exists and checks its validity status.
 * This function should be used to validate license status before performing
 * any premium operations.
 *
 * @since 1.0.0
 * 
 * @global object $ssgsw_multivendor Global license object instance.
 * 
 * @return bool Returns:
 *              - false if the global license object doesn't exist
 *              - The result of is_valid() call on the license object
 * 
 * @example
 * if (ssgsw_multi_vendor_license_valid()) {
 *     // License is valid, proceed with premium features
 * }
 */

 function ssgsw_multi_store_license_valid() {
    global $ssgsw_multistore;
	if ( ! $ssgsw_multistore ) {
		return false;
	}

	return $ssgsw_multistore->is_valid();
}
/**
 * Extracts the base domain (scheme and host) from a given URL.
 *
 * This function parses a full URL and returns only the protocol (scheme, e.g., 'http' or 'https')
 * and the domain name (host, e.g., 'flexstocks.test'). It effectively removes any paths,
 * query strings, or fragments, providing a clean base URL similar to what `home_url()`
 * might return for the site's root.
 *
 * @since 1.0.0
 *
 * @param string $url The URL string to process (e.g., "http://flexstocks.test/wp-admin/admin.php?page=ssgsw-multistore").
 * @return string The base URL consisting of the scheme and host (e.g., "http://flexstocks.test").
 * Returns an empty string if the URL cannot be parsed or if the scheme or host are missing.
 */
function ssgsw_get_base_domain_from_url( $url ) {
    $parsed_url = wp_parse_url( $url );

    if ( $parsed_url && isset( $parsed_url['scheme'] ) && isset( $parsed_url['host'] ) ) {
        return $parsed_url['scheme'] . '://' . $parsed_url['host'];
    }
    return ''; // Return an empty string or handle error as appropriate if parsing fails
}


/**
 * Calculates the total product count across multiple shops, including sub-sites.
 *
 * This function iterates through an array of shop data and sums up the 'total_product'
 * value from each shop. It's designed to work with a shop list that might include
 * data from the main site and any associated sub-sites (if applicable, typically
 * in a WordPress Multisite environment where 'total_product' is pre-calculated
 * for each site/shop entry).
 *
 * @param array $shop_list An array of shop data. Each element in the array
 * should be an associative array with at least a 'total_product' key.
 * Example:
 * [
 * [
 * 'url'           => 'http://main-site.com',
 * 'total_product' => 150
 * ],
 * [
 * 'url'           => 'http://sub-site.com/shop',
 * 'total_product' => 75
 * ]
 * ]
 * @return int The grand total count of products from all shops in the provided list.
 * Returns 0 if the input is not an array, is empty, or if no 'total_product'
 * values are found or are valid numbers.
 */
function ssgsw_total_product_count_with_sub_site($shop_list = []) {
    $count = 0;
    if (is_array($shop_list) && !empty($shop_list)) {
        foreach ($shop_list as $key => $value) {
            // Ensure 'total_product' key exists and is a numeric value
            if (isset($value['total_product']) && is_numeric($value['total_product'])) {
                $count += $value['total_product'];
            }
        }
    }
    return $count;
}

/**
 * Checks and retrieves the current shop's information, allowing it to be filtered.
 *
 * This function initializes an array containing the current shop's URL and its total product count.
 * It then applies a filter, 'ssgsw_shop_list', to this array, allowing other parts of the
 * WordPress system to modify or extend the shop information before it's returned.
 *
 * @since 1.0.0
 *
 * @return array An array of shop information. By default, it contains:
 * [
 * [
 * 'site_url'      => (string) The URL of the current shop (obtained via home_url()).
 * 'total_product' => (int) The total number of products in the current shop
 * (obtained via ssgsw_count_all_product()).
 * ]
 * ]
 * This array can be modified by the 'ssgsw_shop_list' filter.
 */
function ssgsw_check_shop_name_list() {
	$current_shop = [
		[
			'url'      => home_url(),
			'total_product' => ssgsw_count_all_product()
		]
	];
	$shop_list = apply_filters('ssgsw_shop_list', $current_shop);

	return $shop_list;
}
/**
 * Checks if the plugin is activated
 *
 * @return bool
 */
 function ssgsw_multi_store_activated(){
	if ( ! function_exists('is_plugin_active') ) {
		include_once ABSPATH . 'wp-admin/includes/plugin.php';
	}
	// return true;
	// $plugin_uri = ;
	if (is_plugin_active('multi-store-sync-addon-for-flexstock/multi-store-sync-addon-for-flexstock.php') ) {
		return true;
	}
	return false;
}

/**
 * Prepares and formats sheet headers into a usable array of keys.
 *
 * This function takes the raw header data from a Google Sheet and combines it
 * with the plugin's defined column information to create a final array of
 * keys. These keys are used to map the sheet data to product fields.
 *
 * @param array $sheets_info An array containing all the sheet's data,
 * with the first element being the header row.
 * @param object $column     An object containing methods to retrieve plugin column information.
 * Expected to have a method named `get_columns_no_edit_remove`.
 * @return array The formatted array of keys extracted from the sheet headers.
 * Returns an empty array if no valid header information is found.
 */
function ssgsw_prepare_sheet_keys($sheets_info, $column) {
    // Safely get the sheet's header row.
    $sheet_header = isset($sheets_info[0]) ? (array) $sheets_info[0] : [];
    
    // Get the plugin's column information.
    $column_info = $column->get_columns_no_edit_remove();
    
    // Format the sheet headers using the plugin's column information.
    $keys_headers = ssgsw_format_headers_with_sheet_header($sheet_header, $column_info);
    
    // Safely get the final array of keys from the formatted headers.
    $keys_values = isset($keys_headers[0]) ? (array) $keys_headers[0] : [];

    return $keys_values;
}


/**
 * Sends a POST request to a custom sales update endpoint on the same WordPress site.
 *
 * This function is designed to send product-related data, including a token ID and a vendor request flag,
 * to a predefined REST API endpoint. It retrieves an access token from WordPress options for authentication
 * in the request headers.
 *
 * @param int    $product_id     The ID of the product for which the sales update is being sent.
 * @param string $tokenId        A unique token ID associated with the update.
 * @param bool   $vendor_request Indicates whether the request originates from a vendor.
 * @return bool True on successful request (HTTP 200 OK response), false otherwise (e.g., missing token, WP_Error, non-200 response).
 */

function ssgsw_formula_sync_request( $tokenId, $vendor_request) {
    // $access_token = get_option( 'ssgsw_token' );
    // if ( ! $access_token ) {
    //     return;
    // }
    // Prepare your parameters (body of the POST request)
    $params = array(
        'token_id'       => $tokenId,
        'vendor_request' => $vendor_request,
		'sync_formula'   => true,
    );
    
    // Define your endpoint URL on your own shop
    $request = new WP_REST_Request( 'POST', '/ssgsw/v1/sale_update' );
    $request->set_body_params( $params );

    // Get the API server instance
    $server = rest_get_server();

    // Call the API handler function directly
    $response = $server->dispatch( $request );
    // Check if the response is a WP_Error object
    if ( is_wp_error( $response ) ) {
        return false;
    } 
    if(is_array($response) || !is_object($response)) {
		return false;
	}
    // Get the status code from the object directly
    $response_code = $response->get_status();
    
    if ( $response_code !== 200 ) {
        return false;
    }

    // You can also get the data if needed
    // $data = $response->get_data();

    return true;
}


 /**
 * Maps a given key (typically a spreadsheet header) to a corresponding
 * WordPress field or database column name.
 *
 * This function uses a predefined mapping array to translate user-friendly
 * spreadsheet column headers into the internal names used for data processing
 * and storage in WordPress. If a key is not found in the map, the function
 * returns the original key, allowing for flexible or unmapped data fields.
 *
 * @param string $key The spreadsheet header key (e.g., 'Regular price') to be mapped.
 *
 * @return string The mapped WordPress field name (e.g., 'regular_price')
 * or the original key if no mapping is found.
 */
function ssgsw_format_sheet_key_function($key) {
	$sheet_to_wordpress_map = [
		"ID"                => "ID",
		"Type"              => "type",
		"Name"              => "name",
		"Stock"             => "stock",
		"Product URL"       => "ssgsw_product_url",
		"SKU"               => "sku",
		"Regular price"     => "regular_price",
		"Sale price"        => "sale_price",
		"Short description" => "post_excerpt",
		"Long description"  => "post_content",
		"Categories"        => "category",
		"No of Sales"       => "sales",
		"Attributes"        => "attributes",
		"Shop"              => "Shop",
	];
	if ( array_key_exists($key, $sheet_to_wordpress_map) ) {
		return $sheet_to_wordpress_map[ $key ];
	} else {
		return $key;
	}
}

/**
 * Get all product attributes in WooCommerce.
 *
 * @return array List of all unique product attributes.
 */
function ssgsw_get_all_attributes() {
	if ( ! function_exists( 'wc_get_attribute_taxonomies' ) ) {
		return [];
	}

	$attributes = [];
	
	// Global attributes
	$attribute_taxonomies = wc_get_attribute_taxonomies();
	if ( $attribute_taxonomies ) {
		// First pass: count label occurrences to detect duplicates
		$label_counts = [];
		foreach ( $attribute_taxonomies as $tax ) {
			$label = $tax->attribute_label ? $tax->attribute_label : $tax->attribute_name;
			if ( ! isset( $label_counts[ $label ] ) ) {
				$label_counts[ $label ] = 0;
			}
			$label_counts[ $label ]++;
		}

		// Second pass: build attributes array
		foreach ( $attribute_taxonomies as $tax ) {
			$name  = 'pa_' . $tax->attribute_name;
			$label = $tax->attribute_label ? $tax->attribute_label : $tax->attribute_name;

			// If label is duplicated, use the slug as the label to avoid confusion
			if ( isset( $label_counts[ $label ] ) && $label_counts[ $label ] > 1 ) {
				$display_label = $name;
			} else {
				$display_label = $label;
			}

			$attributes[] = [
				'slug'  => $name,
				'label' => $display_label . ' (Global Attribute)',
			];
		}
	}
	// error_log(print_r($attribute_taxonomies, true));

	// Local attributes from postmeta
	global $wpdb;
	$local_attributes = $wpdb->get_col( 
		"SELECT DISTINCT meta_key 
		FROM {$wpdb->postmeta} 
		WHERE meta_key LIKE 'attribute_%' 
		AND meta_key NOT LIKE 'attribute_pa_%'" 
	);
	// error_log(print_r($local_attributes, true));
	if ( $local_attributes ) {
		foreach ( $local_attributes as $meta_key ) {
			$slug = str_replace( 'attribute_', '', $meta_key );
			
			// Check if already added exactly (unlikely for local vs global)
			$found = false;
			foreach($attributes as $attr) {
				if($attr['slug'] === $slug) {
					$found = true;
					break;
				}
			}
			
			if (!$found) {
				$attributes[] = [
					'slug'  => $slug,
					'label' => ucwords( str_replace( [ 'pa_', '-', '_' ], [ '', ' ', ' ' ], $slug ) ) . ' (Custom Attribute)',
				];
			}
		}
	}

	return $attributes;
}

/**
 * Retrieve YITH Badge titles for Google Sheet dropdown.
 *
 * @return array
 */
function ssgsw_get_yith_badge_titles() {
	global $wpdb;

	$titles = $wpdb->get_col( $wpdb->prepare(
		"SELECT post_title FROM {$wpdb->posts} WHERE post_type = %s AND post_status = %s",
		'yith-wcbm-badge',
		'publish'
	) );

	return $titles ? $titles : [];
}
