<?php
/**
 * Amrod App
 *
 * @category   WordPress_Plugin
 * @package    Common
 * @subpackage Suppliers
 * @author     miniOrange <info@miniorange.com>
 * @license    MIT/Expat
 * @link       https://plugins.miniorange.com/woocommerce-api-product-sync-with-woocommerce-rest-apis
 */

namespace MOWCPS\Common\Suppliers;

use WP_Error;
use MOWCPS\Common\Utils\Utils;
use MOWCPS\Common\Abstracts\Base_Supplier_App;
use MOWCPS\Common\Interfaces\Supplier_App_Interface;
use MOWCPS\Common\Constants\Constants;
use MOWCPS\Common\Services\API\API_Service;
use MOWCPS\Common\DTO\Authorization_Request;
use MOWCPS\Common\Constants\Plugin_Filters;
use MOWCPS\Common\Constants\Suppliers\Amrod as Amrod_Constants;
use MOWCPS\Common\Models\Authorization_Model;
use MOWCPS\Common\Models\Connection_Model;
use MOWCPS\Common\Models\Supplier_Model;
use MOWCPS\Common\Models\Sync_Details_DAO;
use MOWCPS\Common\Services\Authorization\Authorization_Service;
use MOWCPS\Common\Services\Logging\Supplier_Logger;
use MOWCPS\Premium\Utils\Model_Utils;

if ( ! defined( 'ABSPATH' ) ) {
	die;
}

/**
 * Class Amrod
 *
 * Handles Amrod supplier app operations
 */
class Amrod extends Base_Supplier_App implements Supplier_App_Interface {

	/**
	 * Memory map for existing category terms (API ID => WP term_id).
	 *
	 * @var array
	 */
	private static $existing_terms_map = array();

	/**
	 * Constructor
	 */
	public function __construct() {
		parent::__construct( Constants::SUPPLIER_APP_TYPE_AMROD );
	}

	/**
	 * Perform Amrod-specific connection test
	 *
	 * @param array $supplier_data Supplier data.
	 * @return array Test result.
	 */
	public function perform_connection_test( $supplier_data ) {
		// Validate supplier-specific configuration using existing validation method.
		$validation_result = $this->validate_supplier_specific_configuration( $supplier_data );
		if ( is_wp_error( $validation_result ) ) {
			return $this->get_test_result(
				false,
				sprintf( Constants::SUPPLIER_AUTH_FAILED, $this->supplier_name, $validation_result->get_error_message() ),
			);
		}

		// Load and process configuration with dynamic parameters.
		$processed_config = $this->load_and_process_configuration( $supplier_data );
		if ( is_wp_error( $processed_config ) ) {
			return $this->get_test_result(
				false,
				sprintf( Constants::SUPPLIER_CONFIG_LOADING_FAILED, $this->supplier_name, $processed_config->get_error_message() ),
			);
		}

		// Get authorization configuration.
		$authorization = $processed_config[ Constants::AUTHORIZATION ] ?? array();
		if ( empty( $authorization ) ) {
			return $this->get_test_result(
				false,
				sprintf( Constants::SUPPLIER_NO_AUTH_CONFIG, $this->supplier_name ),
			);
		}

		$authorization_request = Authorization_Request::from_request_parameters( $authorization );

		if ( is_wp_error( $authorization_request ) ) {
			return $this->get_test_result(
				false,
				sprintf( Constants::SUPPLIER_INVALID_AUTH_CONFIG, $this->supplier_name, $authorization_request->get_error_message() ),
			);
		}

		$request_settings = $authorization['auth_settings']['request_settings'];

		// Prepare request data from configuration.
		$url     = $request_settings['url'];
		$method  = strtoupper( $request_settings['method'] );
		$headers = $request_settings['headers'];
		$body    = null;

		// Process body if present.
		if ( ! empty( $request_settings['body'] ) && ! empty( $request_settings['body']['data'] ) ) {
			$body                    = $request_settings['body']['data'];
			$body                    = wp_json_encode( $body, JSON_UNESCAPED_SLASHES );
			$headers['Content-Type'] = 'application/json';
		} else {
			return $this->get_test_result(
				false,
				sprintf( Constants::SUPPLIER_NO_BODY_DATA, $this->supplier_name ),
			);
		}

		// Prepare API request.
		$api_request = array(
			'endpoint' => $url,
			'method'   => $method,
			'headers'  => $headers,
			'body'     => $body,
			'timeout'  => 30,
		);

		// Make the authentication request.
		$response = API_Service::make_api_call( $api_request['endpoint'], $api_request );

		$response_code    = wp_remote_retrieve_response_code( $response );
		$response_message = wp_remote_retrieve_response_message( $response );
		$response_body    = json_decode( wp_remote_retrieve_body( $response ), true );

		if ( is_wp_error( $response ) ) {
			return $this->get_test_result(
				false,
				sprintf( Constants::SUPPLIER_AUTH_FAILED, $this->supplier_name, $response->get_error_message() ),
				$response_code,
				$response_body,
			);
		}

		if ( ! Utils::is_status_success( $response ) ) {
			return $this->get_test_result(
				false,
				sprintf( Constants::SUPPLIER_AUTH_FAILED, $this->supplier_name, $response_message ),
				$response_code,
				$response_body,
			);
		}

		return $this->get_test_result(
			true,
			sprintf( Constants::SUPPLIER_AUTH_SUCCESSFUL, $this->supplier_name ),
			$response_code,
			$response_body,
		);
	}

