<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css"
        integrity="sha512-SzlrxWUlpfuzQ+pcUCosxcglQRNAq/DZjVsC0lE40xsADsfeQoEypE+enwcOiGjk/bSuGGKHEyjSoQ1zVisanQ=="
        crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
</html>
<?php
/**
 * WooCommerce Product Importer
 *
 * @package Automattic\WooCommerce\Internal\CLI\Migrator\Core
 */

declare( strict_types=1 );

namespace Automattic\WooCommerce\Internal\CLI\Migrator\Core;

use WC_Product;
use WC_Product_Simple;
use WC_Product_Variable;
use WC_Product_Variation;
use WP_Error;
use Exception;
use Automattic\WooCommerce\Utilities\FeaturesUtil;

defined( 'ABSPATH' ) || exit;

/**
 * WooCommerceProductImporter class.
 *
 * Handles the creation and updating of WooCommerce products from mapped data.
 * This class focuses on the actual product creation logic, following WordPress
 * coding standards and our established architecture patterns.
 *
 * @internal This class is part of the CLI Migrator feature and should not be used directly.
 */
class WooCommerceProductImporter {

	/**
	 * Default timeout for image downloads in seconds.
	 *
	 * @var int
	 */
	private const DEFAULT_IMAGE_TIMEOUT = 10;

	/**
	 * Maximum number of images to process per product.
	 *
	 * @var int
	 */
	private const MAX_IMAGES_PER_PRODUCT = 50;

	/**
	 * Import options and configuration.
	 *
	 * @var array
	 */
	private array $import_options;

	/**
	 * Progress callback function for per-product updates.
	 *
	 * @var callable|null
	 */
	private $progress_callback = null;

	/**
	 * Statistics tracking for import operations.
	 *
	 * @var array
	 */
	private array $import_stats = array(
		'products_created'   => 0,
		'products_updated'   => 0,
		'products_skipped'   => 0,
		'images_processed'   => 0,
		'errors_encountered' => 0,
	);

	/**
	 * Migration data including image and variation mappings for session persistence.
	 *
	 * @var array
	 */
	private array $migration_data = array(
		'images_mapping'     => array(),
		'variations_mapping' => array(),
	);

	/**
	 * Mapping of original attribute names to taxonomy names for current product.
	 *
	 * @var array
	 */
	private array $current_attribute_mapping = array();

	/**
	 * Constructor - parameterless to support WooCommerce DI container.
	 */
	public function __construct() {
		$this->import_options = $this->get_default_options();
	}

	/**
	 * Configure the importer with options.
	 *
	 * @param array $options Import options and configuration.
	 */
	public function configure( array $options ): void {
		$this->import_options = array_merge( $this->import_options, $options );
	}

	/**
	 * Set progress callback for per-product import updates.
	 *
	 * @param callable|null $callback Function to call with progress updates.
	 * Receives: (current_index, total_count, product_name, result).
	 */
	public function set_progress_callback( ?callable $callback ): void {
		$this->progress_callback = $callback;
	}

	/**
	 * Import a single product from mapped data.
	 *
	 * @param array $product_data Mapped WooCommerce product data.
	 * @param array $source_data  Original source platform data for reference.
	 * @return array Import result with status and details.
	 */
	public function import_product( array $product_data, array $source_data = array() ): array {
		$start_time   = microtime( true );
		$product_name = $product_data['name'] ?? 'Unknown Product';

		$this->current_attribute_mapping = array();

		try {
			wc_get_logger()->info( "Starting import for product: {$product_name}", array( 'source' => 'wc-migrator' ) );

			$validation_result = $this->validate_product_data( $product_data );
			if ( ! $validation_result['valid'] ) {
				wc_get_logger()->error( "Validation failed for product: {$product_name} - " . $validation_result['message'], array( 'source' => 'wc-migrator' ) );
				return $this->create_error_result( 'validation_failed', $validation_result['message'], $product_data );
			}

			$existing_product_id = $this->find_existing_product( $product_data, $source_data );

			if ( $existing_product_id && $this->import_options['skip_existing'] ) {
				++$this->import_stats['products_skipped'];
				return $this->create_success_result( 'skipped', $existing_product_id, 'Product already exists and skip_existing is enabled' );
			}

			$product_type = $this->determine_product_type( $product_data );
			$product      = $this->get_or_create_product_object( $existing_product_id, $product_type );

			if ( ! $product ) {
				return $this->create_error_result( 'product_creation_failed', 'Failed to create product object', $product_data );
			}

			if ( $existing_product_id ) {
				$existing_migration_data = $product->get_meta( '_migration_data' );
				if ( is_array( $existing_migration_data ) ) {
					$this->migration_data['images_mapping']     = $existing_migration_data['images_mapping'] ?? array();
					$this->migration_data['variations_mapping'] = $existing_migration_data['variations_mapping'] ?? array();
				}
			}

			$this->set_basic_product_properties( $product, $product_data );

			$this->set_product_taxonomies( $product, $product_data );

			$this->handle_product_images( $product, $product_data['images'] ?? array() );

			wc_get_logger()->debug( "Processing {$product_type} product: {$product_name}", array( 'source' => 'wc-migrator' ) );

			switch ( $product_type ) {
				case 'variable':
					$this->handle_variable_product( $product, $product_data );
					break;
				case 'simple':
				default:
					$this->handle_simple_product( $product, $product_data );
					break;
			}

			$product_id = $product->save();

			if ( ! $product_id ) {
				return $this->create_error_result( 'save_failed', 'Failed to save product to database', $product_data );
			}

			$this->handle_post_save_operations( $product_id, $product_data, $source_data );

			if ( $existing_product_id ) {
				++$this->import_stats['products_updated'];
			} else {
				++$this->import_stats['products_created'];
			}

			$duration = microtime( true ) - $start_time;
			$action   = $existing_product_id ? 'updated' : 'created';

			wc_get_logger()->info(
				"Successfully {$action} product: {$product_name} (ID: {$product_id}) in {$duration}s",
				array( 'source' => 'wc-migrator' )
			);

			return $this->create_success_result( $action, $product_id, "Product {$action} successfully in {$duration}s" );

		} catch ( Exception $e ) {
			++$this->import_stats['errors_encountered'];
			$duration = microtime( true ) - $start_time;

			wc_get_logger()->error(
				"Exception importing product: {$product_name} after {$duration}s - " . $e->getMessage(),
				array(
					'source'    => 'wc-migrator',
					'exception' => $e,
				)
			);

			return $this->create_error_result( 'exception', $e->getMessage(), $product_data );
		}
	}

