<?php

/**
 * Class to import Products from Zoho to WooCommerce
 *
 * @package  zoho_inventory_api
 */
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore;

/**
 * Class to import Products from Zoho Inventory to WooCommerce.
 *
 * Handles syncing of simple products, variable products, grouped items,
 * and composite items from Zoho Inventory to WooCommerce.
 *
 * @package zoho_inventory_api
 */
class CMBIRD_Products_ZI {

	/**
	 * Configuration array for Zoho Inventory and WooCommerce settings.
	 *
	 * @var array
	 */
	private $config;

	/**
	 * Flag indicating whether WooCommerce taxes are enabled.
	 *
	 * @var bool
	 */
	private $is_tax_enabled;

	/**
	 * Constructor to initialize Zoho Inventory and WooCommerce configuration.
	 *
	 * Sets up API credentials, sync settings, and WooCommerce price formatting options.
	 * Exits early if WooCommerce is not active.
	 */
	public function __construct() {
		// return if WooCommerce plugin is not active.
		if ( ! is_plugin_active( 'woocommerce/woocommerce.php' ) ) {
			return;
		}

		$this->config = array(
			'ProductZI' => array(
				'OID'    => get_option( 'cmbird_zoho_inventory_oid' ),
				'APIURL' => get_option( 'cmbird_zoho_inventory_url' ),
			),
			'Settings'  => array(
				'disable_description'     => get_option( 'cmbird_zoho_disable_description_sync_status' ),
				'disable_name'            => get_option( 'cmbird_zoho_disable_name_sync_status' ),
				'disable_price'           => get_option( 'cmbird_zoho_disable_price_sync_status' ),
				'disable_stock'           => get_option( 'cmbird_zoho_disable_stock_sync_status' ),
				'enable_accounting_stock' => get_option( 'cmbird_zoho_enable_accounting_stock_status' ),
				'enable_location_stock'   => get_option( 'cmbird_zoho_enable_locationstock_status' ),
				'zoho_location_id'        => get_option( 'cmbird_zoho_location_id_status' ),
				'disable_image'           => get_option( 'cmbird_zoho_disable_image_sync_status' ),
			),
		);

		// Check if WooCommerce taxes are enabled and store the result.
		$this->is_tax_enabled = 'yes' === get_option( 'woocommerce_calc_taxes' );
	}

	/**
	 * Update or Create Custom Fields of Product
	 *
	 * @param array|object $custom_fields - item object coming in from simple item recursive.
	 * @param int          $pdt_id - product id.
	 * @return void
	 */
	public function sync_item_custom_fields( $custom_fields, $pdt_id ) {
		if ( empty( $custom_fields ) || empty( $pdt_id ) ) {
			return;
		}

		$item_field_mappings = $this->get_item_custom_field_mappings();
		$custom_fields       = $this->normalize_custom_fields_payload( $custom_fields );

		if ( empty( $custom_fields ) ) {
			return;
		}

		foreach ( $custom_fields as $custom_field ) {
			// Extract data from custom field.
			$api_name = (string) $this->get_custom_field_attribute( $custom_field, 'api_name' );
			$value    = $this->get_custom_field_attribute( $custom_field, 'value' );

			if ( $this->is_truthy_custom_field_flag( $custom_field, 'is_rich_text_supported' ) ) {
				$formatted_value = $this->get_custom_field_attribute( $custom_field, 'value_formatted', '' );
				if ( ! $this->is_empty_custom_field_value( $formatted_value ) ) {
					$value = $formatted_value;
				}
			}

			// Resolve Woo field key from saved item mapping first, then fallback to api_name.
			$resolved_key = $this->resolve_item_custom_field_key( $custom_field, $item_field_mappings );
			if ( empty( $resolved_key ) ) {
				$resolved_key = $api_name;
			}

			if ( $this->is_attachment_custom_field( $custom_field ) ) {
				$value = $this->sync_item_custom_field_attachment( $custom_field, $pdt_id );
			}

			$value = $this->decode_custom_field_value( $value );

			// Check if both field key and value are present.
			if ( ! empty( $resolved_key ) && ! $this->is_empty_custom_field_value( $value ) ) {
				$stored_in_meta = false;

				// Attempt ACF update first when available.
				if ( function_exists( 'update_field' ) ) {
					$acf_result = update_field( $resolved_key, $value, $pdt_id );

					// If ACF couldn't resolve/write this selector, persist directly as post meta.
					if ( false === $acf_result && ! metadata_exists( 'post', $pdt_id, $resolved_key ) ) {
						update_post_meta( $pdt_id, $resolved_key, $value );
						$stored_in_meta = true;
					}
				}

				if ( ! function_exists( 'update_field' ) ) {
					update_post_meta( $pdt_id, $resolved_key, $value );
					$stored_in_meta = true;
				}

				if ( ! $stored_in_meta && ! metadata_exists( 'post', $pdt_id, $resolved_key ) ) {
					update_post_meta( $pdt_id, $resolved_key, $value );
				}
			}
		}
		// Allow snippet code to execute custom logic after all field updates are processed.
		do_action( 'cmbird_zi_custom_field_updated', $pdt_id );
	}

	/**
	 * Returns a custom field attribute from either array or object payloads.
	 *
	 * @param array|object $custom_field Incoming Zoho custom field object/array.
	 * @param string       $key          Attribute name.
	 * @param mixed        $default      Default value.
	 * @return mixed
	 */
	private function get_custom_field_attribute( $custom_field, $key, $default = '' ) {
		if ( is_object( $custom_field ) && isset( $custom_field->{$key} ) ) {
			return $custom_field->{$key};
		}

		if ( is_array( $custom_field ) && array_key_exists( $key, $custom_field ) ) {
			return $custom_field[ $key ];
		}

		return $default;
	}

	/**
	 * Normalizes incoming custom fields payload to a list of field objects/arrays.
	 *
	 * @param mixed $custom_fields Incoming payload from Zoho item/webhook.
	 * @return array
	 */
	private function normalize_custom_fields_payload( $custom_fields ) {
		if ( is_object( $custom_fields ) && isset( $custom_fields->api_name ) ) {
			return array( $custom_fields );
		}

		if ( is_array( $custom_fields ) && isset( $custom_fields['api_name'] ) ) {
			return array( $custom_fields );
		}

		if ( is_array( $custom_fields ) ) {
			return $custom_fields;
		}

		return array();
	}

	/**
	 * Returns whether a custom-field flag is truthy.
	 *
	 * @param array|object $custom_field Incoming Zoho custom field object/array.
	 * @param string       $key          Flag attribute name.
	 * @return bool
	 */
	private function is_truthy_custom_field_flag( $custom_field, $key ) {
		$flag = $this->get_custom_field_attribute( $custom_field, $key, false );

		if ( is_bool( $flag ) ) {
			return $flag;
		}

		return '1' === (string) $flag || 'true' === strtolower( (string) $flag );
	}

