<?php
/**
 * Variation Helper Utility
 *
 * Provides helper methods for detecting and working with WooCommerce variable
 * products and their variations during Amazon synchronization.
 *
 * @package    MOMCS_Sync
 * @subpackage MOMCS_Sync/includes/common/utils
 * @author     miniOrange
 */

declare(strict_types=1);

namespace MOMCS_Sync\Includes\Common\Utils;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * Class Variation_Helper
 *
 * Utility class for handling WooCommerce variable products and variations.
 */
class Variation_Helper {

	/**
	 * Check if a product is a variable product (parent with variations).
	 *
	 * @param \WC_Product|int $product WooCommerce product object or product ID.
	 * @return bool True if the product is a variable product.
	 */
	public static function is_variable_product( $product ): bool {
		if ( is_int( $product ) ) {
			$product = wc_get_product( $product );
		}

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

		return $product->is_type( 'variable' );
	}

	/**
	 * Check if a product is a variation (child of a variable product).
	 *
	 * @param \WC_Product|int $product WooCommerce product object or product ID.
	 * @return bool True if the product is a variation.
	 */
	public static function is_variation( $product ): bool {
		if ( is_int( (int) $product ) ) {
			$product = wc_get_product( $product );
		}

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

		return $product->is_type( 'variation' );
	}

	/**
	 * Check if a product is a simple product.
	 *
	 * @param \WC_Product|int $product WooCommerce product object or product ID.
	 * @return bool True if the product is a simple product.
	 */
	public static function is_simple_product( $product ): bool {
		if ( is_int( $product ) ) {
			$product = wc_get_product( $product );
		}

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

		return $product->is_type( 'simple' );
	}

	/**
	 * Get all variation IDs for a variable product (parent).
	 *
	 * @param \WC_Product|int $parent_product Variable product object or ID.
	 * @return array<int> Array of variation IDs, empty if not a variable product.
	 */
	public static function get_variation_ids( $parent_product ): array {
		if ( is_int( $parent_product ) ) {
			$parent_product = wc_get_product( $parent_product );
		}

		if ( ! $parent_product instanceof \WC_Product_Variable ) {
			return array();
		}

		return $parent_product->get_children();
	}

	/**
	 * Get parent product from a variation.
	 *
	 * @param \WC_Product|int $variation Variation product object or ID.
	 * @return \WC_Product_Variable|null Parent product or null if not found.
	 */
	public static function get_parent_product( $variation ): ?\WC_Product_Variable {
		if ( is_int( $variation ) ) {
			$variation = wc_get_product( $variation );
		}

		if ( ! $variation instanceof \WC_Product_Variation ) {
			return null;
		}

		$parent_id = $variation->get_parent_id();
		if ( ! $parent_id ) {
			return null;
		}

		$parent = wc_get_product( $parent_id );
		if ( ! $parent instanceof \WC_Product_Variable ) {
			return null;
		}

		return $parent;
	}

	/**
	 * Get the parent product ID for a variation.
	 *
	 * @param \WC_Product|int $variation Variation product object or ID.
	 * @return int Parent product ID or 0 if not found.
	 */
	public static function get_parent_id( $variation ): int {
		if ( is_int( $variation ) ) {
			$variation = wc_get_product( $variation );
		}

		if ( ! $variation instanceof \WC_Product_Variation ) {
			return 0;
		}

		return $variation->get_parent_id();
	}

	/**
	 * Get the parent SKU for a variation.
	 *
	 * @param \WC_Product|int $variation Variation product object or ID.
	 * @return string Parent SKU or empty string if not found.
	 */
	public static function get_parent_sku( $variation ): string {
		$parent = self::get_parent_product( $variation );
		if ( ! $parent ) {
			return '';
		}

		$sku = $parent->get_sku();
		return $sku ? $sku : '';
	}