	/**
	 * Import a batch of products.
	 *
	 * @param array $products_data Array of mapped product data.
	 * @param array $source_data_batch Array of original source data for reference.
	 * @return array Batch import results.
	 */
	public function import_batch( array $products_data, array $source_data_batch = array() ): array {
		$results     = array();
		$batch_stats = array(
			'successful' => 0,
			'failed'     => 0,
			'skipped'    => 0,
		);

		$total_count = count( $products_data );

		foreach ( $products_data as $index => $product_data ) {
			$source_data  = $source_data_batch[ $index ] ?? array();
			$product_name = $product_data['name'] ?? 'Unknown Product';

			$result = $this->import_product( $product_data, $source_data );

			$results[] = $result;

			if ( 'success' === $result['status'] ) {
				if ( 'skipped' === $result['action'] ) {
					++$batch_stats['skipped'];
				} else {
					++$batch_stats['successful'];
				}
			} else {
				++$batch_stats['failed'];
			}

			if ( $this->progress_callback ) {
				call_user_func( $this->progress_callback, $index + 1, $total_count, $product_name, $result );
			}
		}

		return array(
			'results' => $results,
			'stats'   => $batch_stats,
		);
	}

	/**
	 * Get current import statistics.
	 *
	 * @return array Import statistics.
	 */
	public function get_import_stats(): array {
		return $this->import_stats;
	}

	/**
	 * Reset import statistics.
	 */
	public function reset_stats(): void {
		$this->import_stats = array(
			'products_created'   => 0,
			'products_updated'   => 0,
			'products_skipped'   => 0,
			'images_processed'   => 0,
			'errors_encountered' => 0,
		);
	}

	/**
	 * Get default import options.
	 *
	 * @return array Default options.
	 */
	private function get_default_options(): array {
		return array(
			'skip_existing'           => false,
			'update_existing'         => true,
			'import_images'           => true,
			'image_timeout'           => self::DEFAULT_IMAGE_TIMEOUT,
			'max_images_per_product'  => self::MAX_IMAGES_PER_PRODUCT,
			'skip_duplicate_images'   => false,
			'create_categories'       => true,
			'create_tags'             => true,
			'handle_variations'       => true,
			'assign_default_category' => false,
			'dry_run'                 => false,
		);
	}

	/**
	 * Validate product data before import.
	 *
	 * @param array $product_data Product data to validate.
	 * @return array Validation result.
	 */
	private function validate_product_data( array $product_data ): array {
		$required_fields = array( 'name' );
		$missing_fields  = array();

		foreach ( $required_fields as $field ) {
			if ( empty( $product_data[ $field ] ) ) {
				$missing_fields[] = $field;
			}
		}

		if ( ! empty( $missing_fields ) ) {
			return array(
				'valid'   => false,
				'message' => 'Missing required fields: ' . implode( ', ', $missing_fields ),
			);
		}

		return array( 'valid' => true );
	}

	/**
	 * Find existing product by various identifiers.
	 *
	 * @param array $product_data Mapped product data.
	 * @return int|null Existing product ID or null if not found.
	 */
	private function find_existing_product( array $product_data ): ?int {
		if ( ! empty( $product_data['original_product_id'] ) ) {
			$existing_posts = get_posts(
				array(
					'post_type'   => 'product',
					'post_status' => 'any', // Find regardless of status.
					'meta_key'    => '_original_product_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
					'meta_value'  => $product_data['original_product_id'], // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
					'fields'      => 'ids',
					'numberposts' => 1,
				)
			);

			if ( ! empty( $existing_posts ) ) {
				return (int) $existing_posts[0];
			}
		}

		if ( ! empty( $product_data['sku'] ) ) {
			$product_id = wc_get_product_id_by_sku( $product_data['sku'] );
			if ( $product_id ) {
				return $product_id;
			}
		}

		if ( ! empty( $product_data['slug'] ) ) {
			$post = get_page_by_path( $product_data['slug'], OBJECT, 'product' );
			if ( $post ) {
				return $post->ID;
			}
		}

		return null;
	}


	/**
	 * Determine product type from product data.
	 *
	 * @param array $product_data Product data.
	 * @return string Product type.
	 */
	private function determine_product_type( array $product_data ): string {
		if ( isset( $product_data['is_variable'] ) ) {
			return $product_data['is_variable'] ? 'variable' : 'simple';
		}

		if ( ! empty( $product_data['variations'] ) && count( $product_data['variations'] ) >= 1 ) {
			return 'variable';
		}

		if ( ! empty( $product_data['attributes'] ) ) {
			foreach ( $product_data['attributes'] as $attribute ) {
				if ( ! empty( $attribute['is_variation'] ) || ! empty( $attribute['variation'] ) ) {
					return 'variable';
				}
			}
		}

		return 'simple';
	}