	/**
	 * Determines whether the incoming custom field is a file attachment.
	 *
	 * @param array|object $custom_field Incoming Zoho custom field object/array.
	 * @return bool
	 */
	private function is_attachment_custom_field( $custom_field ) {
		$field_type_candidates = array(
			$this->get_custom_field_attribute( $custom_field, 'data_type' ),
			$this->get_custom_field_attribute( $custom_field, 'field_type' ),
			$this->get_custom_field_attribute( $custom_field, 'customfield_type' ),
			$this->get_custom_field_attribute( $custom_field, 'type' ),
			$this->get_custom_field_attribute( $custom_field, 'input_type' ),
		);

		foreach ( $field_type_candidates as $candidate ) {
			$candidate = strtolower( trim( (string) $candidate ) );
			if ( in_array( $candidate, array( 'attachment', 'file', 'file_upload', 'fileupload' ), true ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Downloads a Zoho attachment custom field into the media library and returns its URL.
	 *
	 * @param array|object $custom_field Incoming Zoho custom field object/array.
	 * @param int          $pdt_id       Product id.
	 * @return string
	 */
	private function sync_item_custom_field_attachment( $custom_field, $pdt_id ) {
		$document_id = $this->get_custom_field_attachment_document_id( $custom_field );

		if ( '' === $document_id ) {
			return '';
		}

		$existing_attachment_id = $this->find_attachment_by_zoho_document_id( $document_id );
		if ( $existing_attachment_id ) {
			$existing_url = wp_get_attachment_url( $existing_attachment_id );
			return $existing_url ? $existing_url : '';
		}

		$file_name          = $this->get_custom_field_attachment_file_name( $custom_field, $document_id );
		$zoho_inventory_oid = (string) $this->config['ProductZI']['OID'];
		$zoho_inventory_url = (string) $this->config['ProductZI']['APIURL'];
		$url                = $zoho_inventory_url . 'inventory/v1/documents/' . rawurlencode( $document_id ) . '?organization_id=' . rawurlencode( $zoho_inventory_oid );

		$execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
		$temp_file_path           = $execute_curl_call_handle->execute_curl_call_file_get( $url, $file_name, 'zoho_documents' );

		if ( is_wp_error( $temp_file_path ) || empty( $temp_file_path ) || ! file_exists( $temp_file_path ) ) {
			return '';
		}

		require_once ABSPATH . 'wp-admin/includes/media.php';
		require_once ABSPATH . 'wp-admin/includes/file.php';
		require_once ABSPATH . 'wp-admin/includes/image.php';

		$file_array = array(
			'name'     => sanitize_file_name( $file_name ),
			'tmp_name' => $temp_file_path,
		);

		$attachment_id = media_handle_sideload( $file_array, $pdt_id );
		if ( is_wp_error( $attachment_id ) ) {
			if ( file_exists( $temp_file_path ) ) {
				wp_delete_file( $temp_file_path );
			}
			return '';
		}

		update_post_meta( $attachment_id, 'cmbird_zoho_document_id', $document_id );

		if ( file_exists( $temp_file_path ) ) {
			wp_delete_file( $temp_file_path );
		}

		$attachment_url = wp_get_attachment_url( $attachment_id );

		return $attachment_url ? $attachment_url : '';
	}

	/**
	 * Returns the Zoho document identifier for an attachment custom field.
	 *
	 * @param array|object $custom_field Incoming Zoho custom field object/array.
	 * @return string
	 */
	private function get_custom_field_attachment_document_id( $custom_field ) {
		$value = $this->get_custom_field_attribute( $custom_field, 'value' );

		if ( is_scalar( $value ) ) {
			return trim( (string) $value );
		}

		if ( is_array( $value ) ) {
			foreach ( array( 'document_id', 'id', 'value', 0 ) as $key ) {
				if ( isset( $value[ $key ] ) && is_scalar( $value[ $key ] ) ) {
					return trim( (string) $value[ $key ] );
				}
			}
		}

		if ( is_object( $value ) ) {
			foreach ( array( 'document_id', 'id', 'value' ) as $property ) {
				if ( isset( $value->{$property} ) && is_scalar( $value->{$property} ) ) {
					return trim( (string) $value->{$property} );
				}
			}
		}

		return '';
	}

	/**
	 * Returns a best-effort file name for an attachment custom field.
	 *
	 * @param array|object $custom_field Incoming Zoho custom field object/array.
	 * @param string       $document_id  Zoho document id.
	 * @return string
	 */
	private function get_custom_field_attachment_file_name( $custom_field, $document_id ) {
		$file_name_candidates = array(
			$this->get_custom_field_attribute( $custom_field, 'file_name' ),
			$this->get_custom_field_attribute( $custom_field, 'document_name' ),
			$this->get_custom_field_attribute( $custom_field, 'attachment_name' ),
			$this->get_custom_field_attribute( $custom_field, 'value_formatted' ),
			$this->get_custom_field_attribute( $custom_field, 'label' ),
			$document_id,
		);

		foreach ( $file_name_candidates as $candidate ) {
			if ( is_scalar( $candidate ) ) {
				$candidate = sanitize_file_name( (string) $candidate );
				if ( '' !== $candidate ) {
					return $candidate;
				}
			}
		}

		return $document_id;
	}

	/**
	 * Finds an existing WordPress attachment by stored Zoho document id.
	 *
	 * @param string $document_id Zoho document id.
	 * @return int
	 */
	private function find_attachment_by_zoho_document_id( $document_id ) {
		$attachments = get_posts(
			array(
				'post_type'      => 'attachment',
				'post_status'    => 'inherit',
				'fields'         => 'ids',
				'posts_per_page' => 1,
				'meta_key'       => 'cmbird_zoho_document_id',
				'meta_value'     => $document_id,
			)
		);

		if ( empty( $attachments ) ) {
			return 0;
		}

		return (int) $attachments[0];
	}

	/**
	 * Returns saved item custom field mappings as Zoho-field => Woo-field lookup.
	 *
	 * @return array
	 */
	private function get_item_custom_field_mappings() {
		$stored_option = get_option( 'cmbird_wootozoho_custom_fields', array() );

		if ( is_string( $stored_option ) ) {
			$decoded = json_decode( $stored_option, true );
			if ( is_array( $decoded ) ) {
				$stored_option = $decoded;
			}
		}

		$item_rows = array();
		if ( is_array( $stored_option ) && isset( $stored_option['item'] ) && is_array( $stored_option['item'] ) ) {
			$item_rows = $stored_option['item'];
		}

		$lookup = array();
		foreach ( $item_rows as $woo_key => $row ) {
			$woo_field_key  = '';
			$zoho_field_key = '';

			// New shape: associative map { woo_key: zoho_field }.
			if ( is_string( $woo_key ) && ! is_array( $row ) ) {
				$woo_field_key  = trim( (string) $woo_key );
				$zoho_field_key = trim( (string) $row );
			}

			// Legacy/alternate shape: row array with key/value.
			if ( is_array( $row ) ) {
				$woo_field_key  = isset( $row['key'] ) ? trim( (string) $row['key'] ) : $woo_field_key;
				$zoho_field_key = isset( $row['value'] ) ? trim( (string) $row['value'] ) : $zoho_field_key;
			}

			if ( '' === $woo_field_key || '' === $zoho_field_key ) {
				continue;
			}

			$lookup[ $this->normalize_custom_field_identifier( $zoho_field_key ) ] = $woo_field_key;
		}

		return $lookup;
	}

	/**
	 * Resolves the destination Woo custom field key for an incoming Zoho field.
	 *
	 * @param array|object $custom_field Incoming Zoho custom field object/array.
	 * @param array        $lookup       Saved mapping lookup.
	 * @return string
	 */
	private function resolve_item_custom_field_key( $custom_field, $lookup ) {
		if ( empty( $lookup ) ) {
			return '';
		}

		$candidates = array();

		if ( is_object( $custom_field ) ) {
			if ( isset( $custom_field->field_name_formatted ) ) {
				$candidates[] = (string) $custom_field->field_name_formatted;
			}
			if ( isset( $custom_field->label ) ) {
				$candidates[] = (string) $custom_field->label;
			}
			if ( isset( $custom_field->field_name ) ) {
				$candidates[] = (string) $custom_field->field_name;
			}
			if ( isset( $custom_field->api_name ) ) {
				$candidates[] = (string) $custom_field->api_name;
			}
		} elseif ( is_array( $custom_field ) ) {
			if ( isset( $custom_field['field_name_formatted'] ) ) {
				$candidates[] = (string) $custom_field['field_name_formatted'];
			}
			if ( isset( $custom_field['label'] ) ) {
				$candidates[] = (string) $custom_field['label'];
			}
			if ( isset( $custom_field['field_name'] ) ) {
				$candidates[] = (string) $custom_field['field_name'];
			}
			if ( isset( $custom_field['api_name'] ) ) {
				$candidates[] = (string) $custom_field['api_name'];
			}
		}

		foreach ( $candidates as $candidate ) {
			$key = $this->normalize_custom_field_identifier( $candidate );
			if ( '' !== $key && isset( $lookup[ $key ] ) ) {
				return $lookup[ $key ];
			}
		}

		return '';
	}

	/**
	 * Normalizes field identifiers to support case-insensitive mapping lookup.
	 *
	 * @param string $identifier Identifier from saved mapping or Zoho payload.
	 * @return string
	 */
	private function normalize_custom_field_identifier( $identifier ) {
		return strtolower( trim( (string) $identifier ) );
	}

	/**
	 * Determines whether a custom field value should be considered empty.
	 *
	 * @param mixed $value Incoming value from Zoho.
	 * @return bool
	 */
	private function is_empty_custom_field_value( $value ) {
		return null === $value || '' === $value;
	}

	/**
	 * Decodes HTML entities for string custom field values.
	 *
	 * @param mixed $value Incoming value from Zoho.
	 * @return mixed
	 */
	private function decode_custom_field_value( $value ) {
		if ( ! is_string( $value ) ) {
			return $value;
		}

		return html_entity_decode( $value, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
	}

	/**
	 * Filters an incoming Zoho rate before it is saved as a WooCommerce regular price.
	 *
	 * @param mixed    $rate       Incoming Zoho rate.
	 * @param mixed    $item       Zoho item payload.
	 * @param int|null $product_id WooCommerce product or variation ID when known.
	 * @return mixed
	 */
	private function get_filtered_regular_price( $rate, $item, $product_id = null ) {
		return apply_filters( 'cmbird_zi_item_regular_price', $rate, $item, $product_id );
	}


	/**
	 * Function to add group items recursively by manual sync
	 *
	 * Accepts arguments via func_get_args(): page number and category ID for pagination.
	 *
	 * @return mixed
	 */
	public function sync_groupitem_recursively() {
		// $fd = fopen( __DIR__ . '/sync_groupitem_recursively.txt', 'a+' );

		$args = func_get_args();
		if ( ! empty( $args ) ) {
			if ( is_array( $args ) ) {
				if ( isset( $args['page'] ) && isset( $args['category'] ) ) {
					$page     = $args['page'];
					$category = $args['category'];
				} elseif ( isset( $args[0] ) && isset( $args[1] ) ) {
					$page     = $args[0];
					$category = $args[1];
				} elseif ( isset( $args[0] ) && ! isset( $args[1] ) ) {
					$page     = $args[0]['page'];
					$category = $args[0]['category'];
				} else {
					return;
				}
			} else {
				return;
			}

			// Memory management: Set memory limit if needed.
			if ( function_exists( 'wp_raise_memory_limit' ) ) {
				wp_raise_memory_limit( 'admin' );
			}

			global $wpdb;
			$zoho_inventory_oid = $this->config['ProductZI']['OID'];
			$zoho_inventory_url = $this->config['ProductZI']['APIURL'];
			$url                = $zoho_inventory_url . 'inventory/v1/itemgroups/?organization_id=' . $zoho_inventory_oid . '&category_id=' . $category . '&page=' . $page . '&per_page=20&filter_by=Status.Active';
			$execute_curl_call  = new CMBIRD_API_Handler_Zoho();
			$json               = $execute_curl_call->execute_curl_call_get( $url );
			$code               = $json->code;
			// $message = $json->message;
			$response_msg = array();

			if ( '0' === $code || 0 === $code ) {
				$zi_disable_description_sync = $this->config['Settings']['disable_description'];
				$zi_disable_name_sync        = $this->config['Settings']['disable_name'];
				// Resolve each group's WooCommerce category from its OWN Zoho category_id,
				// not the requested $category. The itemgroups endpoint does not reliably
				// constrain results to the requested category, so trusting $category here
				// re-tags unrelated parent products with the wrong category.
				$category_map  = new \CommerceBird\Admin\Mappings\ZohoCategoryMap();
				$term_id_cache = array();
				$resolve_term  = static function ( $zoho_category_id ) use ( $category_map, &$term_id_cache ) {
					$zoho_category_id = (string) $zoho_category_id;
					if ( '' === $zoho_category_id ) {
						return null;
					}
					if ( ! array_key_exists( $zoho_category_id, $term_id_cache ) ) {
						$term_id_cache[ $zoho_category_id ] = $category_map->term_id_for_zoho_id( $zoho_category_id );
					}
					return $term_id_cache[ $zoho_category_id ];
				};
				$processed_count = 0;
				foreach ( $json->itemgroups as $gp_arr ) {
					// Memory cleanup every 10 group items.
					if ( $processed_count > 0 && $processed_count % 10 === 0 ) {
						wp_cache_flush();
						if ( function_exists( 'gc_collect_cycles' ) ) {
							gc_collect_cycles();
						}
					}
					$zi_group_id   = $gp_arr->group_id;
					$zi_group_name = $gp_arr->group_name;
					// Resolve the category from this group's own Zoho category_id,
					// falling back to the requested category only when absent.
					$group_term_id = $resolve_term( ! empty( $gp_arr->category_id ) ? $gp_arr->category_id : $category );
					// skip if there is no first attribute.
					$zi_group_attribute1 = $gp_arr->attribute_id1;
					if ( empty( $zi_group_attribute1 ) ) {
						continue;
					}

					// Get Group ID.
					$group_id = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'zi_item_id' AND meta_value = %s LIMIT 1", $zi_group_id ) );
					if ( empty( $group_id ) ) {
						// search by zi_group_name if not found by zi_item_id.
						$group_id = $wpdb->get_var( $wpdb->prepare( "SELECT p.ID FROM $wpdb->posts p WHERE p.post_title LIKE %s AND p.post_type = 'product' AND p.post_status IN ('publish', 'draft', 'private') LIMIT 1", '%' . $wpdb->esc_like( $zi_group_name ) . '%' ) );
					}
					// array_push( $response_msg, $this->zi_response_message( 'SUCCESS', 'Zoho Group Item Synced: ' . $zi_group_name, $group_id ) );
					// end insert group product.
					// variable items.
					if ( ! empty( $group_id ) ) {
						$existing_parent_product = wc_get_product( $group_id );
						if ( ! empty( $gp_arr->description ) && ! $zi_disable_description_sync ) {
							$existing_parent_product->set_short_description( $gp_arr->description );
						}
						if ( ! empty( $gp_arr->name ) && ! $zi_disable_name_sync ) {
							$existing_parent_product->set_name( $gp_arr->name );
							// santize the name for slug and save the slug.
							$slug = sanitize_title( $gp_arr->name );
							$existing_parent_product->set_slug( $slug );
						}
						// add zi_category_id as meta.
						$existing_parent_product->update_meta_data( 'zi_category_id', $gp_arr->category_id );
						// add zi_item_id as meta.
						$existing_parent_product->update_meta_data( 'zi_item_id', $gp_arr->group_id );
						// set the product category.
						if ( ! empty( $group_term_id ) ) {
							wp_set_object_terms( $group_id, (int) $group_term_id, 'product_cat' );
							// remove the uncategorized term from the product.
							wp_remove_object_terms( $group_id, 'uncategorized', 'product_cat' );
							wp_remove_object_terms( $group_id, 'uncategorised', 'product_cat' );
						}
						// create attributes if not exists.
						$attributes = $existing_parent_product->get_attributes();
						if ( empty( $attributes ) ) {
							// Create or Update the Attributes.
							$attr_created = $this->sync_attributes_of_group( $gp_arr, $group_id );
						}
						$variations_check = $existing_parent_product->get_children();
						if ( empty( $variations_check ) ) {
							$this->import_variable_product_variations( $gp_arr, $group_id );
						}
						$existing_parent_product->save();
						// ACF Fields.
						if ( ! empty( $gp_arr->custom_fields ) ) {
							$this->sync_item_custom_fields( $gp_arr->custom_fields, $group_id );
						}
						// update Brand.
						if ( ! empty( $gp_arr->brand ) ) {
							// check if the Brand or Brands taxonomy exists and then update the term.
							if ( taxonomy_exists( 'product_brand' ) ) {
								wp_set_object_terms( $group_id, $gp_arr->brand, 'product_brand' );
							} elseif ( taxonomy_exists( 'product_brand' ) ) {
								wp_set_object_terms( $group_id, $gp_arr->brand, 'product_brand' );
							}
						}
					} else {
						// Create the parent variable product.
						$parent_product = new WC_Product_Variable();
						$parent_product->set_name( $zi_group_name );
						$parent_product->set_status( 'publish' );
						$parent_product->set_short_description( $gp_arr->description );
						$parent_product->add_meta_data( 'zi_item_id', $zi_group_id );
						$parent_product->add_meta_data( 'zi_category_id', ! empty( $gp_arr->category_id ) ? $gp_arr->category_id : $category );
						$group_id = $parent_product->save();
						// Sync category by finding it first.
						if ( $group_term_id && term_exists( $group_term_id, 'product_cat' ) ) {
							wp_set_object_terms( $group_id, (int) $group_term_id, 'product_cat' );
						}
						// update Brand.
						if ( ! empty( $gp_arr->brand ) ) {
							// check if the Brand or Brands taxonomy exists and then update the term.
							if ( taxonomy_exists( 'product_brand' ) ) {
								wp_set_object_terms( $group_id, $gp_arr->brand, 'product_brand' );
							} elseif ( taxonomy_exists( 'product_brand' ) ) {
								wp_set_object_terms( $group_id, $gp_arr->brand, 'product_brand' );
							}
						}
						// ACF Fields.
						if ( ! empty( $gp_arr->custom_fields ) ) {
							$this->sync_item_custom_fields( $gp_arr->custom_fields, $group_id );
						}
						// Create or Update the Attributes.
						$attr_created = $this->sync_attributes_of_group( $gp_arr, $group_id );

						if ( ! empty( $group_id ) && $attr_created ) {
							$this->import_variable_product_variations( $gp_arr, $group_id );
						}
					} // end of create variable product.

					++$processed_count;
				} // end foreach group items.

				// Final memory cleanup after processing all group items.
				wp_cache_flush();
				if ( function_exists( 'gc_collect_cycles' ) ) {
					gc_collect_cycles();
				}

				// Schedule next page if available.
				if ( isset( $json->page_context ) && $json->page_context->has_more_page ) {
					$data              = array(
						'page'     => $page + 1,
						'category' => $category,
					);
					$existing_schedule = as_has_scheduled_action( 'import_group_items_cron', $data, 'commercebird' );
					// Check if the scheduled action exists.
					if ( ! $existing_schedule ) {
						as_schedule_single_action( time(), 'import_group_items_cron', $data, 'commercebird' );
					}
				}

				// Store message before unsetting json.
				$message = isset( $json->message ) ? $json->message : '';
				// Clear any temporary variables.
				unset( $json, $execute_curl_call );
				array_push( $response_msg, $this->zi_response_message( $code, $message ) );
			}
			// End of logging.
			// fclose( $fd );
			return $response_msg;
		} else {
			return;
		}
	}

	/**
	 * Callback function for importing a variable product and its variations.
	 *
	 * @param object $gp_arr - Group item object.
	 * @param int    $group_id - Group item id.
	 * @return void
	 */
	public function import_variable_product_variations( $gp_arr, $group_id ): void {
		// $fd = fopen( __DIR__ . '/import_variable_product_variations.txt', 'a+' );

		if ( empty( $gp_arr ) || empty( $group_id ) ) {
			return;
		}

		global $wpdb;
		$product = wc_get_product( $group_id );

		if ( $product ) {
			$item_group      = $gp_arr;
			$items           = $item_group->items;
			$attribute_name1 = $item_group->attribute_name1;
			$attribute_name2 = $item_group->attribute_name2;
			$attribute_name3 = $item_group->attribute_name3;

			// fwrite( $fd, PHP_EOL . 'Items : ' . print_r( $items, true ) );
			// get the options for stock sync.
			$accounting_stock      = $this->config['Settings']['enable_accounting_stock'];
			$zi_disable_stock_sync = $this->config['Settings']['disable_stock'];
			$zi_disable_image_sync = $this->config['Settings']['disable_image'];

			$to_create        = array();
			$to_update        = array();
			$update           = false;
			$batch_count      = 0;
			$max_batch_size   = 100;
			$parent_image_set = false;

			foreach ( $items as $item ) {
				// reset this array.
				$attribute_arr = array();
				$variation_id  = '';
				$status        = $item->status === 'active' ? 'publish' : 'draft';

				$zi_item_id = $item->item_id;
				// Only consider existing records that are actual variation posts and belong to this parent.
				$variation_id = $wpdb->get_var(
					$wpdb->prepare(
						"SELECT p.ID FROM $wpdb->posts p
						 JOIN $wpdb->postmeta pm ON pm.post_id = p.ID
						 WHERE pm.meta_key = 'zi_item_id' AND pm.meta_value = %s
						 AND p.post_type = 'product_variation' AND p.post_parent = %d
						 LIMIT 1",
						$zi_item_id,
						$group_id
					)
				);

				if ( ! empty( $variation_id ) ) {
					$v_product = wc_get_product( $variation_id );
					// Check if the product object is valid.
					if ( $v_product && is_a( $v_product, 'WC_Product' ) ) {
						if ( $v_product->is_type( 'simple' ) ) {
							wp_delete_post( $variation_id, true );
						}
					}
				}
				// SKU check of the variation — if SKU exists on a SIMPLE product, remove that SIMPLE product (not the variation).
				if ( ! empty( $item->sku ) ) {
					$sku_prod_id = wc_get_product_id_by_sku( $item->sku );
					$v_product   = wc_get_product( $sku_prod_id );
					// Check if the product object is valid.
					if ( $v_product && is_a( $v_product, 'WC_Product' ) ) {
						if ( $v_product->is_type( 'simple' ) ) {
							wp_delete_post( $sku_prod_id, true );
						}
					}
				}
				if ( ! empty( $variation_id ) ) {
					$update = true;
				}
				$stock = 0;
				if ( $accounting_stock ) {
					$stock = $item->available_stock;
				} else {
					$stock = $item->actual_available_stock;
				}

				$attribute_name11 = $item->attribute_option_name1;
				$attribute_name12 = $item->attribute_option_name2;
				$attribute_name13 = $item->attribute_option_name3;
				// fwrite( $fd, PHP_EOL . '>>> Item: ' . $item->item_id . ' | SKU: ' . $item->sku . ' | opt1: ' . $attribute_name11 . ' | opt2: ' . $attribute_name12 . ' | opt3: ' . $attribute_name13 );
				// Prepare the variation data.
				if ( ! empty( $attribute_name1 ) ) {
					$sanitized_name1                   = wc_sanitize_taxonomy_name( $attribute_name1 );
					$attribute_arr[ $sanitized_name1 ] = $attribute_name11;
				}
				if ( ! empty( $attribute_name2 ) ) {
					$sanitized_name2                   = wc_sanitize_taxonomy_name( $attribute_name2 );
					$attribute_arr[ $sanitized_name2 ] = $attribute_name12;
				}
				if ( ! empty( $attribute_name3 ) ) {
					$sanitized_name3                   = wc_sanitize_taxonomy_name( $attribute_name3 );
					$attribute_arr[ $sanitized_name3 ] = $attribute_name13;
				}

				// Process and set variation attributes.
				$variation_attributes = array();
				foreach ( $attribute_arr as $attribute => $term_name ) {
					$taxonomy = 'pa_' . $attribute;

					// If taxonomy doesn't exist, create it.
					if ( ! taxonomy_exists( $taxonomy ) ) {
						register_taxonomy(
							$taxonomy,
							'product_variation',
							array(
								'hierarchical' => false,
								'label'        => ucfirst( $attribute ),
								'query_var'    => true,
								'rewrite'      => array( 'slug' => sanitize_title( $attribute ) ),
							),
						);
					}

					// Check if the term exists and create if needed.
					if ( ! term_exists( $term_name, $taxonomy ) ) {
						wp_insert_term( $term_name, $taxonomy );
					}

					// Get the term slug.
					$term_object = get_term_by( 'name', $term_name, $taxonomy );
					$term_slug   = $term_object ? $term_object->slug : sanitize_title( $term_name );

					// Get the post terms from the parent variable product.
					$post_term_names = wp_get_post_terms( $group_id, $taxonomy, array( 'fields' => 'names' ) );

					// Set the term on the parent product if not already set.
					if ( is_array( $post_term_names ) && ! in_array( $term_name, $post_term_names, true ) ) {
						wp_set_post_terms( $group_id, $term_name, $taxonomy, true );
					}

					// Build variation attributes array for batch API.
					$variation_attributes[ $taxonomy ] = array(
						'slug' => $term_slug,
						'name' => $term_name,
					);
				}

				$regular_price = $this->get_filtered_regular_price( $item->rate, $item, ! empty( $variation_id ) ? (int) $variation_id : null );

				// Prepare variation data for batch API.
				$variation_data = array(
					'type'          => 'variation',
					'status'        => $status,
					'regular_price' => (string) $regular_price,
					'sku'           => $item->sku,
				);

				// Set attributes.
				if ( ! empty( $variation_attributes ) ) {
					$variation_data['attributes'] = array();
					foreach ( $variation_attributes as $taxonomy => $term_data ) {
						$variation_data['attributes'][] = array(
							'id'     => wc_attribute_taxonomy_id_by_name( str_replace( 'pa_', '', $taxonomy ) ),
							'option' => $term_data['name'],
						);
					}
				}

				// Handle stock.
				if ( ! $zi_disable_stock_sync && $stock > 0 ) {
					$variation_data['manage_stock']   = true;
					$variation_data['stock_quantity'] = $stock;
				} else {
					$variation_data['manage_stock'] = false;
				}

				// Handle image. Check setting first, then confirm the item actually has an image in Zoho.
				if ( ! $zi_disable_image_sync && ! empty( $item->image_document_id ) && ! empty( $item->image_name ) ) {
					$image_class   = new CMBIRD_Image_ZI();
					$attachment_id = $image_class->cmbird_zi_get_image( $item->item_id, $item->name, $item->image_name, null );
					if ( $attachment_id ) {
						$variation_data['image'] = array( 'id' => $attachment_id );
						// Set parent product thumbnail if not already set.
						if ( ! $parent_image_set && ! has_post_thumbnail( $group_id ) ) {
							set_post_thumbnail( $group_id, $attachment_id );
							$parent_image_set = true;
						}
					}
				}

				// Add meta data.
				$variation_data['meta_data'] = array(
					array(
						'key'   => 'zi_item_id',
						'value' => $item->item_id,
					),
				);

				// Add purchase price if available.
				if ( ! empty( $item->purchase_rate ) ) {
					$variation_data['meta_data'][] = array(
						'key'   => 'cogs_total_value',
						'value' => $item->purchase_rate,
					);
				}

				// Add to batch array.
				if ( ! $update ) {
					$to_create[] = $variation_data;
				} else {
					$variation_data['id'] = $variation_id;
					$to_update[]          = $variation_data;
				}
				++$batch_count;

				// Process batch when we hit the limit.
				if ( $batch_count >= $max_batch_size ) {
					\CommerceBird\Admin\Actions\Sync\ZohoInventorySync::import(
						'variations',
						array(
							'create' => $to_create,
							'update' => $to_update,
						),
						false,
						'/wc/v3/products/' . $group_id . '/variations/batch'
					);
					$to_create   = array();
					$to_update   = array();
					$batch_count = 0;
				}
			}

			// Process remaining variations in the batch.
			if ( ! empty( $to_create ) || ! empty( $to_update ) ) {
				\CommerceBird\Admin\Actions\Sync\ZohoInventorySync::import(
					'variations',
					array(
						'create' => $to_create,
						'update' => $to_update,
					),
					false,
					'/wc/v3/products/' . $group_id . '/variations/batch'
				);

				// Ensure WooCommerce transient/cache & attribute lookup are up to date so variations appear in admin.
				wc_delete_product_transients( $group_id );
				$parent_product = wc_get_product( $group_id );
				if ( $parent_product ) {
					$lookup_data_store = new LookupDataStore();
					$lookup_data_store->create_data_for_product( $parent_product );
				}
			}
			// End group item add process.
			// array_push($response_msg, $this->zi_response_message('SUCCESS', 'Zoho variable item created for zoho item id ' . $zi_item_id, $variation_id));
			// End of Logging.
			// fclose( $fd );
		}
	}

	/**
	 * Update or Create the Product Attributes for the Variable Item Sync
	 *
	 * @param object $gp_arr - the group item object from Zoho Inventory.
	 * @param int    $group_id - the parent product id in WooCommerce.
	 * @return bool - true if attributes were created successfully, false otherwise
	 */
	public function sync_attributes_of_group( $gp_arr, $group_id ) {
		// $fd = fopen( __DIR__ . '/sync_attributes_of_group.txt', 'a+' );
		// Check if the group item has attributes.
		if ( empty( $gp_arr->attribute_name1 ) ) {
			return false;
		}
		// Create attributes.
		$success         = true; // Track the success of attribute creation.
		$attributes_data = array();
		$attribute_count = 0;

		// Loop through the attribute names.
		for ( $i = 1; $i <= 3; $i++ ) {
			$attribute_name_key        = 'attribute_name' . $i;
			$attribute_option_name_key = 'attribute_option_name' . $i;

			// Get the attribute name.
			$attribute_name = $gp_arr->$attribute_name_key;

			if ( ! empty( $attribute_name ) ) {
				// Check if the attribute is already added to the attributes array.
				if ( ! isset( $attributes_data[ $attribute_name ] ) ) {
					// Create the attribute and add it to the attributes array.
					$attribute = array(
						'name'      => $attribute_name,
						'position'  => $attribute_count,
						'visible'   => true,
						'variation' => true,
						'options'   => array(),
					);

					// Loop through the items and retrieve attribute options.
					$attribute_options = array();
					foreach ( $gp_arr->items as $item ) {
						$attribute_option = $item->$attribute_option_name_key;
						if ( ! empty( $attribute_option ) && ! in_array( $attribute_option, $attribute_options, true ) ) {
							$attribute_options[] = $attribute_option;
						}
					}

					// Set the attribute options.
					$attribute['options'] = $attribute_options;

					$attributes_data[] = $attribute;
					++$attribute_count;
				}
			}
		}
		// fwrite( $fd, PHP_EOL . '$attributes : ' . print_r( $attributes_data, true ) );

		// Assign the attributes to the parent product.
		if ( count( $attributes_data ) > 0 ) {
			$product_attributes = array();

			// Loop through defined attribute data.
			foreach ( $attributes_data as $key => $attribute_array ) {
				if ( isset( $attribute_array['name'] ) && isset( $attribute_array['options'] ) ) {
					// Clean attribute name to get the taxonomy.
					$taxonomy        = 'pa_' . wc_sanitize_taxonomy_name( $attribute_array['name'] );
					$option_term_ids = array();
					$existing_terms  = array();
					// Create the attribute if it doesn't exist.
					if ( ! taxonomy_exists( $taxonomy ) ) {
						// Clean attribute label for better display.
						$attribute_label = ucfirst( $attribute_array['name'] );

						// Register the new attribute taxonomy.
						$attribute_args = array(
							'slug'         => $taxonomy,
							'name'         => $attribute_label,
							'type'         => 'select',
							'order_by'     => 'menu_order',
							'has_archives' => false,
						);

						$result = wc_create_attribute( $attribute_args );
						register_taxonomy( $taxonomy, array( 'product' ), array() );

						if ( ! is_wp_error( $result ) ) {
							// fwrite($fd, PHP_EOL . 'result : ' . $result);
							// Loop through defined attribute data options (terms values).
							foreach ( $attribute_array['options'] as $option ) {
								// Check if the term exists for the attribute taxonomy.
								$term = term_exists( $option, $taxonomy );
								// Also check by slug in case option is slug-formatted.
								$term_by_slug = get_term_by( 'slug', sanitize_title( $option ), $taxonomy );
								if ( $term_by_slug ) {
									$term = array( 'term_id' => $term_by_slug->term_id );
								}
								if ( ! $term ) {
									// Term doesn't exist, create a new one.
									// Convert slug-formatted names to proper names.
									$term_name = str_replace( array( '-', '_' ), ' ', $option );
									$term_name = ucwords( $term_name );
									$term      = wp_insert_term(
										$term_name,
										$taxonomy,
										array(
											'slug' => sanitize_title( $term_name ),
										)
									);
									if ( is_wp_error( $term ) ) {
										// fwrite( $fd, PHP_EOL . 'Error creating term: ' . $term->get_error_message() );
										continue;
									}
								}
								// Add term ID to the array for assignment to parent product.
								$term_id           = is_array( $term ) ? $term['term_id'] : $term;
								$option_term_ids[] = $term_id;
							}
						} else {
							$success = false;
							// return error message.
							$error_string = $result->get_error_message();
							return $success;
						}
					} else {
						// Taxonomy exists, get existing terms.
						$existing_terms = get_terms(
							array(
								'taxonomy'   => $taxonomy,
								'hide_empty' => false,
							)
						);
					}

					if ( $existing_terms ) {
						foreach ( $attribute_array['options'] as $option ) {
							$match_found = false;
							foreach ( $existing_terms as $existing_term ) {
								// Check both name and slug to handle cases where Zoho sends slug-formatted names.
								if ( $existing_term->name === $option || $existing_term->slug === sanitize_title( $option ) ) {
									$option_term_ids[] = $existing_term->term_id;
									$match_found       = true;
									break;
								}
							}
							// fwrite( $fd, PHP_EOL . 'Option "' . $option . '" match found: ' . ( $match_found ? 'Yes' : 'No' ) );
							if ( ! $match_found ) {
								// Check if the term exists for the attribute taxonomy.
								$term = get_term_by( 'slug', sanitize_title( $option ), $taxonomy );
								// fwrite( $fd, PHP_EOL . 'Term by slug check for option "' . $option . '": ' . print_r( $term, true ) );
								if ( $term ) {
									$term = array( 'term_id' => $term->term_id );
								}
								if ( ! $term ) {
									// Term doesn't exist, create it.
									// Convert slug-formatted names to proper names.
									$term_name = str_replace( array( '-', '_' ), ' ', $option );
									$term_name = ucwords( $term_name );
									$term      = wp_insert_term( $term_name, $taxonomy );
									if ( ! is_wp_error( $term ) ) {
										// Get the term ID.
										$term_id           = is_array( $term ) ? $term['term_id'] : $term;
										$option_term_ids[] = $term_id;
									} else {
										$success = false;
									}
								} else {
									// Get the existing term ID.
									$term_id           = $term['term_id'];
									$option_term_ids[] = $term_id;
								}
							}
						}
					}
					// Set the selected terms for the product.
					// fwrite( $fd, PHP_EOL . '[SYNC_ATTR] Setting terms for taxonomy: ' . $taxonomy . ' | Term IDs: ' . print_r( $option_term_ids, true ) );
					wp_set_object_terms( $group_id, $option_term_ids, $taxonomy, false );

					$attribute_object = new WC_Product_Attribute();
					$attribute_object->set_id( wc_attribute_taxonomy_id_by_name( $taxonomy ) );
					$attribute_object->set_name( $taxonomy );
					$attribute_object->set_options( $option_term_ids );
					$attribute_object->set_visible( $attribute_array['visible'] ?? true );
					$attribute_object->set_variation( $attribute_array['variation'] ?? true );
					$product_attributes[] = $attribute_object;
				}
			}
		}

		// Save the attributes to the parent product using WooCommerce 10+ API.
		if ( ! empty( $product_attributes ) ) {
			$product = wc_get_product( $group_id );
			if ( $product ) {
				$product->set_attributes( $product_attributes );
				$product->save();
			}
		}
		// fclose($fd);
		return $success;
	}

	/**
	 * Update or create variation in WooCommerce if Group-ID already exists in wpdB
	 *
	 * @param object $item - item object coming in from simple item recursive function.
	 * @return: void
	 */
	public function sync_variation_of_group( $item ) {
		// $fd = fopen( __DIR__ . '/sync_variation_of_group.txt', 'a+' );

		// log the item.
		// fwrite( $fd, PHP_EOL . 'Item : ' . print_r( $item, true ) );

		global $wpdb;
		// Stock mode check.
		$zi_disable_stock_sync = $this->config['Settings']['disable_stock'];
		$accounting_stock      = $this->config['Settings']['enable_accounting_stock'];
		$disable_image_sync    = $this->config['Settings']['disable_image'];
		$is_tax_enabled        = $this->is_tax_enabled;

		if ( $accounting_stock ) {
			$stock = $item->available_stock;
		} else {
			$stock = $item->actual_available_stock;
		}

		$item_id = $item->item_id;
		// $item_category = $item->category_name;
		$groupid = property_exists( $item, 'group_id' ) ? $item->group_id : 0;
		// find parent variable product.
		$group_id = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'zi_item_id' AND meta_value = %s LIMIT 1", $groupid ) );
		if ( empty( $group_id ) ) {
			// search by group name if not found by group id.
			$group_id = $wpdb->get_var( $wpdb->prepare( "SELECT p.ID FROM $wpdb->posts p WHERE p.post_title LIKE %s AND p.post_type = 'product' AND p.post_status IN ('publish', 'draft', 'private') LIMIT 1", '%' . $wpdb->esc_like( $item->group_name ) . '%' ) );
		}
		$stock_quantity = $stock < 0 ? 0 : $stock;
		// fwrite($fd, PHP_EOL . 'Before group item sync : ' . $group_id);
		if ( ! empty( $group_id ) ) {
			// fwrite( $fd, PHP_EOL . 'Inside item sync : ' . $item->name );
			// Brand.
			if ( isset( $item->brand ) && ! empty( $group_id ) ) {
				if ( taxonomy_exists( 'product_brand' ) ) {
					wp_set_object_terms( $group_id, $item->brand, 'product_brand' );
				}
			}
			// update the zi_item_id of parent variable product if not already set.
			$existing_zi_item_id = get_post_meta( $group_id, 'zi_item_id', true );
			if ( empty( $existing_zi_item_id ) ) {
				update_post_meta( $group_id, 'zi_item_id', $groupid );
			}

			// Only accept a matching variation that belongs to this parent product.
			$variation_id = $wpdb->get_var(
				$wpdb->prepare(
					"SELECT p.ID FROM $wpdb->posts p
					 JOIN $wpdb->postmeta pm ON pm.post_id = p.ID
					 WHERE pm.meta_key = 'zi_item_id' AND pm.meta_value = %s
					 AND p.post_type = 'product_variation' AND p.post_parent = %d
					 LIMIT 1",
					$item_id,
					$group_id
				)
			);
			if ( ! empty( $item->sku ) && empty( $variation_id ) ) {
				$variation_id = wc_get_product_id_by_sku( $item->sku );
			}
			if ( $variation_id ) {
				// fwrite( $fd, PHP_EOL . 'Variation ID exists : ' . $variation_id );
				// if product type is not variation then return.
				$v_product = wc_get_product( $variation_id );
				// Check if the product object is valid.
				if ( $v_product && is_a( $v_product, 'WC_Product' ) ) {
					if ( $v_product->is_type( 'simple' ) ) {
						wp_delete_post( $variation_id, true );
						$variation_id = '';
						// Continue to create new variation below.
					}
				}
				// If variation already exists then update it.
				$variation = new WC_Product_Variation( $variation_id );
				// SKU - Imported.
				if ( ! empty( $item->sku ) ) {
					$variation->set_sku( $item->sku );
				}
				// update purchase price as meta data.
				if ( ! empty( $item->purchase_rate ) ) {
					$variation->update_meta_data( 'cogs_total_value', $item->purchase_rate );
				}
				// Price - Imported.
				$zi_disable_price_sync = $this->config['Settings']['disable_price'];
				if ( ! $zi_disable_price_sync ) {
					$variation->set_regular_price( $this->get_filtered_regular_price( $item->rate, $item, (int) $variation_id ) );
				}
				// Set Tax Class.
				if ( $item->tax_id && $is_tax_enabled ) {
					$zi_common_class = new CMBIRD_Common_Functions();
					$woo_tax_class   = $zi_common_class->get_tax_class_by_zoho_id( $item->tax_id )
								?? $zi_common_class->get_tax_class_by_percentage( $item->tax_percentage );
					$variation->set_tax_status( 'taxable' );
					$variation->set_tax_class( $woo_tax_class );
				}
				// Stock Imported code.
				if ( ! $zi_disable_stock_sync && is_numeric( $stock_quantity ) ) {
					// fwrite( $fd, PHP_EOL . 'Stock Quantity : ' . $stock_quantity );
					$variation->set_manage_stock( true );
					if ( $stock_quantity > 0 ) {
						$variation->set_manage_stock( true );
						$variation->set_stock_quantity( $stock_quantity );
						$variation->set_stock_status( 'instock' );
					} elseif ( $stock_quantity <= 0 ) {
						$variation->set_manage_stock( true );
						$variation->set_stock_quantity( $stock_quantity );
						$stock_status = $variation->backorders_allowed() ? 'onbackorder' : 'outofstock';
						$variation->set_stock_status( $stock_status );
					}
				}
				// Update variation attributes if missing or need updating.
				$attribute_name1 = $item->attribute_option_name1;
				$attribute_name2 = $item->attribute_option_name2;
				$attribute_name3 = $item->attribute_option_name3;

				// Prepare the variation data.
				$attribute_arr = array();
				if ( ! empty( $attribute_name1 ) ) {
					$sanitized_name1                   = wc_sanitize_taxonomy_name( $item->attribute_name1 );
					$attribute_arr[ $sanitized_name1 ] = $attribute_name1;
				}
				if ( ! empty( $attribute_name2 ) ) {
					$sanitized_name2                   = wc_sanitize_taxonomy_name( $item->attribute_name2 );
					$attribute_arr[ $sanitized_name2 ] = $attribute_name2;
				}
				if ( ! empty( $attribute_name3 ) ) {
					$sanitized_name3                   = wc_sanitize_taxonomy_name( $item->attribute_name3 );
					$attribute_arr[ $sanitized_name3 ] = $attribute_name3;
				}

				// Update attributes for the variation.
				$variation_attributes = array();
				foreach ( $attribute_arr as $attribute => $term_name ) {
					$taxonomy = 'pa_' . $attribute;

					// If taxonomy doesn't exist, create it.
					if ( ! taxonomy_exists( $taxonomy ) ) {
						register_taxonomy(
							$taxonomy,
							'product_variation',
							array(
								'hierarchical' => false,
								'label'        => ucfirst( $attribute ),
								'query_var'    => true,
								'rewrite'      => array( 'slug' => sanitize_title( $attribute ) ),
							),
						);
					}

					// Check if the term exists and create if needed.
					if ( ! term_exists( $term_name, $taxonomy ) ) {
						wp_insert_term( $term_name, $taxonomy );
					}

					// Get the term slug.
					$term_object = get_term_by( 'name', $term_name, $taxonomy );
					$term_slug   = $term_object ? $term_object->slug : sanitize_title( $term_name );

					// Get the post terms from the parent variable product.
					$post_term_names = wp_get_post_terms( $group_id, $taxonomy, array( 'fields' => 'names' ) );

					// Set the term on the parent product if not already set.
					if ( is_array( $post_term_names ) && ! in_array( $term_name, $post_term_names, true ) ) {
						wp_set_post_terms( $group_id, $term_name, $taxonomy, true );
					}

					// Build variation attributes array for set_attributes().
					$variation_attributes[ $taxonomy ] = $term_slug;
				}

				// Set all attributes at once using WooCommerce 10+ API.
				if ( ! empty( $variation_attributes ) ) {
					$variation->set_attributes( $variation_attributes );
				}

				// Featured Image of variation.
				if ( ! empty( $item->image_document_id ) && ! $disable_image_sync ) {
					$image_class        = new CMBIRD_Image_ZI();
					$variation_image_id = $image_class->cmbird_zi_get_image( $item->item_id, $item->name, $item->image_name, $variation_id );
					if ( $variation_image_id ) {
						// Set the image on the variation object so the save() below persists
						// it. cmbird_zi_get_image() already wrote _thumbnail_id directly, but
						// this stale object (loaded before the image existed) would otherwise
						// overwrite it with an empty image_id on save.
						$variation->set_image_id( $variation_image_id );
						// Fall back to using it as the parent's featured image only if it has none.
						if ( ! has_post_thumbnail( $group_id ) ) {
							set_post_thumbnail( $group_id, $variation_image_id );
						}
					}
				}
				// enable or disable based on status from Zoho.
				$status = ( 'active' === $item->status ) ? 'publish' : 'draft';
				$variation->set_status( $status );
				$variation->save();
				// clear cache.
				wc_delete_product_transients( $variation_id );
			} else {
				// create new variation.
				// if status is not active then return.
				if ( 'active' !== $item->status ) {
					return;
				}

				$attribute_name1 = $item->attribute_option_name1;
				$attribute_name2 = $item->attribute_option_name2;
				$attribute_name3 = $item->attribute_option_name3;
				// Prepare the variation data.
				$attribute_arr = array();
				if ( ! empty( $attribute_name1 ) ) {
					$sanitized_name1                   = wc_sanitize_taxonomy_name( $item->attribute_name1 );
					$attribute_arr[ $sanitized_name1 ] = $attribute_name1;
				}
				if ( ! empty( $attribute_name2 ) ) {
					$sanitized_name2                   = wc_sanitize_taxonomy_name( $item->attribute_name2 );
					$attribute_arr[ $sanitized_name2 ] = $attribute_name2;
				}
				if ( ! empty( $attribute_name3 ) ) {
					$sanitized_name3                   = wc_sanitize_taxonomy_name( $item->attribute_name3 );
					$attribute_arr[ $sanitized_name3 ] = $attribute_name3;
				}
				// fwrite( $fd, PHP_EOL . 'Attributes_arr: ' . print_r( $attribute_arr, true ) );

				// here actually create new variation because sku not found.
				$variation = new WC_Product_Variation();
				$variation->set_parent_id( $group_id );
				$variation->set_status( 'publish' );
				$variation->set_regular_price( $this->get_filtered_regular_price( $item->rate, $item, null ) );
				$variation->set_sku( $item->sku );
				if ( ! $zi_disable_stock_sync ) {
					$variation->set_stock_quantity( $stock_quantity );
					$variation->set_manage_stock( true );
					$variation->set_stock_status( '' );
				} else {
					$variation->set_manage_stock( false );
				}
				$variation->add_meta_data( 'zi_item_id', $item->item_id );
				$variation_id = $variation->save();

				// Get the variation attributes with correct attribute values.
				$variation_attributes = array();
				foreach ( $attribute_arr as $attribute => $term_name ) {
					$taxonomy = 'pa_' . $attribute;

					// If taxonomy doesn't exist, create it.
					if ( ! taxonomy_exists( $taxonomy ) ) {
						register_taxonomy(
							$taxonomy,
							'product_variation',
							array(
								'hierarchical' => false,
								'label'        => ucfirst( $attribute ),
								'query_var'    => true,
								'rewrite'      => array( 'slug' => sanitize_title( $attribute ) ),
							),
						);
					}

					// Check if the term exists and create if needed.
					if ( ! term_exists( $term_name, $taxonomy ) ) {
						wp_insert_term( $term_name, $taxonomy );
					}

					// Get the term slug.
					$term_object = get_term_by( 'name', $term_name, $taxonomy );
					$term_slug   = $term_object ? $term_object->slug : sanitize_title( $term_name );

					// Get the post terms from the parent variable product.
					$post_term_names = wp_get_post_terms( $group_id, $taxonomy, array( 'fields' => 'names' ) );

					// Set the term on the parent product if not already set.
					if ( is_array( $post_term_names ) && ! in_array( $term_name, $post_term_names, true ) ) {
						wp_set_post_terms( $group_id, $term_name, $taxonomy, true );
					}

					// Build variation attributes array for set_attributes().
					$variation_attributes[ $taxonomy ] = $term_slug;
				}

				// Set all attributes at once using WooCommerce 10+ API.
				if ( ! empty( $variation_attributes ) ) {
					$variation->set_attributes( $variation_attributes );
					$variation->save();
				}

				// update purchase price as meta data.
				if ( ! empty( $item->purchase_rate ) ) {
					$variation->update_meta_data( 'cogs_total_value', $item->purchase_rate );
				}
				// Stock.
				if ( ! empty( $stock ) && ! $zi_disable_stock_sync ) {
					update_post_meta( $variation_id, 'manage_stock', true );
					if ( $stock > 0 ) {
						update_post_meta( $variation_id, '_stock', $stock );
						update_post_meta( $variation_id, '_stock_status', 'instock' );
					} else {
						$backorder_status = get_post_meta( $group_id, '_backorders', true );
						update_post_meta( $variation_id, '_stock', $stock );
						if ( 'yes' === $backorder_status ) {
							update_post_meta( $variation_id, '_stock_status', 'onbackorder' );
						} else {
							update_post_meta( $variation_id, '_stock_status', 'outofstock' );
						}
					}
				}
				// Featured Image of variation.
				if ( ! empty( $item->image_document_id ) && ! $disable_image_sync ) {
					$image_class        = new CMBIRD_Image_ZI();
					$variation_image_id = $image_class->cmbird_zi_get_image( $item->item_id, $item->name, $item->image_name, $variation_id );
					if ( $variation_image_id ) {
						// Link the image to the variation itself and persist it.
						$variation->set_image_id( $variation_image_id );
						$variation->save();
						// Fall back to using it as the parent's featured image only if it has none.
						if ( ! has_post_thumbnail( $group_id ) ) {
							set_post_thumbnail( $group_id, $variation_image_id );
						}
					}
				}
				update_post_meta( $variation_id, 'zi_item_id', $item_id );
				// WC_Product_Variable::sync( $group_id );
				// Regenerate lookup table for attributes.
				$parent_product = wc_get_product( $group_id );
				if ( $parent_product ) {
					$lookup_data_store = new LookupDataStore();
					$lookup_data_store->create_data_for_product( $parent_product );
				}
				// End group item add process.
				unset( $attribute_arr );
			}
			// end of grouped item updating.
		}
		unset( $item );
	}

	/**
	 * Helper Function to check if child of composite items already synced  or not
	 *
	 * @param string $composite_zoho_id - zoho composite item id to check if it's child are already synced.
	 * @param string $zi_url - zoho api url.
	 * @param string $zi_org_id - zoho organization token.
	 * @param string $prod_id - woocommerce product id.
	 * @return array | bool of child id and metadata if child item already synced else will return false.
	 */
	public function zi_check_if_child_synced_already( $composite_zoho_id, $zi_url, $zi_org_id, $prod_id ) {
		if ( $prod_id ) {
			$bundle_childs = WC_PB_DB::query_bundled_items(
				array(
					'return'    => 'id=>product_id',
					'bundle_id' => array( $prod_id ),
				)
			);
		}
		global $wpdb;

		$url = $zi_url . 'inventory/v1/compositeitems/' . $composite_zoho_id . '?organization_id=' . $zi_org_id;

		$execute_curl_call = new CMBIRD_API_Handler_Zoho();
		$json              = $execute_curl_call->execute_curl_call_get( $url );
		$code              = $json->code;
		// Flag to allow sync of parent composite item.
		$allow_sync = false;
		// Array of child object metadata.
		$product_array = array(); // [{prod_id:'',metadata:{key:'',value:''}},...].
		if ( '0' === $code || 0 === $code ) {
			foreach ( $json->composite_item->mapped_items as $child_item ) {
				$prod_meta = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->postmeta WHERE meta_key = 'zi_item_id' AND meta_value = %s", $child_item->item_id ) );
				// If any child will not have zoho id in meta field then process will return false and syncing will be skipped for given item.
				if ( ! empty( $prod_meta->post_id ) ) {

					$allow_sync   = true;
					$product      = wc_get_product( $prod_meta->post_id );
					$stock_status = 'out_of_stock';
					if ( $child_item->stock_on_hand > 0 ) {
						$stock_status = 'in_stock';
					} elseif ( $product && $product->backorders_allowed() ) {
						$stock_status = 'onbackorder';
					}
					$prod_obj = (object) array(
						'prod_id'  => $prod_meta->post_id,
						'metadata' => (object) array(
							'quantity_min' => max( 1, $child_item->quantity ),
							'quantity_max' => max( 1, $child_item->quantity ),
							'stock_status' => $stock_status,
							'max_stock'    => $child_item->stock_on_hand,
						),
					);
					if ( is_array( $bundle_childs ) && ! empty( $bundle_childs ) ) {
						$index = array_search( $prod_meta->post_id, $bundle_childs );
						unset( $bundle_childs[ $index ] );
					}
					array_push( $product_array, $prod_obj );
				} else {
					continue;
				}
			}
		}
		if ( is_array( $bundle_childs ) && ! empty( $bundle_childs ) ) {
			foreach ( $bundle_childs as $item_id => $val ) {
				WC_PB_DB::delete_bundled_item( $item_id );
			}
		}
		if ( $allow_sync ) {
			return $product_array;
		}
		return false;
	}
	/**
	 * Mapping of bundled product
	 *
	 * @param number $product_id - Product id of child item of bundle product.
	 * @param number $bundle_id  - Bundle id of product.
	 * @param number $menu_order - Listing order of child product ($menu_order will useful at composite product details page).
	 * @return number - bundle item id.
	 */
	public function add_bundle_product( $product_id, $bundle_id, $menu_order = 0 ) {
		$bundle_items = WC_PB_DB::query_bundled_items(
			array(
				'return'     => 'id=>product_id',
				'bundle_id'  => array( $bundle_id ),
				'product_id' => array( $product_id ),
			)
		);
		$data         = array(
			'menu_order' => $menu_order,
		);

		if ( count( $bundle_items ) > 0 ) {
			$result = WC_PB_DB::update_bundled_item( $bundle_id, $data );
			return $result;
		} else {
			// create data array of bundle item.
			$data      = array(
				'product_id' => $product_id,
				'bundle_id'  => $bundle_id,
				'menu_order' => $menu_order,
			);
			$bundle_id = WC_PB_DB::add_bundled_item( $data );
			return $bundle_id;
		}
	}

	/**
	 * Create or update bundle item metadata
	 *
	 * @param number $bundle_item_id bundle item id.
	 * @param string $meta_key - metadata key.
	 * @param string $meta_value - metadata value.
	 * @return float|int $result - metadata id.
	 */
	public function zi_update_bundle_meta( $bundle_item_id, $meta_key, $meta_value ) {
		// first get metadata from db.
		$metadata = WC_PB_DB::get_bundled_item_meta( $bundle_item_id, $meta_key );
		if ( $metadata ) {
			$result = WC_PB_DB::update_bundled_item_meta( $bundle_item_id, $meta_key, $meta_value );
		} else {
			$result = WC_PB_DB::add_bundled_item_meta( $bundle_item_id, $meta_key, $meta_value );
		}
		return $result;
	}

	/**
	 * Function to sync composite item from zoho to woocommerce
	 *
	 * @param integer $page - Page number of composite item data.
	 * @param string  $category - Category id of composite data.
	 * @return mixed - mostly array of response message.
	 */
	public function recursively_sync_composite_item_from_zoho( $page, $category ) {
		// Start logging.
		// $fd = fopen( __DIR__ . '/recursively_sync_composite_item_from_zoho.txt', 'a+' );

		global $wpdb;
		$zi_org_id = $this->config['ProductZI']['OID'];
		$zi_url    = $this->config['ProductZI']['APIURL'];

		$current_user    = wp_get_current_user();
		$admin_author_id = $current_user->ID;
		if ( ! $admin_author_id ) {
			$admin_author_id = 1;
		}

		$url = $zi_url . 'inventory/v1/compositeitems/?organization_id=' . $zi_org_id . '&filter_by=Status.Active&category_id=' . $category . '&page=' . $page;

		$execute_curl_call = new CMBIRD_API_Handler_Zoho();
		$json              = $execute_curl_call->execute_curl_call_get( $url );
		$code              = $json->code;
		// $message = $json->message;
		// fwrite($fd, PHP_EOL . '$json  : ' . print_r($json, true));
		// Response for item sync with sync button. For cron sync blank array will return.
		$response_msg = array();
		if ( '0' === $code || 0 === $code ) {
			if ( empty( $json->composite_items ) ) {
				array_push( $response_msg, $this->zi_response_message( 'ERROR', 'No composite item to sync for category : ' . $category ) );
				return $response_msg;
			}
			// Accounting stock mode check.
			$accounting_stock = $this->config['Settings']['enable_accounting_stock'];
			foreach ( $json->composite_items as $comp_item ) {
				// fwrite( $fd, PHP_EOL . 'Composite Item : ' . print_r( $comp_item, true ) );
				// Sync stock from specific location check.
				$zi_enable_locationstock = $this->config['Settings']['enable_locationstock'];
				$location_id             = $this->config['Settings']['location_id'];
				$locations               = $comp_item->locations;

				if ( true === $zi_enable_locationstock ) {
					foreach ( $locations as $location ) {
						if ( $location->location_id === $location_id ) {
							if ( $accounting_stock ) {
								$stock = $location->location_available_for_sale_stock;
							} else {
								$stock = $location->location_actual_available_for_sale_stock;
							}
						}
					}
				} elseif ( $accounting_stock ) {
					$stock = $comp_item->available_for_sale_stock;
				} else {
					$stock = $comp_item->actual_available_for_sale_stock;
				}

				// ----------------- Create composite item in woocommerce--------------.
				// Code to skip sync with item already exists with same sku.
				$prod_id = wc_get_product_id_by_sku( $comp_item->sku );
				// Flag to enable or disable sync.
				$allow_to_import = false;
				// Check if product exists with same sku.
				if ( $prod_id ) {
					$zi_item_id = get_post_meta( $prod_id, 'zi_item_id', true );
					if ( $zi_item_id === $comp_item->composite_item_id ) {
						// If product is with same sku and zi_item_id mapped.
						// Do not import ...
						$allow_to_import = false;
					} else {
						// Map existing item with zoho id.
						update_post_meta( $prod_id, 'zi_item_id', $comp_item->composite_item_id );
						$allow_to_import = false;
					}
				} else {
					// If product not exists normal bahaviour of item sync.
					$allow_to_import = true;
				}
				$zoho_comp_item_id = $comp_item->composite_item_id;
				if ( $comp_item->composite_item_id ) {
					$child_items = $this->zi_check_if_child_synced_already( $zoho_comp_item_id, $zi_url, $zi_org_id, $prod_id );
					// Check if child items already synced with zoho.
					if ( ! $child_items ) {
						array_push( $response_msg, $this->zi_response_message( 'ERROR', 'Child not synced for composite item : ' . $zoho_comp_item_id ) );
						continue;
					}
					$product_res = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->postmeta} WHERE meta_key = 'zi_item_id' AND meta_value = %s", $zoho_comp_item_id ) );
					if ( ! empty( $product_res->post_id ) ) {
						$com_prod_id = $product_res->post_id;
					}
					// Check if item is allowed to import or not.
					if ( $allow_to_import ) {
						$product_class = new CMBIRD_Products_ZI_Export();
						$item_array    = json_decode( wp_json_encode( $comp_item ), true );
						$com_prod_id   = $product_class->cmbird_zi_product_to_woocommerce( $item_array, $stock, 'composite' );
						update_post_meta( $com_prod_id, 'zi_item_id', $zoho_comp_item_id );
					}
				}
				// Map composite items to database.
				if ( ! empty( $com_prod_id ) ) {
					wp_set_object_terms( $com_prod_id, 'bundle', 'product_type' );
					foreach ( $child_items as $child_prod ) {
						// Adding product to bundle.
						$child_bundle_id = $this->add_bundle_product( $child_prod->prod_id, $com_prod_id );
						if ( $child_bundle_id ) {
							foreach ( $child_prod->metadata as $bundle_meta_key => $bundle_meta_val ) {
								$this->zi_update_bundle_meta( $child_bundle_id, $bundle_meta_key, $bundle_meta_val );
							}
						}
					}
				}
				// --------------------------------------------------------------------.

				$is_synced_flag = false; // loggin purpose only .

				$product = wc_get_product( $com_prod_id );
				foreach ( $comp_item as $key => $value ) {
					if ( 'status' === $key ) {
						if ( ! empty( $com_prod_id ) ) {
							$status = 'active' === $value ? 'publish' : 'draft';
							$product->set_status( $status );
						}
					}
					if ( 'description' === $key ) {
						if ( ! empty( $com_prod_id ) && ! empty( $value ) ) {
							$product->set_short_description( $value );
						}
					}
					if ( 'name' === $key ) {
						if ( ! empty( $com_prod_id ) ) {
							$product->set_name( $value );
						}
					}
					if ( 'sku' === $key ) {
						if ( ! empty( $com_prod_id ) ) {
							$product->set_sku( $value );
						}
					}
					// Check if stock sync allowed by plugin.
					if ( 'available_stock' === $key || 'actual_available_stock' === $key ) {
						$zi_disable_stock_sync = $this->config['Settings']['disable_stock'];
						if ( ! $zi_disable_stock_sync ) {
							if ( $stock ) {
								if ( ! empty( $com_prod_id ) ) {
									// If value is less than 0 default 1.
									$stock_quantity = $stock < 0 ? 0 : $stock;
									$product->set_manage_stock( true );
									$product->set_stock_quantity( $stock_quantity );
									if ( $stock_quantity > 0 ) {
										$status = 'instock';
									} else {
										$backorder_status = $product->backorders_allowed();
										$status           = $backorder_status ? 'onbackorder' : 'outofstock';
									}
									$product->set_stock_status( $status );
									update_post_meta( $com_prod_id, '_wc_pb_bundled_items_stock_status', $status );
								}
							}
						}
					}
					if ( 'rate' === $key ) {
						$zi_disable_price_sync = $this->config['Settings']['disable_price'];
						if ( ! empty( $com_prod_id ) && ! $zi_disable_price_sync ) {
							$filtered_rate = $this->get_filtered_regular_price( $value, $comp_item, (int) $com_prod_id );
							$sale_price    = $product->get_sale_price();
							if ( empty( $sale_price ) ) {
								$product->set_regular_price( $filtered_rate );
								$product->set_price( $filtered_rate );
								update_post_meta( $com_prod_id, '_wc_pb_base_price', $filtered_rate );
								update_post_meta( $com_prod_id, '_wc_pb_base_regular_price', $filtered_rate );
								update_post_meta( $com_prod_id, '_wc_sw_max_regular_price', $filtered_rate );
							} else {
								$product->set_regular_price( $filtered_rate );
								update_post_meta( $com_prod_id, '_wc_pb_base_price', $filtered_rate );
								update_post_meta( $com_prod_id, '_wc_pb_base_regular_price', $filtered_rate );
								update_post_meta( $com_prod_id, '_wc_sw_max_regular_price', $filtered_rate );
							}
						}
					}
					$product->save();

					if ( 'image_document_id' === $key ) {
						if ( ! empty( $com_prod_id ) && ! empty( $value ) ) {
							$image_class = new CMBIRD_Image_ZI();
							$image_class->cmbird_zi_get_image( $zoho_comp_item_id, $comp_item->name, $comp_item->image_name, $com_prod_id );
						}
					}
					if ( 'category_name' === $key ) {
						if ( ! empty( $com_prod_id ) && $comp_item->category_name != '' ) {
							$term    = get_term_by( 'name', $comp_item->category_name, 'product_cat' );
							$term_id = $term->term_id;
							if ( empty( $term_id ) ) {
								$term    = wp_insert_term(
									$comp_item->category_name,
									'product_cat',
									array(
										'parent' => 0,
									)
								);
								$term_id = $term['term_id'];
							}
							if ( $term_id ) {
								$existing_terms = wp_get_object_terms( $com_prod_id, 'product_cat' );
								if ( $existing_terms && count( $existing_terms ) > 0 ) {
									$is_terms_exist = $this->zi_check_terms_exists( $existing_terms, $term_id );
									if ( ! $is_terms_exist ) {
										update_post_meta( $com_prod_id, 'zi_category_id', $category );
										wp_add_object_terms( $com_prod_id, $term_id, 'product_cat' );
									}
								} else {
									update_post_meta( $com_prod_id, 'zi_category_id', $category );
									wp_set_object_terms( $com_prod_id, $term_id, 'product_cat' );
								}
							}
							// Remove "uncategorized" category if assigned.
							$uncategorized_term = get_term_by( 'slug', 'uncategorized', 'product_cat' );
							if ( $uncategorized_term && has_term( $uncategorized_term->term_id, 'product_cat', $com_prod_id ) ) {
								wp_remove_object_terms( $com_prod_id, $uncategorized_term->term_id, 'product_cat' );
							}
						}
					}
				}

				// sync dimensions and weight.
				$item_url = "{$zi_url}inventory/v1/compositeitems/{$zoho_comp_item_id}?organization_id={$zi_org_id}";
				$this->zi_item_dimension_weight( $item_url, $com_prod_id, true );

				// If item synced append to log : logging purpose only.
				if ( $is_synced_flag ) {
					array_push( $response_msg, $this->zi_response_message( 'SUCCESS', 'Composite item synced for id : ' . $comp_item->composite_item_id, $com_prod_id ) );
				}
			}

			if ( $json->page_context->has_more_page ) {
				++$page;
				$this->recursively_sync_composite_item_from_zoho( $page, $category );
			}
		} else {
			array_push( $response_msg, $this->zi_response_message( $code, $json->message ) );
		}
		// fclose( $fd ); // End of logging.

		return $response_msg;
	}

	/**
	 * Function to retrieve item details, update weight and dimensions.
	 *
	 * @param string $url - URL to ge details.
	 * @return mixed return true if data false if error.
	 */
	public function zi_item_dimension_weight( $url, $product_id, $is_composite = false ) {
		// $fd = fopen(__DIR__ . '/zi_item_dimension_weight.txt', 'a+');
		// Check if item is for syncing purpose.
		$execute_curl_call = new CMBIRD_API_Handler_Zoho();
		$json              = $execute_curl_call->execute_curl_call_get( $url );
		$code              = $json->code;
		$message           = $json->message;
		if ( 0 === $code || '0' === $code ) {
			if ( $is_composite ) {
				// fwrite($fd, PHP_EOL . '$json  : ' . print_r($json, true));
				$details = $json->composite_item->package_details;
			} else {
				$details = $json->item->package_details;
			}
			$product = wc_get_product( $product_id );
			$product->set_weight( floatval( $details->weight ) );
			$product->set_length( floatval( $details->length ) );
			$product->set_width( floatval( $details->width ) );
			$product->set_height( floatval( $details->height ) );
			$product->save();
		} else {
			false;
		}
		// fclose($fd);
	}

	/**
	 * Create response object based on data.
	 *
	 * @param mixed  $index_col - Index value error message.
	 * @param string $message - Response message.
	 * @return object
	 */
	public function zi_response_message( $index_col, $message, $woo_id = '' ) {
		return (object) array(
			'resp_id'     => $index_col,
			'message'     => $message,
			'woo_prod_id' => $woo_id,
		);
	}

	/**
	 * Helper Function to check if terms already exists.
	 */
	public function zi_check_terms_exists( $existing_terms, $term_id ) {
		foreach ( $existing_terms as $woo_existing_term ) {
			if ( $woo_existing_term->term_id === $term_id ) {
				return true;
			} else {
				return false;
			}
		}
	}

	/**
	 * Refactored batch sync method using WooCommerce REST API batch endpoint.
	 *
	 * Fetches items from Zoho API, optionally fetches itemdetails for location stock,
	 * and processes items in batches through the WooCommerce REST API.
	 *
	 * @param array $args Array containing 'page' and 'category' parameters.
	 * @return array|bool Success or failure status.
	 */
	public function sync_items_batch( $args ) {
		// $fd = fopen( __DIR__ . '/sync_items_batch.txt', 'a+' );
		$args   = func_get_args();
		$config = $this->config['Settings'];
		if ( is_array( $args ) ) {
			if ( isset( $args['page'] ) && isset( $args['category'] ) ) {
				$page     = $args['page'];
				$category = $args['category'];
			} elseif ( isset( $args[0] ) && isset( $args[1] ) ) {
				$page     = $args[0];
				$category = $args[1];
			} elseif ( isset( $args[0] ) && ! isset( $args[1] ) ) {
				$page     = $args[0]['page'];
				$category = $args[0]['category'];
			} else {
				return;
			}
		} else {
			return;
		}

		// Fetch items from Zoho items endpoint.
		$zoho_inventory_oid = $this->config['ProductZI']['OID'];
		$zoho_inventory_url = $this->config['ProductZI']['APIURL'];
		$timeout            = ini_get( 'max_execution_time' ) ? ini_get( 'max_execution_time' ) : 60;

		// Dynamically set per_page based on timeout.
		if ( $timeout <= 30 ) {
			$per_page = 20;
		} elseif ( $timeout <= 60 ) {
			$per_page = 50;
		} else {
			$per_page = 100;
		}

		$url               = sprintf(
			'%sinventory/v1/items?organization_id=%s&category_id=%s&page=%d&per_page=%d&sort_column=last_modified_time',
			$zoho_inventory_url,
			$zoho_inventory_oid,
			$category,
			$page,
			$per_page
		);
		$execute_curl_call = new CMBIRD_API_Handler_Zoho();
		$json              = $execute_curl_call->execute_curl_call_get( $url );
		$code              = (int) property_exists( $json, 'code' ) ? $json->code : '0';

		if ( 0 !== $code && '0' !== $code ) {
			// Log the error in WC logger.
			$logger = wc_get_logger();
			$logger->error( sprintf( 'Zoho API error during item sync: Code: %s, Message: %s', $code, $json->message ) );
			return false;
		}

		if ( ! isset( $json->items ) || empty( $json->items ) ) {
			return false;
		}

		// Separate items based on location stock requirement.
		$items_ready              = array();
		$item_ids                 = array();
		$item_summaries_by_id     = array();
		$items_without_attributes = array();

		foreach ( $json->items as $item ) {
			if ( ! empty( $item->item_id ) ) {
				$item_summaries_by_id[ (string) $item->item_id ] = $item;
			}

			// Skip combo products.
			if ( isset( $item->is_combo_product ) && $item->is_combo_product ) {
				continue;
			}

			// Skip variations/groups.
			if ( isset( $item->group_id ) && ! empty( $item->group_id ) ) {
				if ( ! empty( $item->attribute_name1 ) ) {
					$this->sync_variation_of_group( $item );
					continue;
				} else {
					$items_without_attributes[] = $item;
				}
			}

			// Collect item IDs if location stock is enabled.
			if ( $config['enable_location_stock'] ) {
				$item_ids[] = $item->item_id;
			} else {
				$items_ready[] = $item;
			}
		}

		// Notify admin of group items that have no attributes and will be synced as simple items.
		if ( ! empty( $items_without_attributes ) ) {
			$rows = '';
			foreach ( $items_without_attributes as $no_attr_item ) {
				$rows .= '<tr><td>' . esc_html( $no_attr_item->item_id ) . '</td><td>' . esc_html( $no_attr_item->name ) . '</td><td>' . esc_html( $no_attr_item->sku ?? '' ) . '</td><td>' . esc_html( $no_attr_item->group_name ?? '' ) . '</td></tr>';
			}
			$message = '<p>The following Group Items have no attributes set in Zoho Inventory and will be synced as simple items:</p>'
			. '<table border="1" cellpadding="5" cellspacing="0"><thead><tr><th>Item ID</th><th>Name</th><th>SKU</th><th>Group Name</th></tr></thead><tbody>'
			. $rows
			. '</tbody></table>';
			$common  = new CMBIRD_Common_Functions();
			$common->send_email( 'CommerceBird: Group Items Without Attributes', $message );
		}

		// Fetch itemdetails for location stock if needed.
		if ( ! empty( $item_ids ) ) {
			$item_details_url_base = "{$zoho_inventory_url}inventory/v1/itemdetails?organization_id={$zoho_inventory_oid}&item_ids=";
			$item_id_str           = implode( ',', $item_ids );
			$item_details_url      = $item_details_url_base . $item_id_str;
			$json_details          = $execute_curl_call->execute_curl_call_get( $item_details_url );
			$details_code          = (int) property_exists( $json_details, 'code' ) ? $json_details->code : '0';

			if ( 0 === $details_code || '0' === $details_code ) {
				if ( isset( $json_details->items ) && is_array( $json_details->items ) ) {
					foreach ( $json_details->items as $item_detail ) {
						if ( ! empty( $item_detail->item_id ) ) {
							$item_detail = $this->merge_item_summary_fields(
								$item_detail,
								$item_summaries_by_id[ (string) $item_detail->item_id ] ?? null
							);
						}
						$items_ready[] = $item_detail;
					}
				}
			}
		}

		// Process items through batch endpoint.
		if ( ! empty( $items_ready ) ) {
			$this->process_items_batch( $items_ready );
		}

		// Handle pagination.
		if ( isset( $json->page_context->has_more_page ) && $json->page_context->has_more_page && $page < 1000 ) {
			as_schedule_single_action(
				time() + 5,
				'import_simple_items_cron',
				array(
					'page'     => $page + 1,
					'category' => $category,
				),
				'commercebird'
			);
		}

		return array(
			'success' => true,
			'message' => 'Batch processed successfully',
		);
	}

	/**
	 * Preserves summary fields from the items endpoint when itemdetails omits them.
	 *
	 * @param object      $item_detail Detailed item payload.
	 * @param object|null $item_summary Summary item payload from the items endpoint.
	 * @return object
	 */
	private function merge_item_summary_fields( $item_detail, $item_summary ) {
		if ( ! is_object( $item_detail ) || ! is_object( $item_summary ) ) {
			return $item_detail;
		}

		$fields_to_preserve = array(
			'category_id',
			'category_name',
			'image_document_id',
			'image_name',
			'brand',
			'group_id',
			'group_name',
			'attribute_id1',
			'attribute_id2',
			'attribute_id3',
			'attribute_name1',
			'attribute_name2',
			'attribute_name3',
			'attribute_option_id1',
			'attribute_option_id2',
			'attribute_option_id3',
			'attribute_option_name1',
			'attribute_option_name2',
			'attribute_option_name3',
		);

		foreach ( $fields_to_preserve as $field_name ) {
			if (
			( ! isset( $item_detail->$field_name ) || '' === $item_detail->$field_name )
			&& isset( $item_summary->$field_name )
			&& '' !== $item_summary->$field_name
			) {
				$item_detail->$field_name = $item_summary->$field_name;
			}
		}

		return $item_detail;
	}

	/**
	 * Process items batch for create/update via WooCommerce REST API.
	 *
	 * Handles product matching (by SKU or zi_item_id), duplicate removal,
	 * and builds create/update arrays for batch endpoint.
	 *
	 * @param array $items Array of Zoho items to process.
	 * @return void
	 */
	private function process_items_batch( $items ) {
		// $fd = fopen( __DIR__ . '/process_items_batch.txt', 'a+' );
		global $wpdb;

		$to_create = array();
		$to_update = array();

		foreach ( $items as $item ) {
			// 1. Try to find product by SKU.
			$product_id = ( isset( $item->sku ) && ! empty( $item->sku ) )
			? wc_get_product_id_by_sku( $item->sku )
			: 0;

			// 2. If product exists by SKU, check if mapped to Zoho.
			if ( $product_id ) {
				$zi_item_id = get_post_meta( $product_id, 'zi_item_id', true );
				if ( empty( $zi_item_id ) ) {
					// Map existing product with Zoho item ID.
					update_post_meta( $product_id, 'zi_item_id', $item->item_id );
				}
			}

			// 3. Remove duplicates based on SKU.
			if ( $product_id && ! empty( $item->sku ) ) {
				$duplicate_product = $wpdb->get_row(
					$wpdb->prepare(
						"SELECT post_id FROM {$wpdb->prefix}postmeta WHERE meta_key = '_sku' AND meta_value = %s AND post_id != %d LIMIT 1",
						$item->sku,
						$product_id
					)
				);
				if ( $duplicate_product ) {
					wp_delete_post( $duplicate_product->post_id, true );
					$product_id = 0;
				}
			}

			// 4. If no product by SKU, try to find by zi_item_id.
			if ( empty( $product_id ) ) {
				$existing   = $wpdb->get_row(
					$wpdb->prepare(
						"SELECT post_id FROM {$wpdb->prefix}postmeta WHERE meta_key = 'zi_item_id' AND meta_value = %s LIMIT 1",
						$item->item_id
					)
				);
				$product_id = $existing ? $existing->post_id : 0;
			}

			// 5. Create or update based on what we found.
			if ( empty( $product_id ) && isset( $item->status ) && 'active' === $item->status ) {
				// Create new product.
				$to_create[] = $this->map_item_to_array( $item, null );
			} elseif ( ! empty( $product_id ) ) {
				// Update existing product.
				$to_update[] = $this->map_item_to_array( $item, $product_id );
			}
		}

		// Use batch endpoint.
		if ( ! empty( $to_create ) ) {
			\CommerceBird\Admin\Actions\Sync\ZohoInventorySync::import( 'product', $to_create, false, '/wc/v3/products/batch' );
		}

		if ( ! empty( $to_update ) ) {
			\CommerceBird\Admin\Actions\Sync\ZohoInventorySync::import( 'product', $to_update, true, '/wc/v3/products/batch' );
		}
	}

	/**
	 * Map Zoho item to WooCommerce batch endpoint format.
	 *
	 * Transforms Zoho item data into WooCommerce REST API product format
	 * for batch create/update operations.
	 *
	 * @param object $item Zoho item object.
	 * @param int    $product_id WooCommerce product ID (null for create).
	 * @return array WooCommerce product data for batch endpoint.
	 */
	private function map_item_to_array( $item, $product_id = null ) {
		$config = $this->config['Settings'];
		$rate   = isset( $item->rate )
		? $this->get_filtered_regular_price( $item->rate, $item, $product_id )
		: null;
		$data   = array(
			'name'   => isset( $item->name ) ? $item->name : '',
			'sku'    => isset( $item->sku ) ? $item->sku : '',
			'status' => ( isset( $item->status ) && 'active' === $item->status ) ? 'publish' : 'draft',
		);

		// Add product ID for updates.
		if ( $product_id ) {
			$data['id'] = $product_id;
			// Remove "uncategorized" category if assigned.
			$uncategorized_term = get_term_by( 'slug', 'uncategorized', 'product_cat' );
			if ( $uncategorized_term && has_term( $uncategorized_term->term_id, 'product_cat', $product_id ) ) {
				wp_remove_object_terms( $product_id, $uncategorized_term->term_id, 'product_cat' );
			}
		}

		// Add rate if price sync is not disabled.
		if ( ! $config['disable_price'] && null !== $rate ) {
			$data['regular_price'] = (string) $rate;
		}

		// Add description if not disabled.
		if ( ! $config['disable_description'] && isset( $item->description ) ) {
			$data['short_description'] = $item->description;
		}

		// Add images if not disabled.
		if ( ! $config['disable_image'] && isset( $item->image_document_id ) && ! empty( $item->image_document_id ) ) {
			$image_class   = new CMBIRD_Image_ZI();
			$attachment_id = $image_class->cmbird_zi_get_image(
				$item->item_id,
				isset( $item->name ) ? $item->name : '',
				isset( $item->image_name ) ? $item->image_name : '',
				$product_id
			);

			// Add image to batch data.
			if ( $attachment_id ) {
				$data['images'] = array(
					array( 'id' => $attachment_id ),
				);
			}
		}

		// Check if Tax is enabled and set tax status.
		if ( ! empty( $item->tax_id ) && $this->is_tax_enabled ) {
			$zi_common_class    = new CMBIRD_Common_Functions();
			$woo_tax_class      = $zi_common_class->get_tax_class_by_zoho_id( $item->tax_id )
							?? $zi_common_class->get_tax_class_by_percentage( $item->tax_percentage );
			$data['tax_class']  = $woo_tax_class;
			$data['tax_status'] = 'taxable';
		}

		// Handle stock.
		if ( ! $config['disable_stock'] ) {
			$stock = null;

			// Use location stock if available (from itemdetails).
			if ( isset( $item->locations ) && is_array( $item->locations ) ) {
				foreach ( $item->locations as $location ) {
					if ( $location->location_id === $config['zoho_location_id'] ) {
						$stock = $config['enable_accounting_stock']
						? $location->location_available_for_sale_stock
						: $location->location_actual_available_for_sale_stock;
						break;
					}
				}
			} else {
				// Use global stock from items endpoint.
				$stock = $config['enable_accounting_stock']
				? ( isset( $item->available_stock ) ? $item->available_stock : null )
				: ( isset( $item->actual_available_stock ) ? $item->actual_available_stock : null );
			}
			if ( null !== $stock ) {
				$data['manage_stock']   = true;
				$data['stock_quantity'] = $stock;
			}
		}
		// Add cost_of_goods_sold if available.
		if ( isset( $item->purchase_rate ) ) {
			$data['cost_of_goods_sold'] = $item->purchase_rate;
		}

		// Add dimensions directly from items endpoint.
		if ( isset( $item->weight ) ) {
			$data['weight'] = $item->weight;
		}
		if ( isset( $item->length ) ) {
			$data['length'] = $item->length;
		}
		if ( isset( $item->width ) ) {
			$data['width'] = $item->width;
		}
		if ( isset( $item->height ) ) {
			$data['height'] = $item->height;
		}

		// Add categories, preferring a manual/category map match by Zoho category ID.
		$category_term_id = null;
		if ( isset( $item->category_id ) && '' !== $item->category_id ) {
			$category_term_id = ( new \CommerceBird\Admin\Mappings\ZohoCategoryMap() )->term_id_for_zoho_id( (string) $item->category_id );
		}
		if ( null !== $category_term_id ) {
			$data['categories'] = array( array( 'id' => $category_term_id ) );
		} elseif ( isset( $item->category_name ) && ! empty( $item->category_name ) ) {
			$categories = self::prepare_categories( $item->category_name );
			if ( ! empty( $categories ) ) {
				$data['categories'] = $categories;
			}
		}

		// Add brand as category if present.
		if ( isset( $item->brand ) && ! empty( $item->brand ) ) {
			$brand_terms = self::prepare_categories( $item->brand, 'product_brand' );
			if ( ! empty( $brand_terms ) ) {
				$data['brands'] = $brand_terms;
			}
		}

		// Add meta data.
		$data['meta_data'] = array(
			array(
				'key'   => 'zi_item_id',
				'value' => isset( $item->item_id ) ? $item->item_id : '',
			),
			array(
				'key'   => 'zi_category_id',
				'value' => isset( $item->category_id ) ? $item->category_id : '',
			),
		);

		// Add custom fields from items endpoint (flattened format).
		foreach ( $item as $key => $value ) {
			if ( 0 === strpos( $key, 'cf_' ) && false === strpos( $key, '_unformatted' ) ) {
				$data['meta_data'][] = array(
					'key'   => 'zi_' . $key,
					'value' => $value,
				);
			}
		}

		return $data;
	}

	/**
	 * Prepare category data for WooCommerce batch endpoint.
	 *
	 * Creates or retrieves category term and returns in batch format.
	 *
	 * @param string $category_name Category name from Zoho.
	 * @param string $taxonomy Taxonomy to use, default is 'product_cat'.
	 * @return array Category array for WooCommerce batch endpoint.
	 */
	private static function prepare_categories( $category_name, $taxonomy = 'product_cat' ) {
		if ( empty( $category_name ) ) {
			return array();
		}

		$category_name = trim( (string) $category_name );
		$term_id       = 0;

		$term = get_term_by( 'name', $category_name, $taxonomy );
		if ( $term instanceof WP_Term ) {
			$term_id = (int) $term->term_id;
		}

		if ( ! $term_id ) {
			$term = get_term_by( 'slug', sanitize_title( $category_name ), $taxonomy );
			if ( $term instanceof WP_Term ) {
				$term_id = (int) $term->term_id;
			}
		}

		if ( ! $term_id ) {
			$term_exists = term_exists( $category_name, $taxonomy );
			if ( is_array( $term_exists ) && isset( $term_exists['term_id'] ) ) {
				$term_id = (int) $term_exists['term_id'];
			} elseif ( is_int( $term_exists ) ) {
				$term_id = $term_exists;
			}
		}

		if ( ! $term_id ) {
			$term = wp_insert_term( $category_name, $taxonomy, array( 'parent' => 0 ) );

			if ( is_wp_error( $term ) ) {
				if ( 'term_exists' === $term->get_error_code() ) {
					$term_id = (int) $term->get_error_data();
				}
			} else {
				$term_id = isset( $term['term_id'] ) ? (int) $term['term_id'] : 0;
			}
		}

		return $term_id ? array( array( 'id' => $term_id ) ) : array();
	}

	/**
	 * Get sync configuration from class config or WordPress options.
	 *
	 * Merges class configuration with WordPress option settings.
	 *
	 * @return array Configuration array.
	 */
}
$cmbird_products_zi = new CMBIRD_Products_ZI();