	/**
	 * Extract the actual attribute value for a variation from a mapped global attribute.
	 *
	 * When a user maps a global attribute (e.g., `pa_color`) to an Amazon field,
	 * this method extracts the variation's specific value (e.g., "Red") for that attribute.
	 *
	 * @param \WC_Product|int $variation      Variation product object or ID.
	 * @param string          $attribute_slug Attribute slug (e.g., 'pa_color' or 'color').
	 * @return string|null The variation's attribute value or null if not found.
	 */
	public static function get_variation_attribute_value( $variation, string $attribute_slug ): ?string {
		if ( is_int( $variation ) ) {
			$variation = wc_get_product( $variation );
		}

		if ( ! $variation instanceof \WC_Product_Variation ) {
			return null;
		}

		$variation_id = $variation->get_id();

		// Get all variation attributes.
		$attributes = $variation->get_attributes();

		// Normalize the attribute slug for lookup.
		$normalized_slug = self::normalize_attribute_slug( $attribute_slug );

		// Generate all possible key format variants for lookup.
		// WooCommerce stores variation attributes with various key formats.
		$possible_keys = self::generate_attribute_key_variants( $attribute_slug, $normalized_slug );

		// Try to find attribute value from $variation->get_attributes().
		foreach ( $possible_keys as $key ) {
			$lowercase_key = strtolower( $key );
			if ( isset( $attributes[ $lowercase_key ] ) && '' !== $attributes[ $lowercase_key ] ) {
				$value = $attributes[ $lowercase_key ];
				return self::resolve_attribute_term_name( $value, $attribute_slug, $normalized_slug );
			}
		}

		// Fallback: Try post meta lookup for variation attributes.
		// WooCommerce may store variation attributes as post meta with 'attribute_' prefix.
		$meta_keys = array(
			'attribute_pa_' . $normalized_slug,
			'attribute_' . $normalized_slug,
			'attribute_pa_' . str_replace( '-', '_', $normalized_slug ),
			'attribute_' . str_replace( '-', '_', $normalized_slug ),
		);

		foreach ( $meta_keys as $meta_key ) {
			$meta_value = get_post_meta( $variation_id, $meta_key, true );
			if ( ! empty( $meta_value ) ) {
				return self::resolve_attribute_term_name( $meta_value, $attribute_slug, $normalized_slug );
			}
		}

		return null;
	}

	/**
	 * Generate all possible attribute key variants for lookup.
	 *
	 * WooCommerce stores variation attributes with different key formats depending on
	 * how the attribute was created and configured. This method generates all possible
	 * key format variants to ensure we find the attribute regardless of storage format.
	 *
	 * @param string $attribute_slug  Original attribute slug (e.g., 'pa_mo-color').
	 * @param string $normalized_slug Normalized slug without pa_ prefix (e.g., 'mo-color').
	 * @return array<string> Array of possible key variants.
	 */
	private static function generate_attribute_key_variants( string $attribute_slug, string $normalized_slug ): array {
		// Convert hyphens to underscores for additional variants.
		$underscore_slug            = str_replace( '-', '_', $normalized_slug );
		$underscore_attribute_slug  = str_replace( '-', '_', $attribute_slug );

		return array(
			// Original formats.
			$attribute_slug,                          // e.g., 'pa_mo-color'.
			$normalized_slug,                         // e.g., 'mo-color'.

			// With attribute_ prefix (WooCommerce variation meta format).
			'attribute_' . $attribute_slug,           // e.g., 'attribute_pa_mo-color'.
			'attribute_' . $normalized_slug,          // e.g., 'attribute_mo-color'.

			// With pa_ prefix.
			'pa_' . $normalized_slug,                 // e.g., 'pa_mo-color'.

			// Underscore variants (some attributes use underscores instead of hyphens).
			$underscore_slug,                         // e.g., 'mo_color'.
			$underscore_attribute_slug,               // e.g., 'pa_mo_color'.
			'attribute_' . $underscore_slug,          // e.g., 'attribute_mo_color'.
			'attribute_pa_' . $underscore_slug,       // e.g., 'attribute_pa_mo_color'.
			'pa_' . $underscore_slug,                 // e.g., 'pa_mo_color'.
		);
	}