	/**
	 * Get or create product object with proper type conversion handling.
	 *
	 * @param int|null $existing_product_id Existing product ID if updating.
	 * @param string   $required_type Required product type.
	 * @return WC_Product|null Product object or null on failure.
	 */
	private function get_or_create_product_object( ?int $existing_product_id, string $required_type ): ?WC_Product {
		if ( ! $existing_product_id ) {
			return $this->create_product_object( $required_type );
		}

		$existing_product = wc_get_product( $existing_product_id );
		if ( ! $existing_product ) {
			return $this->create_product_object( $required_type );
		}

		$current_type = $existing_product->get_type();
		if ( $current_type === $required_type ) {
			return $existing_product;
		}

		wc_get_logger()->info(
			"Converting product ID {$existing_product_id} from {$current_type} to {$required_type}",
			array( 'source' => 'wc-migrator' )
		);

		switch ( $required_type ) {
			case 'variable':
				return new WC_Product_Variable( $existing_product_id );
			case 'simple':
			default:
				return new WC_Product_Simple( $existing_product_id );
		}
	}

	/**
	 * Create appropriate product object based on type.
	 *
	 * @param string $product_type Product type.
	 * @return WC_Product|null Product object or null on failure.
	 */
	private function create_product_object( string $product_type ): ?WC_Product {
		switch ( $product_type ) {
			case 'variable':
				return new WC_Product_Variable();
			case 'simple':
			default:
				return new WC_Product_Simple();
		}
	}

	/**
	 * Set basic product properties common to all product types.
	 *
	 * @param WC_Product $product      Product object.
	 * @param array      $product_data Product data.
	 */
	private function set_basic_product_properties( WC_Product $product, array $product_data ): void {
		$product->set_name( $product_data['name'] );

		if ( ! empty( $product_data['slug'] ) ) {
			$product->set_slug( $product_data['slug'] );
		}

		if ( ! empty( $product_data['description'] ) ) {
			$product->set_description( $product_data['description'] );
		}

		if ( ! empty( $product_data['short_description'] ) ) {
			$product->set_short_description( $product_data['short_description'] );
		}

		if ( ! empty( $product_data['status'] ) ) {
			$product->set_status( $product_data['status'] );
		}

		if ( ! empty( $product_data['sku'] ) ) {
			$product->set_sku( $product_data['sku'] );
		}

		if ( isset( $product_data['catalog_visibility'] ) ) {
			$product->set_catalog_visibility( $product_data['catalog_visibility'] );
		}

		if ( ! empty( $product_data['date_created_gmt'] ) ) {
			$product->set_date_created( $product_data['date_created_gmt'] );
		}

		if ( ! empty( $product_data['weight'] ) ) {
			$product->set_weight( $product_data['weight'] );
		}

		if ( ! empty( $product_data['tax_status'] ) ) {
			$product->set_tax_status( $product_data['tax_status'] );
		}

		if ( ! empty( $product_data['metafields'] ) ) {
			foreach ( $product_data['metafields'] as $key => $value ) {
				if ( ! empty( $key ) ) {
					$product->add_meta_data( $key, $value, true );
				}
			}
		}

		if ( ! empty( $product_data['meta_data'] ) ) {
			foreach ( $product_data['meta_data'] as $meta ) {
				if ( ! empty( $meta['key'] ) ) {
					$product->add_meta_data( $meta['key'], $meta['value'] ?? '', true );
				}
			}
		}
	}

	/**
	 * Handle simple product specific data.
	 *
	 * @param WC_Product_Simple $product      Simple product object.
	 * @param array             $product_data Product data.
	 */
	private function handle_simple_product( WC_Product_Simple $product, array $product_data ): void {
		if ( ! empty( $product_data['regular_price'] ) ) {
			$product->set_regular_price( $product_data['regular_price'] );
			$product->set_price( $product_data['regular_price'] );
		}

		if ( ! empty( $product_data['sale_price'] ) ) {
			$product->set_sale_price( $product_data['sale_price'] );
			$product->set_price( $product_data['sale_price'] );
		}

		if ( ! empty( $product_data['sku'] ) ) {
			add_filter( 'wc_product_has_unique_sku', '__return_false', 999 );
			$product->set_sku( $product_data['sku'] );
			remove_filter( 'wc_product_has_unique_sku', '__return_false', 999 );
		}

		if ( isset( $product_data['manage_stock'] ) ) {
			$product->set_manage_stock( $product_data['manage_stock'] );
		}

		if ( ! empty( $product_data['stock_quantity'] ) ) {
			$product->set_stock_quantity( (int) $product_data['stock_quantity'] );
		}

		if ( ! empty( $product_data['stock_status'] ) ) {
			$product->set_stock_status( $product_data['stock_status'] );
		}

		if ( array_key_exists( 'cost_of_goods', $product_data ) ) {
			$cogs_is_enabled = FeaturesUtil::feature_is_enabled( 'cost_of_goods_sold' );
			if ( $cogs_is_enabled ) {
				$product->set_cogs_value( (float) $product_data['cost_of_goods'] );
			} else {
				$this->set_cogs_value_direct( $product, (float) $product_data['cost_of_goods'] );
			}
		}
	}

