<?php
/**
 * Class for handling Zoho Inventory order sync related functions.
 */
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class CMBIRD_Order_Sync_ZI {

	/**
	 * Zoho Inventory organization ID.
	 *
	 * @var string
	 */
	private $zoho_inventory_oid;

	/**
	 * Zoho Inventory API base URL.
	 *
	 * @var string
	 */
	private $zoho_inventory_url;

	/**
	 * Zoho Inventory domain code (e.g. 'in' for India).
	 *
	 * @var string
	 */
	private $zoho_inventory_domain;

	/**
	 * Location ID for Zoho Inventory.
	 *
	 * @var string
	 */
	private $location_id;

	/**
	 * Parent location ID for Zoho Inventory.
	 *
	 * @var string
	 */
	private $parent_location_id;

	/**
	 * Map of Zoho location IDs to arrays of WooCommerce billing country codes.
	 * Used to route orders to the correct location based on the customer's country.
	 *
	 * @var array<string, string[]>
	 */
	private $location_country_map;

	/**
	 * Whether to enable order status sync.
	 *
	 * @var bool
	 */
	private $enable_order_status;

	/**
	 * Whether to enable auto-numbering for orders.
	 *
	 * @var bool
	 */
	private $enable_auto_number;

	/**
	 * Whether to enable order cycle sync.
	 *
	 * @var bool
	 */
	private $enable_order_cycle;

	/**
	 * Prefix for order numbers.
	 *
	 * @var string
	 */
	private $order_prefix;

	/**
	 * Custom fields for Zoho Inventory sync.
	 *
	 * @var array
	 */
	private $custom_fields;

	/**
	 * Whether to include tax in calculations.
	 *
	 * @var bool
	 */
	private $include_tax;

	/**
	 * Initialize the class.
	 */
	public function __construct() {
		$zoho_inventory_access_token = get_option( 'cmbird_zoho_inventory_access_token' );
		if ( ! empty( $zoho_inventory_access_token ) ) {
			add_action( 'woocommerce_update_order', array( $this, 'salesorder_void' ) );
			// get options.
			$this->zoho_inventory_oid    = get_option( 'cmbird_zoho_inventory_oid' );
			$this->zoho_inventory_url    = get_option( 'cmbird_zoho_inventory_url' );
			$this->zoho_inventory_domain = get_option( 'cmbird_zoho_inventory_domain' );
			$this->location_id           = get_option( 'cmbird_zoho_location_id_status' );
			$this->parent_location_id    = get_option( 'cmbird_zoho_parent_location_id_status' );
			$this->location_country_map  = (array) get_option( 'cmbird_zoho_location_country_map_status', array() );
			$this->enable_order_status   = get_option( 'cmbird_zoho_enable_order_status_status' );
			$this->enable_auto_number    = get_option( 'cmbird_zoho_enable_auto_number_status' );
			$this->enable_order_cycle    = get_option( 'cmbird_zoho_enable_sales_order_cycle_status' );
			$this->order_prefix          = get_option( 'cmbird_zoho_order_prefix_status' );
			$stored_custom_fields        = json_decode( get_option( 'cmbird_wootozoho_custom_fields' ), true );
			if ( is_array( $stored_custom_fields ) && ( isset( $stored_custom_fields['salesorder'] ) || isset( $stored_custom_fields['item'] ) ) ) {
				$this->custom_fields = is_array( $stored_custom_fields['salesorder'] ?? null ) ? $stored_custom_fields['salesorder'] : array();
			} else {
				$this->custom_fields = is_array( $stored_custom_fields ) ? $stored_custom_fields : array();
			}
			$this->include_tax = get_option( 'woocommerce_prices_include_tax' ) === 'yes';
		} else {
			return;
		}
	}

	/**
	 * Function to map customer on checkout before placing order
	 *
	 * @param int $order_id Order ID.
	 */
	public function cmbird_zi_sync_customer_checkout( $order_id ) {
		// $fd = fopen( __DIR__ . '/cmbird_zi_sync_customer_checkout.txt', 'w+' );
		$order          = wc_get_order( $order_id );
		$userid         = $order->get_user_id();
		$is_guest_order = empty( $userid );
		$user_company   = $order->get_billing_company();
		$user_email     = $order->get_billing_email();

		// For guest orders, get contact ID from order meta; for regular users, from user meta.
		if ( $is_guest_order ) {
			$zi_customer_id = $order->get_meta( 'zi_contact_id', true );
		} else {
			$zi_customer_id = get_user_meta( $userid, 'zi_contact_id', true );
		}

		// Get currency code of the order.
		if ( $is_guest_order ) {
			$currency_id = intval( $order->get_meta( 'zi_currency_id', true ) );
		} else {
			$currency_id = intval( get_user_meta( $userid, 'zi_currency_id', true ) );
		}
		if ( empty( $currency_id ) ) {
			$currency_code         = $order->get_currency();
			$multi_currency_handle = new CMBIRD_Multicurrency_Zoho();
			$currency_id           = $multi_currency_handle->zoho_currency_data( $currency_code, $userid, $order );
		}

		if ( $zi_customer_id ) {
			$zoho_inventory_oid = $this->zoho_inventory_oid;
			$zoho_inventory_url = $this->zoho_inventory_url;
			$get_url            = $zoho_inventory_url . 'inventory/v1/contacts/' . $zi_customer_id . '/?organization_id=' . $zoho_inventory_oid;

			$execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
			$json                     = $execute_curl_call_handle->execute_curl_call_get( $get_url );
			// fwrite($fd,PHP_EOL.'customer_json: '.print_r($json, true)); --- IGNORE ---.
			$code = $json->code;
			if ( 0 !== $code && '0' !== $code ) {
				// For guest orders, clear contact data immediately if validation fails.
				// For registered users, be more cautious - only clear if it's a definitive "not found" error.
				$should_clear_contact_data = false;

				if ( 1003 === $code || '1003' === $code ) {
					// For registered users, only clear if it's a specific "contact not found" error (code 1003).
					// Other errors might be temporary (rate limiting, API issues, etc.).
					$should_clear_contact_data = true;
				}

				if ( $should_clear_contact_data ) {
					// Clear contact data - from order meta for guest orders, user meta for regular users.
					if ( $is_guest_order ) {
						$order->delete_meta_data( 'zi_contact_id' );
						$order->delete_meta_data( 'zi_billing_address_id' );
						$order->delete_meta_data( 'zi_primary_contact_id' );
						$order->delete_meta_data( 'zi_shipping_address_id' );
						$order->delete_meta_data( 'zi_created_time' );
						$order->delete_meta_data( 'zi_last_modified_time' );
						$order->save();
					} else {
						delete_user_meta( $userid, 'zi_contact_id' );
						delete_user_meta( $userid, 'zi_billing_address_id' );
						delete_user_meta( $userid, 'zi_primary_contact_id' );
						delete_user_meta( $userid, 'zi_shipping_address_id' );
						delete_user_meta( $userid, 'zi_created_time' );
						delete_user_meta( $userid, 'zi_last_modified_time' );
					}
					$zi_customer_id = '';
				} else {
					// save the billing and shipping address IDs if contact exists.
					foreach ( $json->contact as $key => $value ) {
						if ( 'billing_address_id' === $key ) {
							if ( $is_guest_order ) {
								$order->update_meta_data( 'zi_billing_address_id', $value );
								$order->save();
							} else {
								update_user_meta( $userid, 'zi_billing_address_id', $value );
							}
						}
						if ( 'shipping_address_id' === $key ) {
							if ( $is_guest_order ) {
								$order->update_meta_data( 'zi_shipping_address_id', $value );
								$order->save();
							} else {
								update_user_meta( $userid, 'zi_shipping_address_id', $value );
							}
						}
					}
				}
				// If we don't clear the contact data, keep the existing $zi_customer_id and proceed.
			}
		}

		/**
		 * Syncing customer if its not in Zoho yet.
		 */
		if ( empty( $zi_customer_id ) ) {

			// First check based on customer email address.
			$zoho_inventory_oid = $this->zoho_inventory_oid;
			$zoho_inventory_url = $this->zoho_inventory_url;
			$url                = $zoho_inventory_url . 'inventory/v1/contacts?organization_id=' . $zoho_inventory_oid . '&email=' . $user_email;

			$execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
			$json                     = $execute_curl_call_handle->execute_curl_call_get( $url );

			$code = $json->code;
			// $message = $json->message;
			if ( 0 === $code || '0' === $code ) {
				// fwrite( $fd, PHP_EOL . 'email found: ' . print_r( $json, true ) );
				if ( empty( $json->contacts ) ) {
					// Second check based on Company Name.
					if ( $user_company ) {
						$company_name = str_replace( ' ', '%20', $user_company );
						$url          = $zoho_inventory_url . 'inventory/v1/contacts?organization_id=' . $zoho_inventory_oid . '&filter_by=Status.Active&search_text=' . $company_name;

						$execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
						$json                     = $execute_curl_call_handle->execute_curl_call_get( $url );

						$code = $json->code;
						if ( 0 === $code || '0' === $code ) {
							if ( empty( $json->contacts ) ) {
								$zi_customer_id = $this->create_contact_for_order( $userid, $order_id, $is_guest_order );
							} else {
								foreach ( $json->contacts[0] as $key => $value ) {
									if ( 'contact_id' === $key ) {
										$zi_customer_id = $value;
										// Store contact ID - in order meta for guest orders, user meta for regular users.
										if ( $is_guest_order ) {
											$order->update_meta_data( 'zi_contact_id', $zi_customer_id );
											$order->save();
										} else {
											update_user_meta( $userid, 'zi_contact_id', $zi_customer_id );
										}
									}
								}
								$zi_customer_id = $this->create_contact_person_for_order( $userid, $order_id, $is_guest_order );
							}
						} else {
							// Company search failed, create new contact.
							$zi_customer_id = $this->create_contact_for_order( $userid, $order_id, $is_guest_order );
						}
					} else {
						$zi_customer_id = $this->create_contact_for_order( $userid, $order_id, $is_guest_order );
					}
				} else {
					// fwrite($fd,PHP_EOL.'Contacts : '.print_r($json->contacts,true));
					foreach ( $json->contacts[0] as $key => $value ) {
						if ( 'contact_id' === $key ) {
							$zi_customer_id = $value;
							// Store contact ID - in order meta for guest orders, user meta for regular users.
							if ( $is_guest_order ) {
								$order->update_meta_data( 'zi_contact_id', $zi_customer_id );
								$order->save();
							} else {
								update_user_meta( $userid, 'zi_contact_id', $zi_customer_id );
							}
						}
					}
				}
			} else {
				// Email search failed, create new contact.
				$zi_customer_id = $this->create_contact_for_order( $userid, $order_id, $is_guest_order );
			}
			// Http request not processed properly.
			// echo $message;
			// Final fallback: if still no contact and this is a guest order, always create contact.
			if ( empty( $zi_customer_id ) && $is_guest_order ) {
				$zi_customer_id = $this->create_contact_for_order( $userid, $order_id, $is_guest_order );
			}
			return $zi_customer_id;
		} else {
			$zoho_inventory_oid = $this->zoho_inventory_oid;
			$zoho_inventory_url = $this->zoho_inventory_url;
			$get_url            = $zoho_inventory_url . 'inventory/v1/contacts/' . $zi_customer_id . '/contactpersons/?organization_id=' . $zoho_inventory_oid;

			$execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
			$contactpersons_response  = $execute_curl_call_handle->execute_curl_call_get( $get_url );

			// fwrite( $fd, PHP_EOL . 'Contactpersons: ' . print_r($contactpersons_response, true) );

			// first check within contactpersons endpoint and then map it with that contactperson if email-id matches.
			if ( 0 === $contactpersons_response->code || '0' === $contactpersons_response->code ) {
				if ( ! empty( $contactpersons_response->contact_persons ) ) {
					foreach ( $contactpersons_response->contact_persons as $key => $contact_persons ) {
						$person_email = trim( $contact_persons->email );
						if ( trim( $user_email ) === $person_email ) {
							/* Match Contact */
							$contactid = $contact_persons->contact_person_id;
							// Store contact person ID - in order meta for guest orders, user meta for regular users.
							if ( $is_guest_order ) {
								$order->update_meta_data( 'zi_contactperson_id_' . $key, $contactid );
								$order->save();
							} else {
								update_user_meta( $userid, 'zi_contactperson_id_' . $key, $contactid );
							}
							if ( true === $contact_persons->is_primary_contact || 1 === $contact_persons->is_primary_contact ) {
								if ( ! $is_guest_order ) {
									$contact_class_handle = new CMBIRD_Contact_ZI();
									$contact_class_handle->cmbird_contact_update_function( $userid, $order_id );
								}
							} elseif ( ! $is_guest_order ) {
								$contact_class_handle = new CMBIRD_Contact_ZI();
								$contact_class_handle->cmbird_update_contact_person( $userid, $order_id );
							}
						}
					}
				} else {
					$get_url     = $zoho_inventory_url . 'inventory/v1/contacts/' . $zi_customer_id . '/?organization_id=' . $zoho_inventory_oid;
					$contact_res = $execute_curl_call_handle->execute_curl_call_get( $get_url );
					if ( ( 0 === $contact_res->code || '0' === $contact_res->code ) && ! empty( $contact_res->contact ) ) {
						// Access the contact object directly (no loop needed - response contains single contact object).
						if ( trim( $contact_res->contact->email ) === trim( $user_email ) ) {
							$contact_class_handle = new CMBIRD_Contact_ZI();
							$contact_class_handle->cmbird_contact_update_function( $userid, $order_id );
						} else {
							$contact_class_handle = new CMBIRD_Contact_ZI();
							$contact_class_handle->cmbird_create_contact_person( $userid );
						}
					}
				}
			}
			// fwrite( $fd, PHP_EOL . 'No contactpersons ' );
		}
		// fclose( $fd );
		return $zi_customer_id;
	}

	/**
	 * Function for admin zoho sync call.
	 *
	 * @param int $order_id Order ID.
	 */
	public function zi_order_sync( $order_id ) {
		$order = wc_get_order( $order_id );
		if ( ! $order ) {
			return;
		}
		// return if there is no zoho inventory access token.
		$zoho_inventory_access_token = get_option( 'cmbird_zoho_inventory_access_token' );
		if ( empty( $zoho_inventory_access_token ) ) {
			return;
		}

		$current_time = time();
		$last_time    = $order->get_meta( 'zi_last_order_sync_time', true );
		if ( ! empty( $last_time ) && $current_time - $last_time < 60 ) {
			return;
		}

		// Create an action hook to prevent orders getting synced if it contains an array of statuses.
		$ignored_statuses = apply_filters( 'cmbird_zi_order_sync_ignored_statuses', array( 'cancelled', 'failed', 'draft', 'trash' ) );
		if ( in_array( $order->get_status(), $ignored_statuses ) ) {
			return;
		}

		$zi_sales_order_id = $order->get_meta( 'zi_salesorder_id' );
		$userid            = $order->get_user_id();
		$is_guest_order    = empty( $userid );

		// For guest orders, get contact details from order meta instead of user meta.
		if ( $is_guest_order ) {
			$customer_id = $order->get_meta( 'zi_contact_id', true );
			$billing_id  = $order->get_meta( 'zi_billing_address_id', true );
			$shipping_id = $order->get_meta( 'zi_shipping_address_id', true );
		} else {
			$customer_id = get_user_meta( $userid, 'zi_contact_id', true );
			$billing_id  = get_user_meta( $userid, 'zi_billing_address_id', true );
			$shipping_id = get_user_meta( $userid, 'zi_shipping_address_id', true );
		}
		// Sync customer if not exists.
		if ( empty( $customer_id ) ) {
			$customer_id = $this->cmbird_zi_sync_customer_checkout( $order_id );
		} elseif ( empty( $billing_id ) || empty( $shipping_id ) ) {
			// run cmbird_contact_update_function() to update address IDs.
			$contact_class_handle = new CMBIRD_Contact_ZI();
			$contact_class_handle->cmbird_contact_update_function( $userid, $order_id );
		}
		// 2nd attempt - For guest orders, get address IDs from order meta; for regular users, from user meta.
		if ( $is_guest_order ) {
			$billing_id  = $order->get_meta( 'zi_billing_address_id', true );
			$shipping_id = $order->get_meta( 'zi_shipping_address_id', true );
		} else {
			$billing_id  = get_user_meta( $userid, 'zi_billing_address_id', true );
			$shipping_id = get_user_meta( $userid, 'zi_shipping_address_id', true );
		}

		$line_items = $this->build_line_items( $order );
		if ( empty( $line_items ) ) {
			return;
		}

		// Final validation: Ensure customer_id is not 0 or '0' before creating request.
		if ( empty( $customer_id ) ) {
			$order->add_order_note( 'Zoho Order Sync Failed: Contact does not exist or could not be created.' );
			return;
		}

		$pdt = array(
			'customer_id'            => $customer_id,
			'date'                   => $order->get_date_created()->format( 'Y-m-d' ),
			'line_items'             => $line_items,
			'is_discount_before_tax' => true,
			'discount_type'          => 'item_level',
			'price_precision'        => '2',
			'notes'                  => preg_replace( '/[^A-Za-z0-9\-]/', ' ', $order->get_customer_note() ),
			'billing_address_id'     => $billing_id,
			'shipping_address_id'    => $shipping_id,
			'delivery_method'        => $order->get_shipping_method(),
			'is_inclusive_tax'       => $this->include_tax,
			'shipping_charge'        => $order->get_shipping_total(),
			'order_status'           => $this->enable_order_status ? 'draft' : 'confirmed',
		);

		if ( $this->parent_location_id && $this->location_id ) {
			$pdt['location_id'] = $this->parent_location_id;
		}

		// determine which tax id should be used for shipping charges. this.
		// method wraps the direct lookup and item-fallback logic so it can be.
		// unit tested in isolation.
		$shipping_tax_id = $this->determine_shipping_tax_id( $order, $line_items );
		if ( $shipping_tax_id ) {
			$pdt['shipping_charge_tax_id'] = $shipping_tax_id;
		}

		$fees = $this->get_order_fees( $order );
		$pdt  = array_merge( $pdt, $fees );

		$pdt['custom_fields'] = $this->prepare_custom_fields( $order );
		$reference            = $this->prepare_reference_number( $order );

		if ( $this->enable_auto_number ) {
			$pdt['reference_number'] = $reference;
		} else {
			$pdt['salesorder_number'] = $order->get_id();
		}

		$userid = $order->get_user_id();
		// get below data if zoho_inventory_domain is 'in'.
		if ( 'in' === $this->zoho_inventory_domain ) {
			$gst_no = get_user_meta( $userid, 'gst_no', true );
			// if gst_no is empty then get it from order.
			if ( empty( $gst_no ) ) {
				$gst_no = $order->get_meta( '_gst_no', true );
			}
			if ( $gst_no ) {
				$pdt['gst_no'] = $gst_no;
			}

			// Determine GST treatment based on billing company, GST number, and country.
			$billing_company = $order->get_billing_company();
			$billing_country = $order->get_billing_country();

			// GST treatment logic - prioritize company + GST combination first.
			if ( ! empty( $billing_company ) && ! empty( $gst_no ) ) {
				// Company with GST number.
				$gst_treatment = 'business_gst';
			} elseif ( ! empty( $billing_company ) && empty( $gst_no ) && 'IN' === $billing_country ) {
				// Company without GST number in India.
				$gst_treatment = 'business_none';
			} elseif ( 'IN' !== $billing_country ) {
				// Non-Indian customer (overseas).
				$gst_treatment = 'overseas';
			} else {
				// Individual consumer in India.
				$gst_treatment = 'consumer';
			}

			// Allow override from user meta or order meta if exists.
			$meta_gst_treatment = get_user_meta( $userid, 'gst_treatment', true );
			if ( empty( $meta_gst_treatment ) ) {
				$meta_gst_treatment = $order->get_meta( 'gst_treatment', true );
			}
			if ( ! empty( $meta_gst_treatment ) ) {
				$gst_treatment = $meta_gst_treatment;
			}

			$pdt['gst_treatment']   = $gst_treatment;
			$pdt['place_of_supply'] = $order->get_billing_state();
		}

		$is_new_order = ! $zi_sales_order_id;
		$response_msg = $zi_sales_order_id ? $this->single_saleorder_zoho_inventory_update( $order_id, $zi_sales_order_id, wp_json_encode( $pdt ) ) : $this->single_saleorder_zoho_inventory( wp_json_encode( $pdt ), $order_id );

		$order->update_meta_data( 'zi_body_request', wp_json_encode( $pdt ) );
		$order->update_meta_data( 'zi_salesorder_id', $response_msg['zi_salesorder_id'] );
		$order->update_meta_data( 'zi_last_order_sync_time', $current_time );
		$order->add_order_note( 'Zoho Order Sync: ' . $response_msg['message'] );
		$order->save();

		// Trigger sales order cycle if enabled and this is a newly created order.
		if ( $is_new_order && $this->enable_order_cycle && ! empty( $response_msg['zi_salesorder_id'] ) ) {
			$this->trigger_sales_order_cycle( $order_id, $response_msg['zi_salesorder_id'] );
		}
	}

	/**
	 * Build line items for a given order.
	 *
	 * @param WC_Order $order WooCommerce order object.
	 *
	 * @return array Array of line items.
	 */
	private function build_line_items( $order ) {
		$items = array();

		// determine the location that should be applied to each line item.
		// we replicate the same country‑map logic used later when creating the.
		// parent order object so the line entries match the order location.
		$resolved_location_id = '';
		if ( $this->parent_location_id && $this->location_id ) {
			$resolved_location_id = $this->parent_location_id;
			$shipping_country     = $order->get_shipping_country();
			if ( ! empty( $shipping_country ) && ! empty( $this->location_country_map ) ) {
				foreach ( $this->location_country_map as $mapped_location_id => $countries ) {
					if ( is_array( $countries ) && in_array( $shipping_country, $countries, true ) ) {
						$resolved_location_id = $mapped_location_id;
						break;
					}
				}
			}
		}

		foreach ( $order->get_items() as $item_id => $item ) {
			$product = $item->get_product();
			if ( ! $product ) {
				continue;
			}

			$name = $item->get_name();
			$meta = $item->get_formatted_meta_data();
			$desc = '';
			if ( ! empty( $meta ) ) {
				foreach ( $meta as $meta_value ) {
					$desc .= $meta_value->display_key . ': ' . wp_strip_all_tags( $meta_value->display_value ) . ' ';
				}
				$desc = sanitize_text_field( trim( $desc ) );
			}

			$quantity = $item->get_quantity();
			$subtotal = $item->get_subtotal();
			$total    = $item->get_total();
			$rate     = $subtotal / max( 1, $quantity );
			$discount = $subtotal > $total ? $subtotal - $total : 0;

			$tax_data    = $item->get_taxes();
			$tax_id      = '';
			$tax_percent = 0;

			// Preserve original tax/discount calculation logic but ensure the line is created.
			// and added even when there are no taxes for the item.
			if ( ! empty( $tax_data['subtotal'] ) ) {
				$tax_rate_ids = array_keys( $tax_data['subtotal'] );
				$tax_count    = count( $tax_rate_ids );

				// Check if this is a compound tax (multiple tax rates).
				if ( $tax_count > 1 ) {
					// Calculate total tax percentage from all rates.
					$total_tax_percent = 0;
					foreach ( $tax_rate_ids as $tax_rate_id ) {
						$tax_rate           = WC_Tax::_get_tax_rate( $tax_rate_id );
						$total_tax_percent += floatval( $tax_rate['tax_rate'] );
					}

					// Try to find a matching tax group.
					$tax_id = $this->zi_get_tax_group_id( $total_tax_percent );

					if ( $tax_id ) {
						// Found a matching tax group, use the total percentage.
						$tax_percent = $total_tax_percent;
					} else {
						// No tax group found, fall back to first tax rate only.
						$tax_rate_id = $tax_rate_ids[0];
						$tax_rate    = WC_Tax::_get_tax_rate( $tax_rate_id );
						$tax_percent = $tax_rate['tax_rate'];

						// For India domain, use tax name to determine IGST or CGST/SGST.
						if ( 'in' === $this->zoho_inventory_domain ) {
							$tax_name = isset( $tax_rate['tax_rate_name'] ) ? $tax_rate['tax_rate_name'] : '';
							if ( stripos( $tax_name, 'IGST' ) !== false ) {
								$tax_id = $this->zi_get_igst_tax_id( $tax_percent );
							} elseif ( stripos( $tax_name, 'SGST' ) !== false || stripos( $tax_name, 'CGST' ) !== false ) {
								$tax_id = $this->zi_get_tax_id( $tax_percent );
							} else {
								// Keep tax_id empty if tax name doesn't indicate IGST or CGST/SGST.
								$tax_id = '';
							}
						} else {
							// For non-India domains, use regular tax_id.
							$tax_id = $this->zi_get_tax_id( $tax_percent );
						}
					}
				} else {
					// Single tax rate - use existing logic.
					$tax_rate_id = $tax_rate_ids[0];
					$tax_rate    = WC_Tax::_get_tax_rate( $tax_rate_id );
					$tax_percent = $tax_rate['tax_rate'];

					// For India domain, use tax name to determine IGST or CGST/SGST.
					if ( 'in' === $this->zoho_inventory_domain ) {
						$tax_name = isset( $tax_rate['tax_rate_name'] ) ? $tax_rate['tax_rate_name'] : '';
						if ( stripos( $tax_name, 'IGST' ) !== false ) {
							$tax_id = $this->zi_get_igst_tax_id( $tax_percent );
						} elseif ( stripos( $tax_name, 'SGST' ) !== false || stripos( $tax_name, 'CGST' ) !== false ) {
							$tax_id = $this->zi_get_tax_id( $tax_percent );
						} else {
							// Keep tax_id empty if tax name doesn't indicate IGST or CGST/SGST.
							$tax_id = '';
						}
					} else {
						// For non-India domains, use regular tax_id.
						$tax_id = $this->zi_get_tax_id( $tax_percent );
					}
				}
			}

			// Always build the line item (previous logic accidentally only added items when taxes existed).
			$line = array(
				'item_id'     => get_post_meta( $product->get_id(), 'zi_item_id', true ),
				'name'        => $name,
				'description' => $desc,
				'quantity'    => $quantity,
				'rate'        => number_format( $rate, 2, '.', '' ),
			);

			if ( $discount > 0 ) {
				$line['discount'] = number_format( $discount, 2, '.', '' );
			}
			if ( $tax_id ) {
				$line['tax_percentage'] = $tax_percent;
				$line['tax_id']         = $tax_id;
			}
			// add resolved location id if available (may be mapped by country).
			if ( ! empty( $resolved_location_id ) ) {
				$line['location_id'] = $resolved_location_id;
			}
			$items[] = $line;
		}
		return $items;
	}

	/**
	 * Get the Zoho Inventory tax id for the shipping tax.
	 *
	 * @param WC_Order $order The WooCommerce order object.
	 * @return string The Zoho Inventory tax id, or empty string if no tax is applicable.
	 */
	private function get_shipping_tax_id( $order ) {
		$shipping_total = $order->get_shipping_total();
		$shipping_tax   = $order->get_shipping_tax();
		if ( $shipping_total > 0 && $shipping_tax > 0 ) {
			// compute the overall tax percentage using the provided.
			// total tax amount. this is the correct WooCommerce API and.
			// works in all contexts (including admin overrides).
			$percent = ( $shipping_tax / $shipping_total ) * 100;
			$percent = round( $percent, 2 );

			// try to match a compound tax group first.
			$tax_id = $this->zi_get_tax_group_id( $percent );
			if ( $tax_id ) {
					return $tax_id;
			}

			// no group found, fall back to single tax lookup.
			return $this->zi_get_tax_id( $percent );
		}
		return '';
	}
	/**
	 * Determine the appropriate shipping tax id, falling back to line item tax ids
	 * when a direct lookup fails. Added to isolate logic for unit testing and to
	 * ensure shipping uses the same tax group as the order lines when possible.
	 *
	 * @param WC_Order $order The WooCommerce order object.
	 * @param array    $line_items Array of line item data previously generated.
	 * @return string Shipping tax id or empty string.
	 */
	public function determine_shipping_tax_id( $order, $line_items = array() ) {
		$shipping_tax_id = $this->get_shipping_tax_id( $order );
		if ( ! $shipping_tax_id && ! empty( $line_items ) ) {
			foreach ( $line_items as $line ) {
				if ( ! empty( $line['tax_id'] ) ) {
					$shipping_tax_id = $line['tax_id'];
					break;
				}
			}
		}
		return $shipping_tax_id;
	}

	/**
	 * Retrieves the fees associated with an order and formats them for Zoho Inventory.
	 *
	 * @param WC_Order $order The WooCommerce order object.
	 *
	 * @return array Associative array containing the fee amount and description.
	 */
	private function get_order_fees( $order ) {
		$fees = $order->get_fees();
		$data = array();
		foreach ( $fees as $fee ) {
			$amount = $fee->get_total();
			if ( $amount > 0 ) {
				$data['adjustment']             = $amount;
				$data['adjustment_description'] = $fee->get_name();
			}
		}
		return $data;
	}

	/**
	 * Prepare custom fields for the order.
	 *
	 * Iterates over the $custom_fields array and creates an associative array
	 * containing the custom field label and value.
	 *
	 * @param WC_Order $order The WooCommerce order object.
	 * @return array Associative array containing the custom field label and value.
	 */
	private function prepare_custom_fields( $order ) {
		$fields = array();
		if ( is_array( $this->custom_fields ) ) {
			foreach ( $this->custom_fields as $meta_key => $label ) {
				$fields[] = array(
					'label' => $label,
					'value' => $order->get_meta( $meta_key ),
				);
			}
		}
		return $fields;
	}

	/**
	 * Prepare the reference number for the sales order.
	 *
	 * The reference number is used to identify the sales order in Zoho Inventory.
	 * It is generated by combining the order prefix (if set) with the transaction ID
	 * (if available) or the order ID.
	 *
	 * @param WC_Order $order The WooCommerce order object.
	 * @return string The reference number for the sales order.
	 */
	private function prepare_reference_number( $order ) {
		$transaction_id = $order->get_transaction_id();
		if ( empty( $transaction_id ) ) {
			$transaction_id = $order->get_meta( '_order_number', true );
		}
		if ( class_exists( 'WCJ_Order_Numbers' ) || class_exists( 'WC_Seq_Order_Number_Pro' ) ) {
			return $this->order_prefix . $transaction_id;
		}
		if ( ! empty( $this->order_prefix ) ) {
			return $this->order_prefix . '-' . $order->get_id();
		}
		return $order->get_id();
	}

	/**
	 * Trigger the sales order cycle to automatically create packages and invoices.
	 *
	 * This function makes a POST request to the Zoho Inventory API to trigger the
	 * sales order cycle for the specified sales order ID. It sends a request to:
	 * {api_url}/inventory/v1/salesorders/{salesorder_id}/triggersocycle?confirm_order=true&organization_id={org_id}
	 * with the body: {"execute_so_cycle":true}
	 *
	 * @param int    $order_id The WooCommerce order ID.
	 * @param string $zi_sales_order_id The Zoho Inventory sales order ID.
	 * @return void
	 */
	private function trigger_sales_order_cycle( $order_id, $zi_sales_order_id ) {
		$order = wc_get_order( $order_id );
		if ( ! $order ) {
			return;
		}

		$handlefunction = new CMBIRD_Auth_Zoho();

		$zoho_inventory_url = $this->zoho_inventory_url;
		$zoho_inventory_oid = $this->zoho_inventory_oid;

		// Get Zoho Inventory access token.
		$zoho_access_token  = get_option( 'cmbird_zoho_inventory_access_token' );
		$zoho_refresh_token = get_option( 'cmbird_zoho_inventory_refresh_token' );
		$zoho_timestamp     = get_option( 'cmbird_zoho_inventory_timestamp' );

		// Check if token needs refresh.
		if ( intval( $zoho_timestamp ) < time() ) {
			$respo_at_js = $handlefunction->get_zoho_refresh_token( $zoho_refresh_token, 'zoho_inventory' );
			if ( is_wp_error( $respo_at_js ) || ! is_array( $respo_at_js ) || ! array_key_exists( 'access_token', $respo_at_js ) ) {
				$order->add_order_note( 'Failed to trigger Zoho Sales Order Cycle: Unable to refresh access token.' );
				$order->save();
				return;
			}
			$zoho_access_token = $respo_at_js['access_token'];
			update_option( 'cmbird_zoho_inventory_access_token', $zoho_access_token );
			update_option( 'cmbird_zoho_inventory_timestamp', time() + $respo_at_js['expires_in'] );
		}

		$url  = $zoho_inventory_url . 'inventory/v1/salesorders/' . $zi_sales_order_id . '/triggersocycle?confirm_order=true&organization_id=' . $zoho_inventory_oid;
		$body = wp_json_encode( array( 'execute_so_cycle' => true ) );

		$args = array(
			'body'    => $body,
			'headers' => array(
				'Authorization' => 'Bearer ' . $zoho_access_token,
				'Content-Type'  => 'application/json',
			),
		);

		$response = wp_remote_post( $url, $args );

		if ( is_wp_error( $response ) ) {
			$error_msg = 'Failed to trigger Zoho Sales Order Cycle: ' . $response->get_error_message();
			$order->add_order_note( $error_msg );
			$message      = 'Error triggering Zoho Sales Order Cycle for Order ID: ' . $order_id . '. ' . $error_msg;
			$class_common = new CMBIRD_Common_Functions();
			$class_common->send_email( 'Error Triggering Zoho Sales Order Cycle', $message );
		} else {
			$body = wp_remote_retrieve_body( $response );
			$json = json_decode( $body );
			$code = isset( $json->code ) ? $json->code : null;
			$msg  = isset( $json->message ) ? $json->message : 'Unknown response';

			if ( '0' === $code || 0 === $code ) {
				$order->add_order_note( 'Zoho Sales Order Cycle Triggered: Packages and invoices will be automatically created.' );
			} else {
				$error_msg = 'Failed to trigger Zoho Sales Order Cycle: ' . $msg;
				$order->add_order_note( $error_msg );
				$message      = 'Error triggering Zoho Sales Order Cycle for Order ID: ' . $order_id . '. ' . $error_msg;
				$class_common = new CMBIRD_Common_Functions();
				$class_common->send_email( 'Error Triggering Zoho Sales Order Cycle', $message );
			}
		}
		$order->save();
	}

	/**
	 * Void the sales order if cancelled in WooCommerce.
	 *
	 * @param int $order_id Order ID.
	 */
	public function salesorder_void( $order_id ) {
		if ( ! $order_id ) {
			return;
		}
		$zoho_inventory_access_token = get_option( 'cmbird_zoho_inventory_access_token' );
		if ( empty( $zoho_inventory_access_token ) ) {
			return;
		}

		$order = wc_get_order( $order_id );
		// return if order is already voided.
		if ( $order->get_meta( 'zi_salesorder_void', true ) ) {
			return;
		}
		$order_status = $order->get_status();

		if ( 'cancelled' === $order_status || 'wc-merged' === $order_status ) {
			$zi_sales_order_id  = $order->get_meta( 'zi_salesorder_id', true );
			$zoho_inventory_oid = $this->zoho_inventory_oid;
			$zoho_inventory_url = $this->zoho_inventory_url;

			$url                      = $zoho_inventory_url . 'inventory/v1/salesorders/' . $zi_sales_order_id . '/status/void?organization_id=' .
				$zoho_inventory_oid;
			$data                     = '';
			$execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
			$json                     = $execute_curl_call_handle->execute_curl_call_post( $url, $data );

			$errmsg = $json->message;
			$code   = $json->code;
			if ( '0' === $code || 0 === $code ) {
				// Add order meta key "zi_salesorder_void" to true.
				$order->update_meta_data( 'zi_salesorder_void', true );
				$order->add_order_note( 'Zoho Order Void: ' . $errmsg );
				$order->save();
				return;
			}
		} else {
			return;
		}
	}

	/**
	 * Sync order from Woo to Zoho.
	 *
	 * @param string $pdt1 JSON string.
	 * @param int    $order_id Order ID.
	 * @return string Error message
	 */
	public function single_saleorder_zoho_inventory( $pdt1, $order_id ) {
		// start logging.
		// $fd = fopen( __DIR__ . '/order-sync-backend.txt', 'w+' );

		$zoho_inventory_oid = $this->zoho_inventory_oid;
		$zoho_inventory_url = $this->zoho_inventory_url;
		$data               = array(
			'JSONString'      => $pdt1,
			'organization_id' => $zoho_inventory_oid,
		);

		// logging.
		// fwrite($fd, PHP_EOL . 'Data log : ' . print_r($data, true));

		if ( empty( $data['JSONString'] ) ) {
			return '';
		}
		$ignore_auto_no           = ( true === $this->enable_auto_number || 'true' === $this->enable_auto_number || '1' === $this->enable_auto_number || 1 === $this->enable_auto_number ) ? 'false' : 'true';
		$url                      = $zoho_inventory_url . 'inventory/v1/salesorders?ignore_auto_number_generation=' . $ignore_auto_no;
		$execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
		$json                     = $execute_curl_call_handle->execute_curl_call_post( $url, $data );

		// fwrite( $fd, PHP_EOL . 'Data log : ' . print_r( $json, true ) );
		$response = array();
		$code     = $json->code;
		$errmsg   = $json->message;
		// fwrite($fd, PHP_EOL . 'Code : ' . $code);

		// Retry once if code is 400.
		if ( 400 === $code || '400' === $code ) {
			// Wait a brief moment before retry.
			sleep( 1 );
			$json   = $execute_curl_call_handle->execute_curl_call_post( $url, $data );
			$code   = $json->code;
			$errmsg = $json->message;
		}

		if ( '0' === $code || 0 === $code ) {
			foreach ( $json->salesorder as $key => $value ) {

				if ( 'salesorder_id' === $key ) {
					$response['zi_salesorder_id'] = $value;
					// $order->add_meta_data('zi_salesorder_id', $value, true);
				}
			}
		} else {
			// send email to admin with the error message.
			// create message that contains the error message and order id.
			$message = 'Error in Zoho Inventory Order Sync: ' . $errmsg;
			// append the link to the order edit page.
			$message     .= '<br><br><a href="' . admin_url( 'admin.php?page=wc-orders&action=edit&id=' . $order_id ) . '">Click here to view the order in WP Admin</a>';
			$message     .= '<br><br>Order ID: ' . $order_id;
			$message     .= '<br><br>Request Body: ' . $pdt1;
			$class_common = new CMBIRD_Common_Functions();
			$class_common->send_email( 'Error in Zoho Order Sync', $message );
		}
		$response['message'] = $errmsg;
		// fclose( $fd );

		return $response;
	}

	/**
	 * Function for updating single sales order.
	 *
	 * @param int    $order_id Order ID.
	 * @param string $zi_sales_order_id Zoho Sales Order ID.
	 * @param string $pdt1 JSON string.
	 * @return string Error message
	 */
	public function single_saleorder_zoho_inventory_update( $order_id, $zi_sales_order_id, $pdt1 ) {
		// $fd = fopen( __DIR__. '/single_saleorder_update.txt', 'w+' );

		$response           = array();
		$zoho_inventory_oid = $this->zoho_inventory_oid;
		$zoho_inventory_url = $this->zoho_inventory_url;

		$url  = $zoho_inventory_url . 'inventory/v1/salesorders/' . $zi_sales_order_id;
		$data = array(
			'JSONString'      => $pdt1,
			'organization_id' => $zoho_inventory_oid,
		);

		$order = wc_get_order( $order_id );
		// fwrite($fd, PHP_EOL. print_r($data, true)); //logging response.
		$execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
		$json                     = $execute_curl_call_handle->execute_curl_call_put( $url, $data );

		// $code = $json->code;
		$errmsg                       = $json->message;
		$response['message']          = $errmsg;
		$response['zi_salesorder_id'] = $zi_sales_order_id;

		// echo '<pre>'; print_r($errmsg);

		$package_id = $order->get_meta( 'zi_package_id', true );

		if ( ! empty( $package_id ) ) {
			// fwrite($fd, PHP_EOL. 'inside package exists'); //logging response.
			foreach ( $json->salesorder as $key => $value ) {
				if ( 'date' === $key ) {
					$ship_date = $value;
				}

				if ( 'line_items' === $key ) {
					foreach ( $value as $kk => $vv ) {
						$line_items[] = '{"so_line_item_id": "' . $vv->line_item_id . '","quantity": "' . $vv->quantity . '"}';
					}
					$impot = implode( ',', $line_items );

					$json_package = '"date": "' . $ship_date . '","line_items": [' . $impot . ']';

					$url_package = $zoho_inventory_url . 'inventory/v1/packages/' . $package_id;
					$data3       = array(
						'JSONString'      => '{' . $json_package . '}',
						'organization_id' => $zoho_inventory_oid,
					);

					$res_package = $execute_curl_call_handle->execute_curl_call_put( $url_package, $data3 );
				}
			}
		}
		// fclose( $fd ); //end of logging.
		return $response;
	}

	/**
	 * Function to get all Zoho Taxes.
	 *
	 * @param int $percentage Tax percentage.
	 * @return string Tax ID.
	 */
	private function zi_get_tax_id( $percentage ) {
		// $fd = fopen( __DIR__ . '/zi_get_tax_id.txt', 'a+' );
		// get all options that contain zoho_inventory_tax_rate_ in the name using global $wpdb.
		global $wpdb;
		$zoho_tax_rates       = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options WHERE option_name LIKE 'cmbird_zoho_inventory_tax_rate_%'" );
		$input_tax_percentage = floor( $percentage * 10 ) / 10;
		// fwrite( $fd, PHP_EOL . 'Input Tax Percentage: ' . $input_tax_percentage );
		$tax_id = '';
		// for each zoho_tax_rate, check if the tax percentage matches the input percentage. The percentage at the end of the value e.g. 69497000002395146##BTW@@Hoog Exclusief##tax##21.
		foreach ( $zoho_tax_rates as $zoho_tax_rate ) {
			$tax_rate       = explode( '##', $zoho_tax_rate->option_value );
			$tax_percentage = $tax_rate[3];
			// Round the stored tax percentage to one decimal place for comparison.
			$stored_tax_percentage = round( $tax_percentage, 1 );
			// Compare the rounded tax percentages with a tolerance for floating-point precision errors.
			if ( abs( $stored_tax_percentage - $input_tax_percentage ) < 0.01 ) {
				$tax_id = $tax_rate[0];
				break;
			}
		}
		return $tax_id;
	}

	/**
	 * Get Zoho Inventory tax group ID by total tax percentage.
	 *
	 * This function attempts to find a matching tax group when multiple
	 * WooCommerce tax rates are applied (compound taxes). Tax groups in Zoho
	 * represent compound taxes as a single combined rate.
	 *
	 * @param float $total_percentage Total tax percentage from all WooCommerce tax rates.
	 * @return string Tax group ID if found, empty string otherwise.
	 */
	private function zi_get_tax_group_id( $total_percentage ) {
		$tax_groups = get_option( 'cmbird_zi_tax_groups', array() );

		if ( empty( $tax_groups ) || ! is_array( $tax_groups ) ) {
			return '';
		}

		foreach ( $tax_groups as $tax_group ) {
			if ( ! isset( $tax_group['tax_id'] ) || ! isset( $tax_group['tax_percentage'] ) ) {
				continue;
			}

			// Allow tolerance of 0.2 for compound tax matching.
			if ( abs( $tax_group['tax_percentage'] - $total_percentage ) <= 0.5 ) {
				return $tax_group['tax_id'];
			}
		}

		return '';
	}

	/**
	 * Function to get Zoho Inventory IGST tax ID for inter-state transactions in India.
	 *
	 * @param int $percentage Tax percentage.
	 * @return string IGST Tax ID.
	 */
	private function zi_get_igst_tax_id( $percentage ) {
		// Get all options that contain zoho_inventory_tax_rate_ in the name for IGST.
		global $wpdb;
		$zoho_tax_rates       = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options WHERE option_name LIKE 'cmbird_zoho_inventory_tax_rate_%'" );
		$input_tax_percentage = floor( $percentage * 10 ) / 10;
		$tax_id               = '';

		// For each zoho_tax_rate, check if it's an IGST tax and the percentage matches.
		foreach ( $zoho_tax_rates as $zoho_tax_rate ) {
			$tax_rate = explode( '##', $zoho_tax_rate->option_value );
			if ( count( $tax_rate ) >= 4 ) {
				$tax_name       = $tax_rate[1];
				$tax_percentage = $tax_rate[3];

				// Check if this is an IGST tax (contains "IGST" in the name).
				if ( stripos( $tax_name, 'IGST' ) !== false ) {
					// Round the stored tax percentage to one decimal place for comparison.
					$stored_tax_percentage = round( $tax_percentage, 1 );
					// Compare the rounded tax percentages with a tolerance for floating-point precision errors.
					if ( abs( $stored_tax_percentage - $input_tax_percentage ) < 0.01 ) {
						$tax_id = $tax_rate[0];
						break;
					}
				}
			}
		}

		// If no IGST tax found, fallback to regular tax_id.
		if ( empty( $tax_id ) ) {
			$tax_id = $this->zi_get_tax_id( $percentage );
		}

		return $tax_id;
	}

	/**
	 * TODO: Get Order Transaction Fees
	 *
	 * @param int $order_id Order ID.
	 * @return float Transaction fees
	 */
	protected function get_transaction_fees( $order_id ) {
		$order = wc_get_order( $order_id );
		$fees  = $order->get_meta( '_stripe_fee' );
		if ( ! empty( $fees ) ) {
			return $fees;
		}
		$fees = $order->get_meta( '_paypal_transaction_fee' );
		if ( ! empty( $fees ) ) {
			return $fees;
		}
		return 0;
	}

	/**
	 * Create contact for order - handles both regular users and guest orders
	 *
	 * @param int  $userid User ID (0 for guest orders).
	 * @param int  $order_id Order ID.
	 * @param bool $is_guest_order Whether this is a guest order.
	 * @return string Contact ID.
	 */
	private function create_contact_for_order( $userid, $order_id, $is_guest_order ) {
		if ( $is_guest_order ) {
			// For guest orders, create contact using order data and store contact info in order meta.
			$order = wc_get_order( $order_id );
			return $this->create_guest_contact( $order );
		} else {
			// For regular users, use existing contact creation function.
			$contact_class_handle = new CMBIRD_Contact_ZI();
			return $contact_class_handle->cmbird_contact_create_function( $userid );
		}
	}

	/**
	 * Create contact person for order - handles both regular users and guest orders
	 *
	 * @param int  $userid User ID (0 for guest orders).
	 * @param int  $order_id Order ID.
	 * @param bool $is_guest_order Whether this is a guest order.
	 * @return string Contact ID.
	 */
	private function create_contact_person_for_order( $userid, $order_id, $is_guest_order ) {
		if ( $is_guest_order ) {
			// For guest orders, create contact person using order data.
			$order = wc_get_order( $order_id );
			return $this->create_guest_contact_person( $order );
		} else {
			// For regular users, use existing contact person creation function.
			$contact_class_handle = new CMBIRD_Contact_ZI();
			return $contact_class_handle->cmbird_create_contact_person( $userid );
		}
	}

	/**
	 * Create Zoho contact for guest order
	 *
	 * @param WC_Order $order Order object.
	 * @return string Contact ID.
	 */
	private function create_guest_contact( $order ) {
		// Billing Details.
		$contact_name     = $order->get_billing_first_name() . ' ' . $order->get_billing_last_name();
		$company_name     = $order->get_billing_company();
		$billing_address  = $order->get_billing_address_1();
		$billing_address2 = $order->get_billing_address_2();
		$billing_city     = $order->get_billing_city();
		$billing_state    = $order->get_billing_state();
		$billing_postcode = $order->get_billing_postcode();
		$billing_country  = $order->get_billing_country();

		// Shipping Details.
		$shipping_attention = trim( $order->get_shipping_first_name() . ' ' . $order->get_shipping_last_name() );
		$shipping_address   = $order->get_shipping_address_1();
		$shipping_address2  = $order->get_shipping_address_2();
		$shipping_city      = $order->get_shipping_city();
		$shipping_state     = $order->get_shipping_state();
		$shipping_postcode  = $order->get_shipping_postcode();
		$shipping_country   = $order->get_shipping_country();
		// If shipping not available assign billing as shipping (same behavior as cmbird_contact_create_function()).
		if ( empty( $shipping_address ) ) {
			$shipping_address = $billing_address;
		}
		if ( empty( $shipping_address2 ) ) {
			$shipping_address2 = $billing_address2;
		}
		if ( empty( $shipping_postcode ) ) {
			$shipping_postcode = $billing_postcode;
		}
		if ( empty( $shipping_city ) ) {
			$shipping_city = $billing_city;
		}
		if ( empty( $shipping_country ) ) {
			$shipping_country = $billing_country;
		}
		if ( empty( $shipping_state ) ) {
			$shipping_state = $billing_state;
		}
		if ( '' === $shipping_attention ) {
			$shipping_attention = $contact_name;
		}
		// Customer Details.
		$customer_email = $order->get_billing_email();
		$customer_phone = $order->get_billing_phone();

		// Get currency code.
		$currency_code         = $order->get_currency();
		$multi_currency_handle = new CMBIRD_Multicurrency_Zoho();
		$currency_id           = $multi_currency_handle->zoho_currency_data( $currency_code, '', $order );

		// Build contact data.
		$data = array(
			'contact_name'     => $contact_name,
			'company_name'     => $company_name,
			'contact_type'     => 'customer',
			'currency_id'      => $currency_id,
			'contact_persons'  => array(
				array(
					'first_name' => $order->get_billing_first_name(),
					'last_name'  => $order->get_billing_last_name(),
					'email'      => $customer_email,
					'phone'      => $customer_phone,
				),
			),
			'billing_address'  => array(
				'attention' => $contact_name,
				'address'   => $billing_address,
				'street2'   => $billing_address2,
				'city'      => $billing_city,
				'state'     => $billing_state,
				'zip'       => $billing_postcode,
				'country'   => $billing_country,
			),
			'shipping_address' => array(
				'attention' => $shipping_attention,
				'address'   => $shipping_address,
				'street2'   => $shipping_address2,
				'city'      => $shipping_city,
				'state'     => $shipping_state,
				'zip'       => $shipping_postcode,
				'country'   => $shipping_country,
			),
		);
		// append place_of_contact if zoho_inventory_domain is 'in'.
		if ( 'in' === $this->zoho_inventory_domain ) {
			$data['place_of_contact'] = $billing_state;
		}

		// Create contact in Zoho (data must be JSON string in 'JSONString' key, as in cmbird_contact_create_function()).
		$zoho_inventory_oid = $this->zoho_inventory_oid;
		$zoho_inventory_url = $this->zoho_inventory_url;
		$url                = $zoho_inventory_url . 'inventory/v1/contacts';

		$post_data = array(
			'JSONString'      => wp_json_encode( $data ),
			'organization_id' => $zoho_inventory_oid,
		);

		$execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
		$json                     = $execute_curl_call_handle->execute_curl_call_post( $url, $post_data );

		if ( isset( $json->code ) && ( 0 === $json->code || '0' === $json->code ) ) {
			$contact_id = $json->contact->contact_id;

			// Store contact details in order meta.
			$order->update_meta_data( 'zi_contact_id', $contact_id );

			if ( isset( $json->contact->billing_address->address_id ) ) {
				$order->update_meta_data( 'zi_billing_address_id', $json->contact->billing_address->address_id );
			}

			if ( isset( $json->contact->shipping_address->address_id ) ) {
				$order->update_meta_data( 'zi_shipping_address_id', $json->contact->shipping_address->address_id );
			}

			if ( isset( $json->contact->primary_contact_id ) ) {
				$order->update_meta_data( 'zi_primary_contact_id', $json->contact->primary_contact_id );
			}

			if ( isset( $json->contact->created_time ) ) {
				$order->update_meta_data( 'zi_created_time', $json->contact->created_time );
			}

			if ( isset( $json->contact->last_modified_time ) ) {
				$order->update_meta_data( 'zi_last_modified_time', $json->contact->last_modified_time );
			}

			if ( isset( $json->contact->currency_id ) ) {
				$order->update_meta_data( 'zi_currency_id', $json->contact->currency_id );
			}

			$order->save();

			return $contact_id;
		}

		return '';
	}

	/**
	 * Create Zoho contact person for guest order
	 *
	 * @param WC_Order $order Order object.
	 * @return string Contact ID.
	 */
	private function create_guest_contact_person( $order ) {
		// For guest orders, just return the main contact ID since it's already created with contact person.
		return $order->get_meta( 'zi_contact_id', true );
	}
}
$CMBIRD_Order_Sync_ZI = new CMBIRD_Order_Sync_ZI();