	/**
	 * Resolve attribute term name from a slug or value.
	 *
	 * For taxonomy (global) attributes, the value stored in variation attributes
	 * is typically a term slug. This method resolves the slug to the human-readable
	 * term name.
	 *
	 * @param string $value           The attribute value (might be a slug or direct value).
	 * @param string $attribute_slug  Original attribute slug (e.g., 'pa_color').
	 * @param string $normalized_slug Normalized slug without pa_ prefix (e.g., 'color').
	 * @return string Resolved term name or original value if not a taxonomy term.
	 */
	private static function resolve_attribute_term_name( string $value, string $attribute_slug, string $normalized_slug ): string {
		// Determine the taxonomy to check.
		$taxonomies_to_check = array(
			$attribute_slug,                              // e.g., 'pa_mo-color'.
			'pa_' . $normalized_slug,                     // e.g., 'pa_mo-color'.
			'pa_' . str_replace( '-', '_', $normalized_slug ), // e.g., 'pa_mo_color'.
		);

		foreach ( $taxonomies_to_check as $taxonomy ) {
			if ( taxonomy_exists( $taxonomy ) ) {
				// Try to get term by slug first.
				$term = get_term_by( 'slug', $value, $taxonomy );
				if ( $term && ! is_wp_error( $term ) ) {
					return $term->name;
				}

				// Also try by name in case the value is already a name.
				$term = get_term_by( 'name', $value, $taxonomy );
				if ( $term && ! is_wp_error( $term ) ) {
					return $term->name;
				}
			}
		}

		// Return the value as-is if no term found (might be a custom attribute value).
		return $value;
	}

	/**
	 * Normalize an attribute slug by removing the 'pa_' prefix if present.
	 *
	 * @param string $attribute_slug The attribute slug to normalize.
	 * @return string Normalized attribute slug.
	 */
	public static function normalize_attribute_slug( string $attribute_slug ): string {
		if ( 0 === strpos( $attribute_slug, 'pa_' ) ) {
			return substr( $attribute_slug, 3 );
		}
		return $attribute_slug;
	}

	/**
	 * Get all variation attributes for a variation product.
	 *
	 * Returns an array mapping attribute names to their values.
	 *
	 * @param \WC_Product|int $variation Variation product object or ID.
	 * @return array<string,string> Array of attribute name => value pairs.
	 */
	public static function get_all_variation_attributes( $variation ): array {
		if ( is_int( $variation ) ) {
			$variation = wc_get_product( $variation );
		}

		if ( ! $variation instanceof \WC_Product_Variation ) {
			return array();
		}

		$result     = array();
		$attributes = $variation->get_attributes();

		foreach ( $attributes as $attribute_slug => $value ) {
			if ( '' === $value ) {
				continue;
			}

			// Try to get the human-readable name for taxonomy attributes.
			$taxonomy = 'pa_' . self::normalize_attribute_slug( $attribute_slug );
			if ( taxonomy_exists( $taxonomy ) ) {
				$term = get_term_by( 'slug', $value, $taxonomy );
				if ( $term && ! is_wp_error( $term ) ) {
					$value = $term->name;
				}
			}

			$result[ $attribute_slug ] = $value;
		}

		return $result;
	}