	/**
	 * Handle variable product specific data.
	 *
	 * @param WC_Product_Variable $product      Variable product object.
	 * @param array               $product_data Product data.
	 */
	private function handle_variable_product( WC_Product_Variable $product, array $product_data ): void {
		$product->set_sku( '' );
		$product->set_regular_price( '' );
		$product->set_sale_price( '' );
		$product->set_manage_stock( false );
		$product->set_weight( '' );
		$product->set_stock_quantity( null );

		if ( ! empty( $product_data['attributes'] ) ) {
			$this->setup_attributes( $product, $product_data['attributes'] );
		}

		$product_id = $product->save();

		if ( ! empty( $product_data['variations'] ) && $this->import_options['handle_variations'] ) {
			$this->sync_variations( $product, $product_data['variations'] );
		}
	}

	/**
	 * Set product attributes.
	 *
	 * @param WC_Product $product    Product object.
	 * @param array      $attributes Attributes data.
	 */
	private function set_product_attributes( WC_Product $product, array $attributes ): void {
		$product_attributes = array();

		foreach ( $attributes as $attribute_data ) {
			if ( empty( $attribute_data['name'] ) ) {
				continue;
			}

			$attribute = new \WC_Product_Attribute();
			$attribute->set_name( $attribute_data['name'] );
			$attribute->set_options( $attribute_data['options'] ?? array() );
			$attribute->set_variation( $attribute_data['is_variation'] ?? $attribute_data['variation'] ?? false );
			$attribute->set_visible( $attribute_data['is_visible'] ?? $attribute_data['visible'] ?? true );

			$product_attributes[] = $attribute;
		}

		$product->set_attributes( $product_attributes );
	}

	/**
	 * Sets up product attributes for variable products with global taxonomy creation.
	 *
	 * @param WC_Product_Variable $product The variable product object.
	 * @param array               $attributes_data              Standardized attribute data from mapper.
	 */
	private function setup_attributes( WC_Product_Variable $product, array $attributes_data ): void {
		$woo_attributes                  = array();
		$this->current_attribute_mapping = array();

		foreach ( $attributes_data as $attribute_info ) {
			$attr_name    = $attribute_info['name'] ?? null;
			$attr_options = $attribute_info['options'] ?? array();
			if ( empty( $attr_name ) || empty( $attr_options ) ) {
				continue;
			}

			$taxonomy_slug = sanitize_title( $attr_name );
			$taxonomy_name = 'pa_' . $taxonomy_slug;
			$attribute_id  = 0;

			if ( ! taxonomy_exists( $taxonomy_name ) ) {
				$attribute_id = wc_create_attribute(
					array(
						'name'         => $attr_name,
						'slug'         => $taxonomy_slug,
						'type'         => 'select',
						'order_by'     => 'menu_order',
						'has_archives' => false,
					)
				);
				if ( is_wp_error( $attribute_id ) ) {
					wc_get_logger()->warning( "Failed to create attribute '{$attr_name}': " . $attribute_id->get_error_message(), array( 'source' => 'wc-migrator' ) );
					continue;
				}

				register_taxonomy(
					$taxonomy_name,
					/**
					 * Filters the object types associated with the attribute taxonomy.
					 *
					 * @since 10.2.0
					 * @param array $object_types Array of object types.
					 */
					apply_filters( 'woocommerce_taxonomy_objects_' . $taxonomy_name, array( 'product' ) ),
					/**
					 * Filters the arguments for registering the attribute taxonomy.
					 *
					 * @since 10.2.0
					 * @param array $args Array of taxonomy registration arguments.
					 */
					apply_filters(
						'woocommerce_taxonomy_args_' . $taxonomy_name,
						array(
							'labels'       => array(
								'name' => $attr_name,
							),
							'hierarchical' => false,
							'show_ui'      => false,
							'show_in_rest' => true,
							'query_var'    => true,
							'rewrite'      => false,
							'public'       => false,
						)
					)
				);
			} else {
				$attribute_id = wc_attribute_taxonomy_id_by_name( $taxonomy_name );
			}

			$term_ids   = array();
			$term_slugs = array();
			foreach ( $attr_options as $value ) {
				$term_slug = sanitize_title( $value );
				$term      = get_term_by( 'slug', $term_slug, $taxonomy_name );
				if ( ! $term ) {
					$term_result = wp_insert_term( $value, $taxonomy_name, array( 'slug' => $term_slug ) );
					if ( is_wp_error( $term_result ) ) {
						wc_get_logger()->warning( "Failed to insert term '{$value}' (slug: {$term_slug}) into {$taxonomy_name}: " . $term_result->get_error_message(), array( 'source' => 'wc-migrator' ) );
						continue;
					}
					$term_ids[]   = $term_result['term_id'];
					$term_slugs[] = $term_slug;
				} else {
					$term_ids[]   = $term->term_id;
					$term_slugs[] = $term->slug;
				}
			}

			$woo_attribute = new \WC_Product_Attribute();
			$woo_attribute->set_name( $taxonomy_name );
			$woo_attribute->set_id( $attribute_id );
			$woo_attribute->set_options( $term_ids );
			$woo_attribute->set_position( $attribute_info['position'] ?? 0 );
			$woo_attribute->set_visible( $attribute_info['is_visible'] ?? true );
			$woo_attribute->set_variation( $attribute_info['is_variation'] ?? true );
			$woo_attributes[] = $woo_attribute;

			$this->current_attribute_mapping[ $attr_name ] = $taxonomy_name;
		}

		$product->set_attributes( $woo_attributes );
	}