	/**
	 * Validate Amrod-specific configuration
	 *
	 * @param array $supplier_data Supplier data to validate.
	 * @return bool|WP_Error True if valid, WP_Error if invalid.
	 */
	public function validate_supplier_specific_configuration( $supplier_data ) {
		// Validate required fields for Amrod.
		if ( empty( $supplier_data['username'] ) ) {
			return new WP_Error(
				Constants::ERROR_INVALID_SUPPLIER_CONFIG,
				sprintf( Constants::SUPPLIER_MISSING_USERNAME, $this->supplier_name ),
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		if ( empty( $supplier_data['password'] ) ) {
			return new WP_Error(
				Constants::ERROR_INVALID_SUPPLIER_CONFIG,
				sprintf( Constants::SUPPLIER_MISSING_PASSWORD, $this->supplier_name ),
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		if ( empty( $supplier_data['customer_code'] ) ) {
			return new WP_Error(
				Constants::ERROR_INVALID_SUPPLIER_CONFIG,
				sprintf( Constants::SUPPLIER_MISSING_CUSTOMER_CODE, $this->supplier_name ),
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		// Validate field formats if needed.
		if ( ! is_string( $supplier_data['username'] ) || strlen( $supplier_data['username'] ) < 3 ) {
			return new WP_Error(
				Constants::ERROR_INVALID_SUPPLIER_CONFIG,
				sprintf( Constants::SUPPLIER_INVALID_USERNAME, $this->supplier_name ),
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		return true;
	}

	/**
	 * Function to sync categories from Amrod to WooCommerce.
	 *
	 * @param array $arguments Cron arguments.
	 *
	 * @return void
	 */
	public static function sync_categories( $arguments ) {
		$external_api_config = Connection_Model::get_by_id( $arguments['connection_id'] );
		if ( Amrod_Constants::PRODUCTS_ENDPOINT === $external_api_config['configuration']['endpoint'] ) {
			$request = array(
				'method'  => $external_api_config['api']['configuration']['method'] ?? 'GET',
				'headers' => $external_api_config['api']['configuration']['headers'] ?? array(),
				'body'    => $external_api_config['api']['configuration']['body']['data'] ?? array(),
			);

			$auth_details       = Sync_Details_DAO::get_instance()->get_authorization_details( $arguments['supplier_id'] );
			$auth_services      = new Authorization_Service();
			$amrod_header       = $auth_services->generate_headers( $auth_details, $arguments['supplier_id'] );
			$request['headers'] = array_merge( $amrod_header, $request['headers'] );
			$data               = json_decode( API_Service::make_api_call( Amrod_Constants::CATEGORY_ENDPOINT, $request )['body'], true );

			if ( ! empty( $data ) ) {
				self::create_categories( $data );
			}
		}
	}

	/**
	 * Function to create categories and subcategories inside WooCommerce related to Amrod.
	 * Optimized version with batch processing and memory-mapped lookups.
	 *
	 * @param array $categories Categories data coming from the category endpoint of Amrod.
	 * @param bool  $parent_category Deprecated parameter (kept for backward compatibility).
	 *
	 * @return void
	 */
	public static function create_categories( $categories, $parent_category = false ) {
		// Performance optimizations: defer counting and cache invalidation.
		wp_defer_term_counting( true );
		wp_suspend_cache_invalidation( true );

		// Build memory map of existing categories for O(1) lookups.
		self::build_existing_terms_map();

		// Process categories recursively.
		self::process_categories( $categories );

		// Re-enable counting and cache.
		wp_suspend_cache_invalidation( false );
		wp_defer_term_counting( false );
	}

	/**
	 * Build memory map of existing categories by their API source ID.
	 * This enables O(1) lookups instead of database queries for each category.
	 *
	 * @return void
	 */
	private static function build_existing_terms_map() {
		$terms = get_terms(
			array(
				'taxonomy'   => 'product_cat',
				'hide_empty' => false,
				'meta_query' => array(
					array(
						'key'     => '_api_source_id',
						'compare' => 'EXISTS',
					),
				),
			)
		);

		if ( ! empty( $terms ) && ! is_wp_error( $terms ) ) {
			foreach ( $terms as $term ) {
				$api_id = get_term_meta( $term->term_id, '_api_source_id', true );
				if ( $api_id ) {
					self::$existing_terms_map[ $api_id ] = $term->term_id;
				}
			}
		}
	}

	/**
	 * Recursively process categories with slug fallback for migration.
	 * This handles both new categories and existing categories without API ID meta.
	 *
	 * @param array $categories Array of category data from API.
	 * @param int   $parent_wp_id WordPress parent term ID (0 for top-level).
	 *
	 * @return void
	 */
	private static function process_categories( $categories, $parent_wp_id = 0 ) {
		foreach ( $categories as $category_data ) {
			// Extract API data.
			$api_id      = isset( $category_data['id'] ) ? (string) $category_data['id'] : '';
			$cat_name    = $category_data['categoryName'];
			$target_slug = sanitize_title( $category_data['categoryPath'] );

			$term_id = 0;

			// STEP 1: Fast path - Check memory map for existing category by API ID.
			if ( ! empty( $api_id ) && isset( self::$existing_terms_map[ $api_id ] ) ) {
				$term_id = self::$existing_terms_map[ $api_id ];
			} else {
				// STEP 2: Migration path - Fallback to slug lookup for existing categories.
				$existing_term = get_term_by( 'slug', $target_slug, 'product_cat' );

				if ( $existing_term ) {
					$term_id = $existing_term->term_id;

					// Migration: Save API ID meta for future fast lookups.
					if ( ! empty( $api_id ) ) {
						update_term_meta( $term_id, '_api_source_id', $api_id );
						self::$existing_terms_map[ $api_id ] = $term_id;
					}
				}
			}

			// STEP 3: Update existing or insert new category.
			if ( $term_id > 0 ) {
				// Update existing category.
				$update_args = array(
					'parent' => $parent_wp_id,
					'slug'   => $target_slug,
				);

				$update_result = wp_update_term( $term_id, 'product_cat', $update_args );

				if ( is_wp_error( $update_result ) ) {
					$supplier_id = Sync_Details_DAO::get_instance()->get_supplier_id();
					Supplier_Logger::debug( $supplier_id, sprintf( 'Error updating category ID %d: %s', $term_id, $update_result->get_error_message() ) );
				}
			} else {
				// Insert new category.
				$insert_args = array(
					'parent' => $parent_wp_id,
					'slug'   => $target_slug,
				);

				$new_term = wp_insert_term( $cat_name, 'product_cat', $insert_args );

				if ( ! is_wp_error( $new_term ) ) {
					$term_id = $new_term['term_id'];

					// Save API ID meta for future lookups.
					if ( ! empty( $api_id ) ) {
						update_term_meta( $term_id, '_api_source_id', $api_id );
						self::$existing_terms_map[ $api_id ] = $term_id;
					}
				} elseif ( 'term_exists' === $new_term->get_error_code() ) {
					// Handle edge case: term exists but wasn't found by slug (conflict recovery).
					$existing_id = $new_term->get_error_data( 'term_exists' );
					if ( $existing_id ) {
						$term_id = $existing_id;

						// Heal the meta and fix parent.
						if ( ! empty( $api_id ) ) {
							update_term_meta( $term_id, '_api_source_id', $api_id );
							self::$existing_terms_map[ $api_id ] = $term_id;
						}
						wp_update_term( $term_id, 'product_cat', array( 'parent' => $parent_wp_id ) );
					}
				} else {
					$supplier_id = Sync_Details_DAO::get_instance()->get_supplier_id();
					Supplier_Logger::debug( $supplier_id, sprintf( 'Error creating category "%s": %s', $cat_name, $new_term->get_error_message() ) );
					continue; // Skip children if parent creation failed.
				}
			}

			// STEP 4: Recursively process children.
			if ( ! empty( $category_data['children'] ) && $term_id > 0 ) {
				self::process_categories( $category_data['children'], $term_id );
			}
		}
	}

	/**
	 * Function to modify the data coming from Amrod into suitable structure which is accepted by the plugin.
	 *
	 * @param string $event Trigger name on which we want to modify the data.
	 * @param array  $data API data coming from Amrod.
	 *
	 * @return array
	 */
	public static function transform_product_data( $data ) {
		if ( Plugin_Filters::EVENT_VARIATION_PRODUCTS_DATA_ARRAY_INITIAL === $data['event_specifier'] ) {

			if ( isset( $data['parent_data']['actionType'] ) && 2 === $data['parent_data']['actionType'] ) {
				self::delete_product_and_skip_sync( $data );
				$data['parent_data']     = array();
				$data['variation_array'] = array();
				return $data;
			}
			// Handle both decoupled variations and structure validation.
			$price_stock_data = array();

			// Collect data from decoupled products (separated from parent).
			$decoupled_data = self::handle_decoupled_variations( $data );
			if ( ! empty( $decoupled_data ) ) {
				$price_stock_data = $decoupled_data;
			}

			// Collect data from wrong structure products (re-merging case).
			$structure_data = self::handle_structure_validation( $data );
			if ( ! empty( $structure_data ) ) {
				$price_stock_data = array_merge( $price_stock_data, $structure_data );
			}

			// Attach captured price/stock data if any exists.
			if ( ! empty( $price_stock_data ) ) {
				$data['decoupled_price_stock'] = $price_stock_data;
			}

			// Alter SKU for single variation products.
			if ( isset( $data['variation_array'] ) && 1 === count( $data['variation_array'] ) ) {
				$data['parent_data']['fullCode'] = $data['variation_array'][0]['fullCode'];
				$data['variation_array']         = array();
			}
		} elseif ( Plugin_Filters::EVENT_ALTER_VARIATION_PRODUCT_DATA === $data['event_specifier'] ) {
			$current_variation_color_code = $data['variation_array']['codeColour'];
			if ( ! empty( $data['parent_data']['colourImages'] ) ) {
				foreach ( $data['parent_data']['colourImages'] as $value ) {
					if ( $current_variation_color_code === $value['code'] ) {
						$data['variation_array']['images'] = $value['images'];
						break;
					}
				}
			}
		} elseif ( Plugin_Filters::EVENT_ALTER_PRODUCTS_CATEGORY === $data['event_specifier'] ) {
			// Alter Categories.
			if ( ! empty( $data['product_data']['categories'] ) ) {
				$curated_categories = array();
				$category_slugs     = array();

				// Convert category paths to slugs.
				foreach ( $data['product_data']['categories'] as $category ) {
					array_push( $category_slugs, sanitize_title( $category['path'] ) );
				}

				// Remove duplicates and set categories with 0 as key.
				$category_slugs = array_unique( $category_slugs );

				if ( ! empty( $category_slugs ) ) {
					$curated_categories[0] = $category_slugs;
					$data['categories']    = $curated_categories;
				}
			}
		} elseif ( 'apply_preserved_price_stock' === $data['event_specifier'] ) {
			// Apply preserved price/stock to product object after set_fields.
			if ( ! empty( $data['product_object'] ) && ! empty( $data['decoupled_price_stock'] ) ) {
				$product_object = $data['product_object'];

				// Validate that product_object is actually an object.
				if ( ! is_object( $product_object ) ) {
					return $data; // Return unchanged if not an object.
				}

				$current_sku = $product_object->sku;

				if ( ! empty( $current_sku ) && isset( $data['decoupled_price_stock'][ $current_sku ] ) ) {
					$preserved = $data['decoupled_price_stock'][ $current_sku ];

					if ( ! empty( $preserved['sale_price'] ) ) {
						$product_object->sale_price = $preserved['sale_price'];
					}

					if ( ! empty( $preserved['price'] ) ) {
						$product_object->regular_price = $preserved['price'];
					}

					// Restore stock management settings from the original product
					if ( isset( $preserved['manage_stock'] ) ) {
						$product_object->manage_stock = ( true === $preserved['manage_stock'] || 1 === $preserved['manage_stock'] || 'yes' === $preserved['manage_stock'] ) ? 1 : -1;
					}

					// Restore stock quantity (can be null, 0, or a number)
					if ( isset( $preserved['stock_quantity'] ) ) {
						$product_object->stock_quantity = $preserved['stock_quantity'];
					}

					// Update the product_object in data array
					$data['product_object'] = $product_object;
				}
			}
		}

		return $data;
	}

	/**
	 * Delete product and its variations from site without syncing (for discontinued products).
	 * Reuses existing deletion logic to ensure consistency.
	 *
	 * @param array $data The product data array.
	 * @return void
	 */
	private static function delete_product_and_skip_sync( $data ) {
		global $wpdb;
		$skus_to_delete = array();

		// Collect parent SKU.
		if ( ! empty( $data['parent_data']['fullCode'] ) ) {
			$skus_to_delete[] = $data['parent_data']['fullCode'];
		}

		// Collect variation SKUs.
		if ( ! empty( $data['variation_array'] ) ) {
			foreach ( $data['variation_array'] as $variation ) {
				if ( ! empty( $variation['fullCode'] ) ) {
					$skus_to_delete[] = $variation['fullCode'];
				}
			}
		}

		// Delete each product/variation using existing deletion logic.
		foreach ( $skus_to_delete as $sku ) {
			$product_id = wc_get_product_id_by_sku( $sku );
			if ( $product_id ) {
				$product = wc_get_product( $product_id );
				if ( $product ) {
					$product->delete( true );

					// Clean up lookup tables (same as delete_decoupled_product_family).
					$wpdb->delete(
						$wpdb->prefix . 'wc_product_meta_lookup',
						array( 'product_id' => $product_id ),
						array( '%d' )
					);
					$wpdb->delete(
						$wpdb->prefix . 'wc_product_attributes_lookup',
						array( 'product_id' => $product_id ),
						array( '%d' )
					);
					wc_delete_product_transients( $product_id );

					// Log successful deletion.
					$supplier_id = Sync_Details_DAO::get_instance()->get_supplier_id();
					Supplier_Logger::debug( $supplier_id, sprintf( 'Deleted product with SKU: %s (actionType=0 - discontinued)', $sku ) );
				}
			}
		}
	}

	/**
	 * Handle structure validation for inverse case (re-merging separated products).
	 * Validates that variations have correct parent-child structure.
	 * If wrong structure found, deletes and captures price/stock data.
	 * Skipped for a single Amrod variation: those payloads are synced as simple products elsewhere.
	 *
	 * @param array $data The product data array.
	 * @return array Array of price/stock data keyed by SKU for deleted products.
	 */
	private static function handle_structure_validation( $data ) {
		$price_stock_data = array();

		// Get parent SKU from API data.
		if ( empty( $data['parent_data']['fullCode'] ) || empty( $data['variation_array'] ) ) {
			return $price_stock_data;
		}

		// Matches transform below: one variation → simple; existing simple SKU = variation fullCode.
		if ( 1 === count( $data['variation_array'] ) ) {
			return $price_stock_data;
		}

		$expected_parent_sku = $data['parent_data']['fullCode'];

		// Check each variation for wrong structure.
		foreach ( $data['variation_array'] as $variation ) {
			if ( ! empty( $variation['fullCode'] ) ) {
				$variation_sku = $variation['fullCode'];
				$product_id    = wc_get_product_id_by_sku( $variation_sku );

				if ( ! $product_id ) {
					continue; // Product doesn't exist, will be created correctly.
				}

				$product = wc_get_product( $product_id );
				if ( ! $product ) {
					continue;
				}

				// Check if wrong structure and delete if needed.
				if ( self::has_wrong_structure( $product, $expected_parent_sku ) ) {
					$captured_data    = self::delete_product_family( $product );
					$price_stock_data = array_merge( $price_stock_data, $captured_data );
				}
			}
		}

		return $price_stock_data;
	}

	/**
	 * Check if product has wrong structure compared to expected parent.
	 *
	 * @param WC_Product $product The product to check.
	 * @param string     $expected_parent_sku Expected parent SKU.
	 * @return bool True if wrong structure, false otherwise.
	 */
	private static function has_wrong_structure( $product, $expected_parent_sku ) {
		$product_type = $product->get_type();

		if ( 'variation' === $product_type ) {
			// Check if it has the correct parent.
			$parent_id = $product->get_parent_id();
			if ( $parent_id ) {
				$parent_product = wc_get_product( $parent_id );
				if ( $parent_product ) {
					$current_parent_sku = $parent_product->get_sku();
					return $current_parent_sku !== $expected_parent_sku;
				}
			}
			return true; // No parent found - wrong structure.
		} elseif ( in_array( $product_type, array( 'simple', 'variable' ), true ) ) {
			// Standalone product should be a variation - wrong structure.
			return true;
		}

		return true; // Unknown type - assume wrong.
	}

	/**
	 * Delete product family and capture all price/stock data.
	 *
	 * @param WC_Product $product The product to delete.
	 * @return array Price/stock data for all deleted products.
	 */
	private static function delete_product_family( $product ) {
		global $wpdb;
		$product_type        = $product->get_type();
		$price_stock_data    = array();
		$ids_to_delete       = array();
		$image_ids_to_delete = array();

		// Collect image IDs from current product.
		$image_ids = self::get_product_image_ids( $product );
		if ( ! empty( $image_ids ) ) {
			$image_ids_to_delete = array_merge( $image_ids_to_delete, $image_ids );
		}

		// Capture current product data.
		$product_sku = $product->get_sku();
		if ( ! empty( $product_sku ) ) {
			$captured = self::get_price_and_stock_by_sku( $product_sku );
			if ( $captured ) {
				$price_stock_data[ $product_sku ] = $captured;
			}
		}

		if ( 'variation' === $product_type ) {
			// Delete parent (which deletes all variations).
			$parent_id = $product->get_parent_id();
			if ( $parent_id ) {
				$parent_product = wc_get_product( $parent_id );
				if ( $parent_product ) {
					// Collect images from parent and all variations.
					$parent_images = self::get_product_image_ids( $parent_product );
					if ( ! empty( $parent_images ) ) {
						$image_ids_to_delete = array_merge( $image_ids_to_delete, $parent_images );
					}

					$variation_ids = $parent_product->get_children();
					foreach ( $variation_ids as $var_id ) {
						$variation = wc_get_product( $var_id );
						if ( $variation ) {
							$var_images = self::get_product_image_ids( $variation );
							if ( ! empty( $var_images ) ) {
								$image_ids_to_delete = array_merge( $image_ids_to_delete, $var_images );
							}
						}
					}

					// Capture parent and all variations.
					$parent_data      = self::capture_parent_and_variations( $parent_product );
					$price_stock_data = array_merge( $price_stock_data, $parent_data );

					$ids_to_delete[] = $parent_id;
					$parent_product->delete( true );
				} else {
					$ids_to_delete[] = $product->get_id();
					$product->delete( true );
				}
			} else {
				$ids_to_delete[] = $product->get_id();
				$product->delete( true );
			}
		} elseif ( 'variable' === $product_type ) {
			// Collect images from all variations.
			$variation_ids = $product->get_children();
			foreach ( $variation_ids as $var_id ) {
				$variation = wc_get_product( $var_id );
				if ( $variation ) {
					$var_images = self::get_product_image_ids( $variation );
					if ( ! empty( $var_images ) ) {
						$image_ids_to_delete = array_merge( $image_ids_to_delete, $var_images );
					}
				}
			}

			// Capture all variations before deletion.
			$parent_data      = self::capture_parent_and_variations( $product );
			$price_stock_data = array_merge( $price_stock_data, $parent_data );

			$ids_to_delete[] = $product->get_id();
			$product->delete( true );
		} else {
			// Simple or other - just delete.
			$ids_to_delete[] = $product->get_id();
			$product->delete( true );
		}

		// Clean up lookup tables for all deleted products.
		foreach ( $ids_to_delete as $product_id ) {
			$wpdb->delete(
				$wpdb->prefix . 'wc_product_meta_lookup',
				array( 'product_id' => $product_id ),
				array( '%d' )
			);
			$wpdb->delete(
				$wpdb->prefix . 'wc_product_attributes_lookup',
				array( 'product_id' => $product_id ),
				array( '%d' )
			);
			wc_delete_product_transients( $product_id );
		}

		// Clean up orphaned images (inline deletion).
		if ( ! empty( $image_ids_to_delete ) ) {
			foreach ( array_unique( $image_ids_to_delete ) as $image_id ) {
				$reference_count = Model_Utils::get_image_reference_count( $image_id );

				// Check variation gallery usage in serialized format.
				$variation_gallery_usage = $wpdb->get_var(
					$wpdb->prepare(
						"SELECT COUNT(*) FROM {$wpdb->postmeta} 
						WHERE meta_key = 'woo_variation_gallery_images' 
						AND meta_value LIKE %s",
						'%' . $wpdb->esc_like( 'i:' . $image_id . ';' ) . '%'
					)
				);

				// Delete if not used anywhere.
				if ( 0 === $reference_count && 0 === (int) $variation_gallery_usage ) {
					wp_delete_attachment( $image_id, true );
				}
			}
		}

		return $price_stock_data;
	}

	/**
	 * Capture price/stock data for parent and all its variations.
	 *
	 * @param WC_Product_Variable $parent_product Parent product.
	 * @return array Price/stock data keyed by SKU.
	 */
	private static function capture_parent_and_variations( $parent_product ) {
		$price_stock_data = array();

		// Capture parent data.
		$parent_sku = $parent_product->get_sku();
		if ( ! empty( $parent_sku ) ) {
			$captured = self::get_price_and_stock_by_sku( $parent_sku );
			if ( $captured ) {
				$price_stock_data[ $parent_sku ] = $captured;
			}
		}

		// Capture all variations.
		$variation_ids = $parent_product->get_children();
		foreach ( $variation_ids as $variation_id ) {
			$variation = wc_get_product( $variation_id );
			if ( $variation ) {
				$variation_sku = $variation->get_sku();
				if ( ! empty( $variation_sku ) ) {
					$captured = self::get_price_and_stock_by_sku( $variation_sku );
					if ( $captured ) {
						$price_stock_data[ $variation_sku ] = $captured;
					}
				}
			}
		}

		return $price_stock_data;
	}

	/**
	 * Handle decoupled products by deleting them from existing database.
	 * This processes products marked with [decoupled] => 1 and removes them completely.
	 * Returns price and stock data of deleted products keyed by SKU.
	 *
	 * @param array $data The product data array.
	 * @return array Array of price/stock data keyed by SKU. Empty array if no decoupled products.
	 */
	private static function handle_decoupled_variations( $data ) {
		// Check if the parent product is decoupled.
		if ( ! empty( $data['parent_data']['decoupled'] ) && 1 === intval( $data['parent_data']['decoupled'] ) ) {
			return self::delete_decoupled_product_family( $data );
		}
		return array();
	}

	/**
	 * Delete a decoupled product family (parent + all variations) from database.
	 * Captures price and stock data before deletion and returns it.
	 * Also cleans up orphaned product images.
	 *
	 * @param array $data The product data array containing parent and variation SKUs.
	 * @return array Array of price/stock data keyed by SKU.
	 */
	private static function delete_decoupled_product_family( $data ) {
		$skus_to_delete      = array();
		$price_stock_data    = array();
		$image_ids_to_delete = array();
		$real_parent_id      = null;

		// Add parent SKU to deletion list.
		if ( ! empty( $data['parent_data']['fullCode'] ) ) {
			$skus_to_delete[] = $data['parent_data']['fullCode'];
		}

		// Add all variation SKUs to deletion list.
		if ( ! empty( $data['variation_array'] ) ) {
			foreach ( $data['variation_array'] as $variation ) {
				if ( ! empty( $variation['fullCode'] ) ) {
					$skus_to_delete[] = $variation['fullCode'];
				}
			}
		}

		// Find the REAL parent product (the product that currently contains these variations).
		// Example: ALT-ECH-R is being decoupled from ALT-ECH parent.
		// Case 1: ALT-ECH-R exists as variation under ALT-ECH → get parent from ALT-ECH-R
		// Case 2: Only ALT-ECH-R-S, ALT-ECH-R-M exist under ALT-ECH → get parent from first child

		// STEP 1: Try parent SKU first.
		if ( ! empty( $data['parent_data']['fullCode'] ) ) {
			$parent_sku        = $data['parent_data']['fullCode'];
			$parent_product_id = wc_get_product_id_by_sku( $parent_sku );

			if ( $parent_product_id ) {
				$parent_product = wc_get_product( $parent_product_id );
				if ( $parent_product && $parent_product->get_parent_id() ) {
					$real_parent_id = $parent_product->get_parent_id();
				}
			}
		}

		// STEP 2: Fallback - Get parent from first child variation.
		if ( ! $real_parent_id && ! empty( $data['variation_array'] ) ) {
			foreach ( $data['variation_array'] as $variation ) {
				if ( ! empty( $variation['fullCode'] ) ) {
					$child_sku = $variation['fullCode'];
					$child_id  = wc_get_product_id_by_sku( $child_sku );

					if ( $child_id ) {
						$child_product = wc_get_product( $child_id );
						if ( $child_product && $child_product->get_parent_id() ) {
							$real_parent_id = $child_product->get_parent_id();
							break; // Found parent, stop searching.
						}
					}
				}
			}
		}

		// STEP 3: Capture price/stock, collect image IDs, and collect attribute terms BEFORE deletion.
		$deleted_attributes = array(); // Collect attribute terms from deleted variations.
		foreach ( $skus_to_delete as $sku ) {
			$product_id = wc_get_product_id_by_sku( $sku );
			if ( $product_id ) {
				$product = wc_get_product( $product_id );
				if ( $product ) {
					// Capture price/stock data.
					$captured_data = self::get_price_and_stock_by_sku( $sku );
					if ( $captured_data ) {
						$price_stock_data[ $sku ] = $captured_data;
					}

					// Collect image IDs for later cleanup.
					$image_ids = self::get_product_image_ids( $product );
					if ( ! empty( $image_ids ) ) {
						$image_ids_to_delete = array_merge( $image_ids_to_delete, $image_ids );
					}

					// Collect attribute terms from variations being deleted (reuse existing product load).
					if ( $product->is_type( 'variation' ) ) {
						$variation_attrs = $product->get_variation_attributes();
						foreach ( $variation_attrs as $taxonomy => $term_slug ) {
							// Normalize taxonomy key: remove 'attribute_' prefix to match $remaining_terms structure.
							$normalized_taxonomy = str_replace( 'attribute_', '', $taxonomy );

							if ( ! isset( $deleted_attributes[ $normalized_taxonomy ] ) ) {
								$deleted_attributes[ $normalized_taxonomy ] = array();
							}
							if ( ! empty( $term_slug ) ) {
								$deleted_attributes[ $normalized_taxonomy ][] = $term_slug;
							}
						}
					}
				}
			}
		}

		// Remove duplicates from deleted attributes.
		foreach ( $deleted_attributes as $taxonomy => $terms ) {
			$deleted_attributes[ $taxonomy ] = array_unique( $terms );
		}

		$image_ids_to_delete = array_unique( $image_ids_to_delete );

		// STEP 4: Delete each product/variation from database.
		global $wpdb;
		foreach ( $skus_to_delete as $sku ) {
			$product_id = wc_get_product_id_by_sku( $sku );
			if ( $product_id ) {
				$product = wc_get_product( $product_id );
				if ( $product ) {
					$product->delete( true );

					// Clean up WooCommerce lookup tables to prevent SKU conflicts.
					$wpdb->delete(
						$wpdb->prefix . 'wc_product_meta_lookup',
						array( 'product_id' => $product_id ),
						array( '%d' )
					);
					$wpdb->delete(
						$wpdb->prefix . 'wc_product_attributes_lookup',
						array( 'product_id' => $product_id ),
						array( '%d' )
					);
					wc_delete_product_transients( $product_id );
				}
			}
		}

		// STEP 5: Remove unused attribute terms from parent product (optimized batch operation).
		if ( $real_parent_id && ! empty( $deleted_attributes ) ) {
			self::remove_unused_attribute_terms_from_parent( $real_parent_id, $deleted_attributes, $wpdb );
		}

		// STEP 6: Remove images from REAL parent's gallery (direct meta updates - fast & clean).
		if ( $real_parent_id && ! empty( $image_ids_to_delete ) ) {
			// Remove from regular gallery (_product_image_gallery).
			$current_gallery = get_post_meta( $real_parent_id, '_product_image_gallery', true );
			if ( ! empty( $current_gallery ) ) {
				$gallery_array   = array_map( 'trim', explode( ',', $current_gallery ) );
				$updated_gallery = array_diff( $gallery_array, $image_ids_to_delete );
				update_post_meta( $real_parent_id, '_product_image_gallery', implode( ',', $updated_gallery ) );
			}

			// Remove from variation gallery (woo_variation_gallery_images).
			$variation_gallery = get_post_meta( $real_parent_id, 'woo_variation_gallery_images', true );
			if ( is_array( $variation_gallery ) && ! empty( $variation_gallery ) ) {
				$updated_variation_gallery = array_diff( $variation_gallery, $image_ids_to_delete );
				update_post_meta( $real_parent_id, 'woo_variation_gallery_images', $updated_variation_gallery );
			}
		}

		// STEP 7: Delete orphaned images (now they have 0 references after parent cleanup).
		if ( ! empty( $image_ids_to_delete ) ) {
			foreach ( $image_ids_to_delete as $image_id ) {
				$reference_count = Model_Utils::get_image_reference_count( $image_id );

				// Check variation gallery usage in serialized format.
				$variation_gallery_usage = $wpdb->get_var(
					$wpdb->prepare(
						"SELECT COUNT(*) FROM {$wpdb->postmeta} 
						WHERE meta_key = 'woo_variation_gallery_images' 
						AND meta_value LIKE %s",
						'%' . $wpdb->esc_like( 'i:' . $image_id . ';' ) . '%'
					)
				);

				// Delete if not used anywhere.
				if ( 0 === $reference_count && 0 === (int) $variation_gallery_usage ) {
					wp_delete_attachment( $image_id, true );
				}
			}
		}

		return $price_stock_data;
	}

	/**
	 * Remove unused attribute terms from parent product after decoupled variations are deleted.
	 * Optimized to use a single query to get remaining variation attributes.
	 *
	 * @param int   $parent_id          Parent product ID.
	 * @param array $deleted_attributes Array of deleted attributes: [taxonomy => [term_slug1, term_slug2]].
	 * @param wpdb  $wpdb               WordPress database object.
	 * @return void
	 */
	private static function remove_unused_attribute_terms_from_parent( $parent_id, $deleted_attributes, $wpdb ) {
		if ( empty( $deleted_attributes ) || ! $parent_id ) {
			return;
		}

		// STEP 1: Get all remaining variation attributes in a single query (optimized).
		// NOTE: This query reads from VARIATION postmeta (not parent postmeta).
		// Variation attributes are stored as: meta_key='attribute_pa_color', meta_value='red' (slug)
		// Parent product terms are stored in wp_term_relationships table, NOT in postmeta.
		$remaining_attributes = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT DISTINCT pm.meta_key, pm.meta_value 
				FROM {$wpdb->postmeta} pm
				INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID
				WHERE p.post_parent = %d 
				AND p.post_type = 'product_variation'
				AND p.post_status != 'trash'
				AND pm.meta_key LIKE %s",
				$parent_id,
				$wpdb->esc_like( 'attribute_' ) . '%'
			),
			ARRAY_A
		);

		// Debug logging.
		$supplier_id = Sync_Details_DAO::get_instance()->get_supplier_id();
		Supplier_Logger::debug( $supplier_id, sprintf( 'Removing unused attribute terms for parent ID %d. Deleted attributes: %s', $parent_id, wp_json_encode( $deleted_attributes ) ) );
		Supplier_Logger::debug( $supplier_id, sprintf( 'Remaining attributes from DB: %s', wp_json_encode( $remaining_attributes ) ) );

		// STEP 2: Build remaining terms map: [taxonomy => [term_slug1, term_slug2]].
		$remaining_terms = array();
		foreach ( $remaining_attributes as $attr ) {
			// Extract taxonomy from meta_key (e.g., 'attribute_pa_color' -> 'pa_color').
			$taxonomy = str_replace( 'attribute_', '', $attr['meta_key'] );
			if ( ! isset( $remaining_terms[ $taxonomy ] ) ) {
				$remaining_terms[ $taxonomy ] = array();
			}
			if ( ! empty( $attr['meta_value'] ) ) {
				$remaining_terms[ $taxonomy ][] = $attr['meta_value'];
			}
		}

		// Remove duplicates.
		foreach ( $remaining_terms as $taxonomy => $terms ) {
			$remaining_terms[ $taxonomy ] = array_unique( $terms );
		}

		// STEP 3: Compare deleted vs remaining and remove unused terms.
		foreach ( $deleted_attributes as $taxonomy => $deleted_slugs ) {
			$remaining_slugs = isset( $remaining_terms[ $taxonomy ] ) ? $remaining_terms[ $taxonomy ] : array();

			// Normalize slugs for comparison (lowercase, trim).
			$deleted_slugs_normalized   = array_map( 'strtolower', array_map( 'trim', $deleted_slugs ) );
			$remaining_slugs_normalized = array_map( 'strtolower', array_map( 'trim', $remaining_slugs ) );
			$unused_slugs_normalized    = array_diff( $deleted_slugs_normalized, $remaining_slugs_normalized );

			// Debug logging.
			$supplier_id = Sync_Details_DAO::get_instance()->get_supplier_id();
			Supplier_Logger::debug( $supplier_id, sprintf( 'Taxonomy %s: Deleted slugs: %s, Remaining slugs: %s, Unused slugs: %s', $taxonomy, wp_json_encode( $deleted_slugs ), wp_json_encode( $remaining_slugs ), wp_json_encode( $unused_slugs_normalized ) ) );

			if ( ! empty( $unused_slugs_normalized ) ) {
				// Map back to original slugs (preserve case).
				$unused_slugs = array();
				foreach ( $deleted_slugs as $original_slug ) {
					if ( in_array( strtolower( trim( $original_slug ) ), $unused_slugs_normalized, true ) ) {
						$unused_slugs[] = $original_slug;
					}
				}

				if ( ! empty( $unused_slugs ) ) {
					Supplier_Logger::debug( $supplier_id, sprintf( 'Removing unused terms from parent %d: Taxonomy %s, Slugs: %s', $parent_id, $taxonomy, wp_json_encode( $unused_slugs ) ) );
					// Remove unused terms from parent product.
					self::remove_terms_from_parent( $parent_id, $taxonomy, $unused_slugs );
				}
			}
		}
	}

	/**
	 * Remove specific attribute terms from parent product.
	 * Updates both term relationships and _product_attributes meta.
	 *
	 * @param int    $parent_id    Parent product ID.
	 * @param string $taxonomy     Taxonomy name (e.g., 'pa_color').
	 * @param array  $term_slugs   Array of term slugs to remove.
	 * @return void
	 */
	private static function remove_terms_from_parent( $parent_id, $taxonomy, $term_slugs ) {
		if ( empty( $term_slugs ) || ! $parent_id ) {
			return;
		}

		// Get term IDs from slugs.
		$term_ids_to_remove = array();
		$supplier_id        = Sync_Details_DAO::get_instance()->get_supplier_id();

		foreach ( $term_slugs as $term_slug ) {
			// Try slug first (most common).
			$term = get_term_by( 'slug', $term_slug, $taxonomy );

			// If not found, try name (fallback for case sensitivity).
			if ( ! $term || is_wp_error( $term ) ) {
				$term = get_term_by( 'name', $term_slug, $taxonomy );
			}

			// If still not found, try sanitized slug.
			if ( ! $term || is_wp_error( $term ) ) {
				$sanitized_slug = sanitize_title( $term_slug );
				$term           = get_term_by( 'slug', $sanitized_slug, $taxonomy );
			}

			if ( $term && ! is_wp_error( $term ) ) {
				$term_ids_to_remove[] = $term->term_id;
				Supplier_Logger::debug( $supplier_id, sprintf( 'Found term ID %d for slug "%s" in taxonomy %s', $term->term_id, $term_slug, $taxonomy ) );
			} else {
				Supplier_Logger::debug( $supplier_id, sprintf( 'Warning: Could not find term for slug "%s" in taxonomy %s', $term_slug, $taxonomy ) );
			}
		}

		if ( empty( $term_ids_to_remove ) ) {
			Supplier_Logger::debug( $supplier_id, sprintf( 'No term IDs found to remove for taxonomy %s, slugs: %s', $taxonomy, wp_json_encode( $term_slugs ) ) );
			return;
		}

		Supplier_Logger::debug( $supplier_id, sprintf( 'Removing term IDs %s from parent %d, taxonomy %s', wp_json_encode( $term_ids_to_remove ), $parent_id, $taxonomy ) );

		// Remove term relationships from parent.
		wp_remove_object_terms( $parent_id, $term_ids_to_remove, $taxonomy );

		// Update _product_attributes meta to remove term IDs.
		$product_attributes = get_post_meta( $parent_id, '_product_attributes', true );
		if ( ! empty( $product_attributes ) && is_array( $product_attributes ) && isset( $product_attributes[ $taxonomy ] ) ) {
			$attr_data = $product_attributes[ $taxonomy ];

			// Get current term IDs from attribute value (pipe-separated slugs).
			$current_value = isset( $attr_data['value'] ) ? $attr_data['value'] : '';
			if ( ! empty( $current_value ) ) {
				$current_slugs = array_filter( explode( '|', trim( $current_value, '|' ) ) );
				$updated_slugs = array_diff( $current_slugs, $term_slugs );

				if ( ! empty( $updated_slugs ) ) {
					$product_attributes[ $taxonomy ]['value'] = implode( '|', $updated_slugs ) . '|';
				} else {
					// No terms left, remove attribute entirely.
					unset( $product_attributes[ $taxonomy ] );
				}

				update_post_meta( $parent_id, '_product_attributes', $product_attributes );
			}
		}

		// Update parent product object attributes (WooCommerce way).
		$parent_product = wc_get_product( $parent_id );
		if ( $parent_product && $parent_product->is_type( 'variable' ) ) {
			$attributes = $parent_product->get_attributes();
			if ( isset( $attributes[ $taxonomy ] ) ) {
				$attr_obj = $attributes[ $taxonomy ];
				$options  = $attr_obj->get_options();

				// Remove term IDs from options.
				$updated_options = array_diff( $options, $term_ids_to_remove );

				if ( ! empty( $updated_options ) ) {
					$attr_obj->set_options( array_values( $updated_options ) );
					$attributes[ $taxonomy ] = $attr_obj;
					$parent_product->set_attributes( $attributes );
					$parent_product->save();
				} else {
					// No options left, remove attribute.
					unset( $attributes[ $taxonomy ] );
					$parent_product->set_attributes( $attributes );
					$parent_product->save();
				}

				// Clear WooCommerce transients and object cache.
				wc_delete_product_transients( $parent_id );
				clean_post_cache( $parent_id );
				wp_cache_delete( 'product-' . $parent_id, 'products' );
			}
		}
	}

	/**
	 * Get price and stock data from an existing product by SKU.
	 * Retrieves all pricing and inventory data before product deletion.
	 *
	 * @param string $sku The product SKU.
	 * @return array|null Array with price and stock data, or null if product not found.
	 */
	private static function get_price_and_stock_by_sku( $sku ) {
		$product_id = wc_get_product_id_by_sku( $sku );

		if ( ! $product_id ) {
			return null;
		}

		$product = wc_get_product( $product_id );
		if ( ! $product ) {
			return null;
		}

		return array(
			'regular_price'  => $product->get_regular_price(),
			'sale_price'     => $product->get_sale_price(),
			'price'          => $product->get_price(),
			'stock_quantity' => $product->get_stock_quantity(),
			'stock_status'   => $product->get_stock_status(),
			'manage_stock'   => $product->get_manage_stock(),
		);
	}

	/**
	 * Get all image IDs associated with a product.
	 *
	 * @param WC_Product $product The product object.
	 * @return array Array of image attachment IDs.
	 */
	private static function get_product_image_ids( $product ) {
		$image_ids = array();

		// Get featured/thumbnail image.
		$thumbnail_id = $product->get_image_id();
		if ( $thumbnail_id ) {
			$image_ids[] = $thumbnail_id;
		}

		// Get gallery images.
		$gallery_ids = $product->get_gallery_image_ids();
		if ( ! empty( $gallery_ids ) ) {
			$image_ids = array_merge( $image_ids, $gallery_ids );
		}

		// Get variation gallery images (for products with variation gallery plugin).
		$variation_gallery_ids = get_post_meta( $product->get_id(), 'woo_variation_gallery_images', true );
		if ( ! empty( $variation_gallery_ids ) && is_array( $variation_gallery_ids ) ) {
			$image_ids = array_merge( $image_ids, $variation_gallery_ids );
		}

		return array_unique( $image_ids );
	}

	/**
	 * Register Amrod product info tabs on WooCommerce product page.
	 *
	 * @param array $tabs Existing WooCommerce tabs.
	 * @return array
	 */
	public static function register_product_info_tabs( $tabs ) {
		if ( ! function_exists( 'is_product' ) || ! is_product() ) {
			return $tabs;
		}

		global $product;
		if ( ! $product instanceof \WC_Product ) {
			return $tabs;
		}

		$product_id  = $product->get_id();
		if ( ! self::should_load_amrod_product_tab_assets( $product_id ) ) {
			return $tabs;
		}

		$supplier_id      = self::get_product_supplier_id( $product_id );
		$display_settings = self::get_product_page_display_settings( $supplier_id );

		$full_branding_guide = get_post_meta( $product_id, 'fullBrandingGuide', true );
		$logo24_branding     = get_post_meta( $product_id, 'logo24BrandingGuide', true );
		$branding_templates   = self::normalize_branding_templates( get_post_meta( $product_id, 'brandingTemplates', true ) );
		$has_artwork_template = self::branding_templates_have_urls( $branding_templates );

		if ( ! empty( $display_settings['branding_guidelines'] ) && self::has_usable_guide_url_meta( $full_branding_guide ) ) {
			$tabs['branding_guidelines'] = array(
				'title'    => __( 'Branding Guidelines', 'woocommerce' ),
				'priority' => 25,
				'callback' => array( __CLASS__, 'render_branding_guidelines_tab' ),
			);
		}

		if ( ! empty( $display_settings['logo24_guide'] ) && self::has_usable_guide_url_meta( $logo24_branding ) ) {
			$tabs['logo24_guideline'] = array(
				'title'    => __( 'Logo24 Guideline', 'woocommerce' ),
				'priority' => 26,
				'callback' => array( __CLASS__, 'render_logo24_guidelines_tab' ),
			);
		}

		if ( ! empty( $display_settings['branding_templates'] ) && $has_artwork_template ) {
			$tabs['artwork_templates'] = array(
				'title'    => __( 'Artwork Template/s', 'woocommerce' ),
				'priority' => 27,
				'callback' => array( __CLASS__, 'render_branding_templates_tab' ),
			);
		}

		if ( ! empty( $display_settings['incoming_stock'] ) ) {
			$tabs['incoming_stock'] = array(
				'title'    => __( 'Incoming Stock', 'woocommerce' ),
				'priority' => 28,
				'callback' => array( __CLASS__, 'render_incoming_stock_tab' ),
			);
		}

		return $tabs;
	}

	/**
	 * Render branding guideline tab content.
	 *
	 * @return void
	 */
	public static function render_branding_guidelines_tab() {
		global $product;
		if ( ! $product instanceof \WC_Product ) {
			return;
		}

		$full_branding_guide = get_post_meta( $product->get_id(), 'fullBrandingGuide', true );
		if ( ! self::has_usable_guide_url_meta( $full_branding_guide ) ) {
			return;
		}

		echo '<h2>' . esc_html__( 'Branding Guide', 'woocommerce' ) . '</h2>';

		echo '<div class="pdf-container" style="width: 100%; margin: 20px 0; overflow: hidden;">';
		echo '<iframe src="' . esc_url( $full_branding_guide ) . '#view=FitH" width="100%" height="800px" style="border: none; display: block; width: 100%;" allow="fullscreen"></iframe>';
		echo '</div>';
	}

	/**
	 * Render Logo24 guideline tab content.
	 *
	 * @return void
	 */
	public static function render_logo24_guidelines_tab() {
		global $product;
		if ( ! $product instanceof \WC_Product ) {
			return;
		}

		$logo24_guide = get_post_meta( $product->get_id(), 'logo24BrandingGuide', true );
		if ( ! self::has_usable_guide_url_meta( $logo24_guide ) ) {
			return;
		}

		echo '<h2>' . esc_html__( 'Logo24 Guide', 'woocommerce' ) . '</h2>';

		echo '<div class="pdf-container" style="width: 100%; margin: 20px 0; overflow: hidden;">';
		echo '<iframe src="' . esc_url( $logo24_guide ) . '#view=FitH" width="100%" height="800px" style="border: none; display: block; width: 100%;" allow="fullscreen"></iframe>';
		echo '</div>';
	}

	/**
	 * Render branding templates tab content.
	 *
	 * @return void
	 */
	public static function render_branding_templates_tab() {
		global $product;
		if ( ! $product instanceof \WC_Product ) {
			return;
		}

		$templates = self::normalize_branding_templates( get_post_meta( $product->get_id(), 'brandingTemplates', true ) );
		if ( ! self::branding_templates_have_urls( $templates ) ) {
			return;
		}

		echo '<h2>' . esc_html__( 'Artwork Template/s', 'woocommerce' ) . '</h2>';

		echo '<table class="woocommerce-product-attributes shop_attributes artwork-table-styled">';
		echo '<thead><tr>';
		echo '<th class="woocommerce-product-attributes-item__label">' . esc_html__( 'Template Name', 'woocommerce' ) . '</th>';
		echo '<th class="woocommerce-product-attributes-item__label" style="text-align:center;">' . esc_html__( 'Preview', 'woocommerce' ) . '</th>';
		echo '<th class="woocommerce-product-attributes-item__label" style="text-align:center;">' . esc_html__( 'Download', 'woocommerce' ) . '</th>';
		echo '</tr></thead><tbody>';

		foreach ( $templates as $template ) {
			$url  = $template['url'] ?? '';
			$name = isset( $template['name'] ) ? ucwords( strtolower( trim( $template['name'] ) ) ) : '';
			if ( empty( $url ) ) {
				continue;
			}

			echo '<tr class="woocommerce-product-attributes-item">';
			echo '<td class="woocommerce-product-attributes-item__value">' . esc_html( $name ) . '</td>';
			echo '<td class="woocommerce-product-attributes-item__value column-action" style="text-align:center;">';
			echo '<a href="' . esc_url( $url ) . '" target="_blank" rel="noopener noreferrer" class="artwork-icon-link">';
			echo '<span class="dashicons dashicons-visibility"></span>';
			echo '</a></td>';
			echo '<td class="woocommerce-product-attributes-item__value column-action" style="text-align:center;">';
			echo '<a href="' . esc_url( $url ) . '" download class="artwork-icon-link">';
			echo '<span class="dashicons dashicons-download"></span>';
			echo '</a></td>';
			echo '</tr>';
		}

		echo '</tbody></table>';
	}

	/**
	 * Render incoming stock tab content.
	 *
	 * @return void
	 */
	public static function render_incoming_stock_tab() {
		global $product;
		if ( ! $product instanceof \WC_Product ) {
			return;
		}

		echo '<h2>' . esc_html__( 'Incoming Stock', 'woocommerce' ) . '</h2>';

		$groups = self::build_incoming_stock_rows( $product );
		if ( empty( $groups ) ) {
			echo '<p>' . esc_html__( 'Incoming stock information is not available.', 'woocommerce' ) . '</p>';
			return;
		}

		foreach ( $groups as $group ) {
			$group_rows  = $group['rows'] ?? array();
			$group_label = $group['label'] ?? __( 'Product', 'woocommerce' );
			$image_url   = ! empty( $group['image_url'] ) ? $group['image_url'] : wc_placeholder_img_src();

			if ( empty( $group_rows ) ) {
				continue;
			}

			$total_color_rows = 0;
			foreach ( $group_rows as $row ) {
				$incoming_list     = ! empty( $row['incoming_list'] ) ? $row['incoming_list'] : array( array( 'total' => 0, 'date' => 'To Be Confirmed' ) );
				$total_color_rows += count( $incoming_list );
			}

			echo '<div class="incoming-stock-wrapper" style="margin-bottom:40px;">';
			echo '<table class="shop_table shop_table_responsive incoming-stock-native-table">';
			echo '<thead><tr>';
			echo '<th class="col-colour">' . esc_html__( 'Colour', 'woocommerce' ) . '</th>';
			echo '<th class="col-code">' . esc_html__( 'Code', 'woocommerce' ) . '</th>';
			echo '<th>' . esc_html__( 'In Stock', 'woocommerce' ) . '</th>';
			echo '<th>' . esc_html__( 'Reserved', 'woocommerce' ) . '</th>';
			echo '<th>' . esc_html__( 'Incoming', 'woocommerce' ) . '</th>';
			echo '<th>' . esc_html__( 'Incoming ETA', 'woocommerce' ) . '</th>';
			echo '</tr></thead><tbody>';

			$is_first_sku = true;
			foreach ( $group_rows as $row ) {
				$incoming_list  = ! empty( $row['incoming_list'] ) ? $row['incoming_list'] : array( array( 'total' => 0, 'date' => 'To Be Confirmed' ) );
				$sku_rowcount   = count( $incoming_list );
				$stock_ok_color = ( (int) $row['on_hand'] > 0 ) ? '#2ed93f' : '#c01823';

				echo '<tr>';
				if ( $is_first_sku ) {
					echo '<td rowspan="' . esc_attr( $total_color_rows ) . '" class="col-colour-cell">';
					echo '<img src="' . esc_url( $image_url ) . '" class="stock-colour-img" alt="' . esc_attr( $group_label ) . '" />';
					echo '<span class="stock-colour-name">' . esc_html( $group_label ) . '</span>';
					echo '</td>';
					$is_first_sku = false;
				}

				echo '<td rowspan="' . esc_attr( $sku_rowcount ) . '" class="col-code-cell" data-title="' . esc_attr__( 'Code', 'woocommerce' ) . '">' . esc_html( $row['code'] ) . '</td>';
				echo '<td rowspan="' . esc_attr( $sku_rowcount ) . '" data-title="' . esc_attr__( 'In Stock', 'woocommerce' ) . '"><strong style="color:' . esc_attr( $stock_ok_color ) . '">' . esc_html( $row['on_hand'] ) . '</strong></td>';
				echo '<td rowspan="' . esc_attr( $sku_rowcount ) . '" data-title="' . esc_attr__( 'Reserved', 'woocommerce' ) . '">' . esc_html( $row['reserved'] ) . '</td>';
				echo '<td data-title="' . esc_attr__( 'Incoming', 'woocommerce' ) . '">' . esc_html( $incoming_list[0]['total'] ) . '</td>';
				echo '<td data-title="' . esc_attr__( 'Incoming ETA', 'woocommerce' ) . '">' . esc_html( $incoming_list[0]['date'] ) . '</td>';
				echo '</tr>';

				if ( $sku_rowcount > 1 ) {
					for ( $i = 1; $i < $sku_rowcount; $i++ ) {
						echo '<tr class="sub-row">';
						echo '<td data-title="' . esc_attr__( 'Incoming', 'woocommerce' ) . '">' . esc_html( $incoming_list[ $i ]['total'] ) . '</td>';
						echo '<td data-title="' . esc_attr__( 'Incoming ETA', 'woocommerce' ) . '">' . esc_html( $incoming_list[ $i ]['date'] ) . '</td>';
						echo '</tr>';
					}
				}
			}

			echo '</tbody></table></div>';
		}
	}

	/**
	 * Build incoming stock rows for product/variations.
	 *
	 * @param \WC_Product $product Product object.
	 * @return array
	 */
	private static function build_incoming_stock_rows( $product ) {
		if ( $product->is_type( 'variable' ) ) {
			$groups     = array();
			$variations = $product->get_available_variations( 'objects' );
			foreach ( $variations as $variation ) {
				if ( ! $variation instanceof \WC_Product ) {
					continue;
				}

				$attributes = $variation->get_attributes();
				$raw_color  = $attributes['pa_colours'] ?? ( $attributes['pa_colour'] ?? ( $attributes['pa_color'] ?? 'default' ) );
				$color_key  = (string) $raw_color;
				if ( '' === $color_key ) {
					$color_key = 'default';
				}

				if ( ! isset( $groups[ $color_key ] ) ) {
					$groups[ $color_key ] = array(
						'label'     => self::format_stock_group_label( $color_key ),
						'image_url' => wp_get_attachment_image_url( $variation->get_image_id(), 'thumbnail' ),
						'rows'      => array(),
					);
				}

				$groups[ $color_key ]['rows'][] = self::get_stock_row_data( $variation );
			}

			return array_values( $groups );
		}

		return array(
			array(
				'label'     => __( 'Product', 'woocommerce' ),
				'image_url' => wp_get_attachment_image_url( $product->get_image_id(), 'thumbnail' ),
				'rows'      => array( self::get_stock_row_data( $product ) ),
			),
		);
	}

	/**
	 * Format incoming stock color group label from variation attribute value.
	 *
	 * @param string $label Raw attribute value.
	 * @return string
	 */
	private static function format_stock_group_label( $label ) {
		$label = str_replace( '-', ' ', (string) $label );
		$label = trim( $label );
		if ( '' === $label || 'default' === strtolower( $label ) ) {
			return __( 'Product', 'woocommerce' );
		}

		return ucwords( strtolower( $label ) );
	}

	/**
	 * Extract stock row data from product object.
	 *
	 * @param \WC_Product $product Product or variation.
	 * @return array
	 */
	private static function get_stock_row_data( $product ) {
		$incoming_data = get_post_meta( $product->get_id(), 'incomingStock', true );
		if ( is_string( $incoming_data ) ) {
			$decoded = json_decode( $incoming_data, true );
			$incoming_data = is_array( $decoded ) ? $decoded : array();
		}

		$incoming_list = array();
		if ( is_array( $incoming_data ) ) {
			foreach ( $incoming_data as $item ) {
				$total = isset( $item['total'] ) ? $item['total'] : 0;
				$date  = isset( $item['date'] ) && ! empty( $item['date'] ) ? date_i18n( 'd M Y', strtotime( $item['date'] ) ) : 'To Be Confirmed';
				$incoming_list[] = array(
					'total' => $total,
					'date'  => $date,
				);
			}
		}

		return array(
			'code'          => $product->get_sku() ? $product->get_sku() : 'N/A',
			'image_url'     => wp_get_attachment_image_url( $product->get_image_id(), 'thumbnail' ),
			'on_hand'       => null !== $product->get_stock_quantity() ? (int) $product->get_stock_quantity() : 0,
			'reserved'      => (int) ( get_post_meta( $product->get_id(), 'reservedStock', true ) ?: 0 ),
			'incoming_list' => $incoming_list,
		);
	}

	/**
	 * Whether branding / Logo24 guide meta is a usable URL string (trim, reject placeholders).
	 *
	 * @param mixed $meta Post meta value.
	 * @return bool
	 */
	private static function has_usable_guide_url_meta( $meta ) {
		if ( is_array( $meta ) ) {
			$meta = isset( $meta['url'] ) ? $meta['url'] : '';
		}
		if ( null !== $meta && ! is_scalar( $meta ) ) {
			return false;
		}
		$url = trim( (string) $meta );
		if ( '' === $url ) {
			return false;
		}
		if ( preg_match( '/^(null|false|undefined|none|n\\/a|-)$/i', $url ) ) {
			return false;
		}
		return true;
	}

	/**
	 * Normalize branding templates meta into array.
	 *
	 * @param mixed $templates Templates meta value.
	 * @return array
	 */
	private static function normalize_branding_templates( $templates ) {
		if ( is_string( $templates ) ) {
			$decoded = json_decode( $templates, true );
			$templates = is_array( $decoded ) ? $decoded : array();
		}

		return is_array( $templates ) ? $templates : array();
	}

	/**
	 * Whether normalized branding templates include at least one row with a URL.
	 *
	 * Used when registering the artwork tab and when rendering (tab only registers when true).
	 *
	 * @param array $templates Normalized templates list.
	 * @return bool
	 */
	private static function branding_templates_have_urls( array $templates ) {
		foreach ( $templates as $template ) {
			if ( ! empty( $template['url'] ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Get supplier id for a synced product.
	 *
	 * @param int $product_id Product ID.
	 * @return int
	 */
	private static function get_product_supplier_id( $product_id ) {
		$created_supplier_id = absint( get_post_meta( $product_id, 'mowcps_connection_name_product_created', true ) );
		if ( $created_supplier_id > 0 ) {
			return $created_supplier_id;
		}

		$updated_supplier_id = absint( get_post_meta( $product_id, 'mowcps_connection_name_product_updated', true ) );
		return $updated_supplier_id > 0 ? $updated_supplier_id : 0;
	}

	/**
	 * Check if supplier belongs to Amrod.
	 *
	 * @param int $supplier_id Supplier ID.
	 * @return bool
	 */
	private static function is_amrod_supplier( $supplier_id ) {
		$supplier = Supplier_Model::get_by_id( $supplier_id );
		if ( empty( $supplier ) || empty( $supplier['app_type'] ) ) {
			return false;
		}

		return Constants::SUPPLIER_APP_TYPE_AMROD === strtolower( (string) $supplier['app_type'] );
	}



	/**
	 * Whether Product Page Display (master) applies for this product — shared by tabs + frontend CSS.
	 *
	 * @param int $product_id Product ID.
	 * @return bool
	 */
	private static function should_load_amrod_product_tab_assets( $product_id ) {
		if ( ! $product_id ) {
			return false;
		}
		$supplier_id = self::get_product_supplier_id( $product_id );
		if ( ! $supplier_id || ! self::is_amrod_supplier( $supplier_id ) ) {
			return false;
		}
		$display_settings = self::get_product_page_display_settings( $supplier_id );
		return ! empty( $display_settings['is_active'] );
	}

	/**
	 * Enqueue Dashicons + Amrod product-tab stylesheet on qualifying single product pages.
	 *
	 * Stylesheet path (relative to plugin root): `includes/common/assets/css/amrod-product-tabs.css`
	 * — migrated from the legacy addon `fix_wc_bullet_line_break` block (incoming stock table,
	 * artwork table, short description, tab panels). Runs only when
	 * {@see Amrod::should_load_amrod_product_tab_assets()} allows it for the current product.
	 *
	 * @return void
	 */
	public static function maybe_enqueue_amrod_product_tab_assets() {
		if ( ! function_exists( 'is_product' ) || ! is_product() ) {
			return;
		}
		$product_id = get_queried_object_id();
		if ( ! self::should_load_amrod_product_tab_assets( $product_id ) ) {
			return;
		}
		wp_enqueue_style( 'dashicons' );
		if ( ! defined( 'MOWCPS_PLUGIN_DIR' ) || ! defined( 'MOWCPS_PLUGIN_URL' ) ) {
			return;
		}
		$css_relative = 'includes/common/assets/css/amrod-product-tabs.css';
		$path         = MOWCPS_PLUGIN_DIR . $css_relative;
		if ( ! is_readable( $path ) ) {
			return;
		}
		wp_enqueue_style(
			'mowcps-amrod-product-tabs',
			MOWCPS_PLUGIN_URL . $css_relative,
			array(),
			(string) filemtime( $path )
		);
	}

	/**
	 * Resolve product page display settings from supplier details.
	 *
	 * @param int $supplier_id Supplier ID.
	 * @return array
	 */
	private static function get_product_page_display_settings( $supplier_id ) {
		$defaults = array(
			'is_active'           => false,
			'incoming_stock'      => false,
			'branding_guidelines' => false,
			'logo24_guide'        => false,
			'branding_templates'  => false,
		);

		$supplier = Supplier_Model::get_by_id( $supplier_id );
		if ( empty( $supplier ) || empty( $supplier['details'] ) ) {
			return $defaults;
		}

		$details = is_array( $supplier['details'] ) ? $supplier['details'] : json_decode( $supplier['details'], true );
		if ( ! is_array( $details ) ) {
			return $defaults;
		}

		$product_page_display = isset( $details['product_page_display'] ) && is_array( $details['product_page_display'] )
			? $details['product_page_display']
			: array();

		$settings = wp_parse_args( $product_page_display, $defaults );
		foreach ( $settings as $key => $value ) {
			$settings[ $key ] = (bool) $value;
		}

		return $settings;
	}
}