	/**
	 * Expand a list of product IDs to include parent and all variation IDs.
	 *
	 * For each variable product in the input list, this method:
	 * - Keeps the parent ID first.
	 * - Adds all variation IDs after the parent.
	 * - Simple products are passed through unchanged.
	 * - Variations without their parent are included with the parent added first.
	 *
	 * @param array<int> $product_ids Array of product IDs (may include variable, simple, or variation IDs).
	 * @return array<int> Expanded array with parent-first ordering for variable products.
	 */
	public static function expand_product_ids_with_variations( array $product_ids ): array {
		$expanded       = array();
		$processed_ids  = array();

		foreach ( $product_ids as $product_id ) {
			// Skip if already processed (prevents duplicates).
			if ( isset( $processed_ids[ $product_id ] ) ) {
				continue;
			}

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

			if ( self::is_variable_product( $product ) ) {
				// Variable product: add parent first, then all variations.
				$expanded[]                    = $product_id;
				$processed_ids[ $product_id ] = true;

				$variation_ids = self::get_variation_ids( $product );
				foreach ( $variation_ids as $variation_id ) {
					if ( ! isset( $processed_ids[ $variation_id ] ) ) {
						$expanded[]                      = $variation_id;
						$processed_ids[ $variation_id ] = true;
					}
				}
			} elseif ( self::is_variation( $product ) ) {
				// Variation: ensure parent is included first.
				$parent_id = self::get_parent_id( $product );
				if ( $parent_id && ! isset( $processed_ids[ $parent_id ] ) ) {
					// Add parent and all its variations for consistency.
					$parent = wc_get_product( $parent_id );
					if ( $parent && self::is_variable_product( $parent ) ) {
						$expanded[]                   = $parent_id;
						$processed_ids[ $parent_id ] = true;

						$variation_ids = self::get_variation_ids( $parent );
						foreach ( $variation_ids as $variation_id ) {
							if ( ! isset( $processed_ids[ $variation_id ] ) ) {
								$expanded[]                      = $variation_id;
								$processed_ids[ $variation_id ] = true;
							}
						}
					}
				} elseif ( ! isset( $processed_ids[ $product_id ] ) ) {
					// Parent already processed, just skip this variation (it's included).
					$processed_ids[ $product_id ] = true;
				}
			} else {
				// Simple product or other type: add directly.
				$expanded[]                    = $product_id;
				$processed_ids[ $product_id ] = true;
			}
		}

		return $expanded;
	}

	/**
	 * Determine the parentage level for a product.
	 *
	 * @param \WC_Product|int $product WooCommerce product object or ID.
	 * @return string 'parent' for variable products, 'child' for variations, empty string for simple/other.
	 */
	public static function get_parentage_level( $product ): string {
		if ( is_int( $product ) ) {
			$product = wc_get_product( $product );
		}

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

		if ( self::is_variable_product( $product ) ) {
			return 'parent';
		}

		if ( self::is_variation( $product ) ) {
			return 'child';
		}

		return '';
	}

	/**
	 * Get count of visible variations for a variable product.
	 *
	 * @param \WC_Product|int $product Variable product object or ID.
	 * @return int Number of visible variations.
	 */
	public static function get_variation_count( $product ): int {
		$variation_ids = self::get_variation_ids( $product );
		return count( $variation_ids );
	}

	/**
	 * Check if any variation of a variable product has a specific attribute.
	 *
	 * @param \WC_Product|int $parent_product Variable product object or ID.
	 * @param string          $attribute_slug Attribute slug to check.
	 * @return bool True if at least one variation has the attribute.
	 */
	public static function has_variation_attribute( $parent_product, string $attribute_slug ): bool {
		$variation_ids = self::get_variation_ids( $parent_product );

		foreach ( $variation_ids as $variation_id ) {
			$value = self::get_variation_attribute_value( $variation_id, $attribute_slug );
			if ( null !== $value && '' !== $value ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Get all unique values for a specific attribute across all variations.
	 *
	 * @param \WC_Product|int $parent_product Variable product object or ID.
	 * @param string          $attribute_slug Attribute slug to get values for.
	 * @return array<string> Array of unique attribute values.
	 */
	public static function get_all_variation_attribute_values( $parent_product, string $attribute_slug ): array {
		$variation_ids = self::get_variation_ids( $parent_product );
		$values        = array();

		foreach ( $variation_ids as $variation_id ) {
			$value = self::get_variation_attribute_value( $variation_id, $attribute_slug );
			if ( null !== $value && '' !== $value && ! in_array( $value, $values, true ) ) {
				$values[] = $value;
			}
		}

		return $values;
	}
}