	/**
	 * Creates or updates product variations with proper mapping and lookup.
	 *
	 * @param WC_Product_Variable $product The parent variable product.
	 * @param array               $variations_data Standardized variation data from mapper.
	 */
	private function sync_variations( WC_Product_Variable $product, array $variations_data ): void {
		$parent_product_id       = $product->get_id();
		$parent_original_id      = $product->get_meta( '_original_product_id' );
		$processed_variation_ids = array();

		$variation_count = count( $variations_data );
		wc_get_logger()->debug( "Syncing {$variation_count} variations for product ID {$parent_product_id}", array( 'source' => 'wc-migrator' ) );

		$attribute_taxonomy_map = $this->current_attribute_mapping;

		// Build fallback mapping from product attributes if current mapping is empty.
		if ( empty( $attribute_taxonomy_map ) ) {
			$product_attributes = $product->get_attributes();
			foreach ( $product_attributes as $taxonomy => $attribute_obj ) {
				if ( $attribute_obj->get_variation() ) {
					$attribute_label = wc_attribute_label( $taxonomy, $product );
					// Store mapping with both original case and lowercase for case-insensitive lookup.
					$attribute_taxonomy_map[ $attribute_label ]               = $taxonomy;
					$attribute_taxonomy_map[ strtolower( $attribute_label ) ] = $taxonomy;
				}
			}
		}

		foreach ( $variations_data as $var_data ) {
			$original_variant_id = $var_data['original_id'] ?? null;
			if ( ! $original_variant_id ) {
				wc_get_logger()->warning( 'Skipping variation: Missing original ID.', array( 'source' => 'wc-migrator' ) );
				continue;
			}

			$variation_id = null;
			$variation    = null;

			if ( isset( $this->migration_data['variations_mapping'][ $original_variant_id ] ) ) {
				$_variation_id = $this->migration_data['variations_mapping'][ $original_variant_id ];
				$_variation    = wc_get_product( $_variation_id );
				if ( $_variation instanceof WC_Product_Variation && $_variation->get_parent_id() === $parent_product_id ) {
					$variation    = $_variation;
					$variation_id = $_variation_id;
				} else {
					unset( $this->migration_data['variations_mapping'][ $original_variant_id ] );
				}
			}

			if ( ! $variation ) {
				$query_args = array(
					'post_parent' => $parent_product_id,
					'post_type'   => 'product_variation',
					'numberposts' => 1,
					'post_status' => 'any',
					'meta_key'    => '_original_variant_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
					'meta_value'  => $original_variant_id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
					'fields'      => 'ids',
				);

				$found_ids = get_posts( $query_args );
				if ( ! empty( $found_ids ) ) {
					$variation_id = $found_ids[0];
					$variation    = wc_get_product( $variation_id );
					if ( ! ( $variation instanceof WC_Product_Variation ) ) {
						wc_get_logger()->warning( "Found post ID {$variation_id} for original variant {$original_variant_id}, but it's not a WC_Product_Variation.", array( 'source' => 'wc-migrator' ) );
						$variation    = null;
						$variation_id = null;
					}
				}
			}

			if ( ! $variation ) {
				$variation = new WC_Product_Variation();
				$variation->set_parent_id( $parent_product_id );
			}

			$variation->set_status( 'publish' );
			$variation->set_menu_order( $var_data['menu_order'] ?? 0 );

			$variation->set_regular_price( $var_data['regular_price'] ?? '' );
			$variation->set_sale_price( $var_data['sale_price'] ?? '' );

			if ( ! empty( $var_data['sku'] ) ) {
				add_filter( 'wc_product_has_unique_sku', '__return_false', 999 );
				$variation->set_sku( $var_data['sku'] );
				remove_filter( 'wc_product_has_unique_sku', '__return_false', 999 );
			}

			$variation->set_manage_stock( $var_data['manage_stock'] ?? false );
			$variation->set_stock_quantity( $var_data['stock_quantity'] ?? null );
			$variation->set_stock_status( $var_data['stock_status'] ?? 'instock' );

			$variation->set_weight( $var_data['weight'] ?? '' );

			if ( ! empty( $var_data['tax_status'] ) ) {
				$variation->set_tax_status( $var_data['tax_status'] );
			}

			$image_original_id = $var_data['image_original_id'] ?? null;
			if ( $image_original_id && isset( $this->migration_data['images_mapping'][ $image_original_id ] ) ) {
				$variation->set_image_id( $this->migration_data['images_mapping'][ $image_original_id ] );
			} else {
				$variation->set_image_id( '' );
			}

			$wc_variation_attributes = array();
			if ( ! empty( $var_data['attributes'] ) && is_array( $var_data['attributes'] ) ) {
				foreach ( $var_data['attributes'] as $attr_name => $attr_value ) {
					if ( isset( $attribute_taxonomy_map[ $attr_name ] ) ) {
						$taxonomy                  = $attribute_taxonomy_map[ $attr_name ];
						$term_slug                 = sanitize_title( $attr_value );
						$normalized_attribute_name = wc_variation_attribute_name( $taxonomy );

						$wc_variation_attributes[ $normalized_attribute_name ] = $term_slug;
					} else {
						wc_get_logger()->warning( "Attribute taxonomy mapping not found for option '{$attr_name}' while processing variation {$original_variant_id}.", array( 'source' => 'wc-migrator' ) );
					}
				}
			}
			$variation->set_attributes( $wc_variation_attributes );

			$variation->update_meta_data( '_original_variant_id', $original_variant_id );
			if ( $parent_original_id ) {
				$variation->update_meta_data( '_original_product_id', $parent_original_id );
			}

			$saved_variation_id = $variation->save();
			if ( $saved_variation_id ) {
				$processed_variation_ids[] = $saved_variation_id;
				$this->migration_data['variations_mapping'][ $original_variant_id ] = $saved_variation_id;
				if ( ! empty( $var_data['cost_of_goods'] ) ) {
					update_post_meta( $saved_variation_id, '_cogs_total_value', (float) $var_data['cost_of_goods'] );
				}
			} else {
				wc_get_logger()->error( "Failed to save variation for original variant {$original_variant_id}", array( 'source' => 'wc-migrator' ) );
			}
		}

		WC_Product_Variable::sync( $parent_product_id );

		$processed_count = count( $processed_variation_ids );
		wc_get_logger()->debug( "Successfully synced {$processed_count}/{$variation_count} variations for product ID {$parent_product_id}", array( 'source' => 'wc-migrator' ) );
	}

	/**
	 * Create product variations (legacy method - keeping for backward compatibility).
	 *
	 * @param int   $parent_id  Parent product ID.
	 * @param array $variations Variations data.
	 */
	private function create_product_variations( int $parent_id, array $variations ): void {
		$product = wc_get_product( $parent_id );
		if ( $product instanceof WC_Product_Variable ) {
			$this->sync_variations( $product, $variations );
		}
	}

	/**
	 * Handle post-save operations like metadata and migration tracking.
	 *
	 * @param int   $product_id   Product ID.
	 * @param array $product_data Product data.
	 */
	private function handle_post_save_operations( int $product_id, array $product_data ): void {

		if ( ! empty( $product_data['original_product_id'] ) ) {
			update_post_meta( $product_id, '_original_product_id', $product_data['original_product_id'] );
		}

		if ( ! empty( $product_data['original_url'] ) ) {
			update_post_meta( $product_id, '_original_url', $product_data['original_url'] );
		}

		update_post_meta( $product_id, '_migration_data', $this->migration_data );

		if ( ! empty( $product_data['metafields'] ) ) {
			$this->update_seo_meta( $product_id, $product_data['metafields'], $product_data );
		}
	}

	/**
	 * Set product taxonomies (categories, tags, brand) before product save.
	 *
	 * @param WC_Product $product The product object.
	 * @param array      $product_data Standardized data containing taxonomies.
	 */
	private function set_product_taxonomies( WC_Product $product, array $product_data ): void {
		$product_id = $product->get_id();
		if ( ! $product_id ) {
			$product_id = $product->save();
			if ( ! $product_id ) {
				wc_get_logger()->warning( 'Could not save product to set taxonomies.', array( 'source' => 'wc-migrator' ) );
				return;
			}
		}

		$taxonomies_to_set = array();

		if ( isset( $product_data['categories'] ) && is_array( $product_data['categories'] ) && $this->import_options['create_categories'] ) {
			$term_ids = $this->get_or_create_terms( $product_data['categories'], 'product_cat' );
			if ( ! empty( $term_ids ) ) {
				$taxonomies_to_set['product_cat'] = $term_ids;
			} elseif ( $this->import_options['assign_default_category'] ) {
				$default_cat_id = get_option( 'default_product_cat' );
				if ( $default_cat_id ) {
					$taxonomies_to_set['product_cat'] = array( $default_cat_id );
					wc_get_logger()->info( "Assigned default category (ID: {$default_cat_id}) to product with no categories", array( 'source' => 'wc-migrator' ) );
				}
			} else {
				wc_get_logger()->debug( 'Product has no categories and assign_default_category is disabled', array( 'source' => 'wc-migrator' ) );
			}
		}

		if ( isset( $product_data['tags'] ) && is_array( $product_data['tags'] ) && $this->import_options['create_tags'] ) {
			$term_ids = $this->get_or_create_terms( $product_data['tags'], 'product_tag' );
			if ( ! empty( $term_ids ) ) {
				$taxonomies_to_set['product_tag'] = $term_ids;
			}
		}

		if ( ! empty( $product_data['brand']['name'] ) && taxonomy_exists( 'product_brand' ) ) {
			$brand_data = array( $product_data['brand'] );
			$term_ids   = $this->get_or_create_terms( $brand_data, 'product_brand' );
			if ( ! empty( $term_ids ) ) {
				$taxonomies_to_set['product_brand'] = $term_ids;
			}
		}

		foreach ( $taxonomies_to_set as $taxonomy => $ids ) {
			wp_set_object_terms( $product_id, $ids, $taxonomy, false );
		}
	}

	/**
	 * Helper to get or create term IDs for a given taxonomy.
	 *
	 * @param array  $terms_data Array of ['name' => ..., 'slug' => ...].
	 * @param string $taxonomy Taxonomy slug.
	 * @return array Array of term IDs.
	 */
	private function get_or_create_terms( array $terms_data, string $taxonomy ): array {
		$term_ids = array();
		foreach ( $terms_data as $term_info ) {
			$term_name = $term_info['name'] ?? null;
			$term_slug = $term_info['slug'] ?? sanitize_title( $term_name );

			if ( empty( $term_name ) || empty( $term_slug ) ) {
				continue;
			}

			$term = get_term_by( 'slug', $term_slug, $taxonomy );

			if ( ! $term ) {
				$term_result = wp_insert_term( $term_name, $taxonomy, array( 'slug' => $term_slug ) );
				if ( is_wp_error( $term_result ) ) {
					wc_get_logger()->warning( "Failed to insert term '{$term_name}' (slug: {$term_slug}) into {$taxonomy}: " . $term_result->get_error_message(), array( 'source' => 'wc-migrator' ) );
					continue;
				}
				$term_ids[] = $term_result['term_id'];
			} else {
				$term_ids[] = $term->term_id;
			}
		}
		return array_unique( $term_ids );
	}

	/**
	 * Handle product images using product object methods.
	 *
	 * @param WC_Product $product The product object.
	 * @param array      $images_data Standardized image data from mapper.
	 */
	private function handle_product_images( WC_Product $product, array $images_data ): void {
		if ( empty( $images_data ) ) {
			return;
		}

		$gallery_ids     = array();
		$featured_id     = null;
		$product_id      = $product->get_id();
		$processed_count = 0;

		foreach ( $images_data as $index => $image ) {
			if ( $processed_count >= $this->import_options['max_images_per_product'] ) {
				break;
			}

			$original_id = $image['original_id'] ?? null;
			$image_url   = $image['src'] ?? null;
			$image_alt   = $image['alt'] ?? '';
			$is_featured = $image['is_featured'] ?? ( 0 === $index );

			if ( empty( $original_id ) || empty( $image_url ) ) {
				wc_get_logger()->warning( 'Skipping image: Missing original ID or URL.', array( 'source' => 'wc-migrator' ) );
				continue;
			}

			if ( isset( $this->migration_data['images_mapping'][ $original_id ] ) && wp_attachment_is_image( $this->migration_data['images_mapping'][ $original_id ] ) ) {
				$attachment_id = $this->migration_data['images_mapping'][ $original_id ];
			} else {
				if ( ! $product_id ) {
					$product_id = $product->save();
					if ( ! $product_id ) {
						wc_get_logger()->warning( "Skipping image upload {$original_id}: Could not get product ID before sideloading.", array( 'source' => 'wc-migrator' ) );
						continue;
					}
				}

				$start_time    = microtime( true );
				$image_desc    = $image_alt ? $image_alt : $product->get_name();
				$attachment_id = $this->import_image( $image_url, $image_alt, $product_id );
				$duration      = microtime( true ) - $start_time;

				if ( is_wp_error( $attachment_id ) ) {
					wc_get_logger()->error( "Error uploading {$image_url}: " . $attachment_id->get_error_message() . " (Duration: {$duration}s)", array( 'source' => 'wc-migrator' ) );
					continue;
				}

				if ( ! $attachment_id ) {
					wc_get_logger()->warning( "Image upload failed for {$image_url} (Duration: {$duration}s)", array( 'source' => 'wc-migrator' ) );
					continue;
				}

				$this->migration_data['images_mapping'][ $original_id ] = $attachment_id;

				if ( $image_alt ) {
					update_post_meta( $attachment_id, '_wp_attachment_image_alt', $image_alt );
				}
			}

			if ( $is_featured ) {
				$featured_id = $attachment_id;
			} else {
				$gallery_ids[] = $attachment_id;
			}

			++$processed_count;
			++$this->import_stats['images_processed'];
		}

		if ( $featured_id ) {
			$product->set_image_id( $featured_id );
		}
		if ( ! empty( $gallery_ids ) ) {
			$product->set_gallery_image_ids( array_unique( $gallery_ids ) );
		}
	}

	/**
	 * Import image from URL with mapping optimization.
	 *
	 * @param string      $image_url   Image URL.
	 * @param string      $alt_text    Alt text for the image.
	 * @param string|null $original_id Original platform image ID.
	 * @param int         $product_id  Product ID for sideloading.
	 * @return int|null Attachment ID or null on failure.
	 */
	private function import_image_with_mapping( string $image_url, string $alt_text = '', ?string $original_id = null, int $product_id = 0 ): ?int {
		if ( $original_id && isset( $this->migration_data['images_mapping'][ $original_id ] ) ) {
			$attachment_id = $this->migration_data['images_mapping'][ $original_id ];
			if ( wp_attachment_is_image( $attachment_id ) ) {
				return $attachment_id;
			} else {
				unset( $this->migration_data['images_mapping'][ $original_id ] );
			}
		}

		$start_time    = microtime( true );
		$attachment_id = $this->import_image( $image_url, $alt_text, $product_id );
		$duration      = microtime( true ) - $start_time;

		if ( $attachment_id && $original_id ) {
			$this->migration_data['images_mapping'][ $original_id ] = $attachment_id;
		}

		if ( $attachment_id ) {
			$message = sprintf( 'Image uploaded successfully in %.2fs: %s -> %d', $duration, $image_url, $attachment_id );

			if ( $this->import_options['verbose'] ?? false ) {
				\WP_CLI::log( $message );
			}

			wc_get_logger()->info( $message, array( 'source' => 'wc-migrator-images' ) );
		} else {
			$message = sprintf( 'Image upload failed in %.2fs: %s', $duration, $image_url );

			if ( $this->import_options['verbose'] ?? false ) {
				\WP_CLI::warning( $message );
			}

			wc_get_logger()->error( $message, array( 'source' => 'wc-migrator-images' ) );
		}

		return $attachment_id;
	}

	/**
	 * Import image from URL.
	 *
	 * @param string $image_url Image URL.
	 * @param string $alt_text  Alt text for the image.
	 * @param int    $product_id Product ID for sideloading.
	 * @return int|null Attachment ID or null on failure.
	 */
	private function import_image( string $image_url, string $alt_text = '', int $product_id = 0 ): ?int {
		if ( $this->import_options['dry_run'] ) {
			return null;
		}

		if ( ! $this->import_options['skip_duplicate_images'] ) {
			$existing_attachment = $this->get_attachment_by_url( $image_url );
			if ( $existing_attachment ) {
				return $existing_attachment;
			}
		}

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

		add_filter( 'http_request_timeout', array( $this, 'set_image_download_timeout' ) );
		add_filter( 'http_request_args', array( $this, 'optimize_http_request_args' ) );
		add_filter( 'image_sideload_extensions', array( $this, 'add_avif_support_to_sideload' ) );
		try {
			$attachment_id = media_sideload_image( $image_url, $product_id, null, 'id' );

			if ( is_wp_error( $attachment_id ) ) {
				$message = sprintf( 'Image import failed for URL %s: %s', $image_url, $attachment_id->get_error_message() );

				if ( $this->import_options['verbose'] ?? false ) {
					\WP_CLI::warning( $message );
				}
				wc_get_logger()->error( $message, array( 'source' => 'wc-migrator-images' ) );
				return null;
			}

			if ( $alt_text ) {
				update_post_meta( $attachment_id, '_wp_attachment_image_alt', $alt_text );
			}

			return $attachment_id;
		} finally {
			remove_filter( 'http_request_timeout', array( $this, 'set_image_download_timeout' ) );
			remove_filter( 'http_request_args', array( $this, 'optimize_http_request_args' ) );
			remove_filter( 'image_sideload_extensions', array( $this, 'add_avif_support_to_sideload' ) );
		}
	}

	/**
	 * Set HTTP timeout for image downloads.
	 *
	 * @return int Modified timeout.
	 */
	public function set_image_download_timeout(): int {
		return $this->import_options['image_timeout'];
	}

	/**
	 * Optimize HTTP request arguments for faster image downloads.
	 *
	 * @param array $args HTTP request arguments.
	 * @return array Optimized arguments.
	 */
	public function optimize_http_request_args( array $args ): array {
		$args['redirection'] = 3;
		$args['timeout']     = $this->import_options['image_timeout'] ?? 30;

		return $args;
	}

	/**
	 * Add AVIF support to image sideload extensions.
	 *
	 * @param array $allowed_extensions Array of allowed file extensions.
	 * @return array Modified array with AVIF support.
	 */
	public function add_avif_support_to_sideload( array $allowed_extensions ): array {
		if ( ! in_array( 'avif', $allowed_extensions, true ) ) {
			$allowed_extensions[] = 'avif';
		}
		return $allowed_extensions;
	}

	/**
	 * Get existing attachment by URL.
	 *
	 * @param string $image_url Image URL.
	 * @return int|null Attachment ID or null if not found.
	 */
	private function get_attachment_by_url( string $image_url ): ?int {
		global $wpdb;

		$basename      = wp_basename( $image_url );
		$attachment_id = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wp_attached_file' AND meta_value LIKE %s",
				'%' . $wpdb->esc_like( $basename )
			)
		);
		return $attachment_id ? (int) $attachment_id : null;
	}

	/**
	 * Create success result array.
	 *
	 * @param string $action     Action performed (created, updated, skipped).
	 * @param int    $product_id Product ID.
	 * @param string $message    Success message.
	 * @return array Success result.
	 */
	private function create_success_result( string $action, int $product_id, string $message ): array {
		return array(
			'status'     => 'success',
			'action'     => $action,
			'product_id' => $product_id,
			'message'    => $message,
		);
	}

	/**
	 * Updates SEO meta fields if Yoast SEO is active.
	 *
	 * @param int   $product_id  The product ID.
	 * @param array $metafields  Key-value array of metafields from standardized data.
	 * @param array $product_data Full product data for fallbacks.
	 */
	private function update_seo_meta( int $product_id, array $metafields, array $product_data ): void {
		if ( ! defined( 'WPSEO_VERSION' ) ) {
			return;
		}

		$seo_title       = $metafields['global_title_tag'] ?? null;
		$seo_description = $metafields['global_description_tag'] ?? null;

		$final_seo_title       = $seo_title ? $seo_title : ( $product_data['name'] ?? '' );
		$fallback_desc         = $product_data['description'] ? $product_data['description'] : ( $product_data['short_description'] ?? '' );
		$final_seo_description = $seo_description ? $seo_description : wp_strip_all_tags( $fallback_desc );

		$current_title = get_post_meta( $product_id, '_yoast_wpseo_title', true );
		if ( $current_title !== $final_seo_title && ! empty( $final_seo_title ) ) {
			update_post_meta( $product_id, '_yoast_wpseo_title', $final_seo_title );
		}

		$current_desc = get_post_meta( $product_id, '_yoast_wpseo_metadesc', true );
		if ( $current_desc !== $final_seo_description && ! empty( $final_seo_description ) ) {
			$truncated_desc = mb_substr( $final_seo_description, 0, 160 );
			update_post_meta( $product_id, '_yoast_wpseo_metadesc', $truncated_desc );
		}
	}

	/**
	 * Set COGS value directly using meta data.
	 *
	 * @param WC_Product $product The product object.
	 * @param float      $cogs_value The COGS value to set.
	 */
	private function set_cogs_value_direct( WC_Product $product, float $cogs_value ): void {
		$product->update_meta_data( '_cogs_total_value', $cogs_value );
	}

	/**
	 * Create error result array.
	 *
	 * @param string $error_code   Error code.
	 * @param string $message      Error message.
	 * @param array  $product_data Product data that failed.
	 * @return array Error result.
	 */
	private function create_error_result( string $error_code, string $message, array $product_data ): array {
		return array(
			'status'       => 'error',
			'error_code'   => $error_code,
			'message'      => $message,
			'product_data' => $product_data,
		);
	}
}
