<?php

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

/**
 * Server-side WooCommerce cart operations for Niwa frontend agent tools.
 */
class Niwa_AI_Cart_Tool_Service {

	/**
	 * Add a simple product or a resolved variation to the WooCommerce cart.
	 *
	 * @param array $args Tool/AJAX arguments.
	 * @param array $context Request context for attribution.
	 * @return array
	 */
	public function add_product( $args = array(), $context = array() ) {
		if ( ! class_exists( 'WooCommerce' ) || ! function_exists( 'WC' ) || ! function_exists( 'wc_get_product' ) ) {
			return $this->error( 'WooCommerce is not active.', 'woocommerce_unavailable' );
		}

		if ( function_exists( 'wc_load_cart' ) ) {
			wc_load_cart();
		}

		if ( ! WC()->cart ) {
			return $this->error( 'Cart is not available.', 'cart_unavailable' );
		}

		$args     = is_array( $args ) ? $args : array();
		$context  = is_array( $context ) ? $context : array();
		$quantity = isset( $args['quantity'] ) ? absint( $args['quantity'] ) : ( isset( $args['qty'] ) ? absint( $args['qty'] ) : 1 );
		$quantity = max( 1, $quantity );

		$resolved = $this->resolve_product( $args );
		if ( empty( $resolved['success'] ) ) {
			return $resolved;
		}

		$product      = $resolved['product'];
		$product_id   = intval( $product->get_id() );
		$variation_id = isset( $resolved['variation_id'] ) ? intval( $resolved['variation_id'] ) : 0;
		$variation    = array();
		$add_product  = $product;

		if ( method_exists( $product, 'is_type' ) && $product->is_type( 'variation' ) ) {
			$variation_id = $product_id;
			$product_id   = intval( $product->get_parent_id() );
			$add_product  = $product;
			$product      = wc_get_product( $product_id );
			if ( ! $product ) {
				return $this->error( 'Variation parent product was not found.', 'product_not_found' );
			}
			$variation = $this->get_variation_attributes_for_cart( $variation_id );
			foreach ( $this->extract_raw_variation_attributes( $args ) as $key => $value ) {
				if ( 0 === strpos( (string) $key, 'attribute_' ) && '' !== (string) $value ) {
					$variation[ (string) $key ] = (string) $value;
				}
			}
		} elseif ( method_exists( $product, 'is_type' ) && $product->is_type( 'variable' ) ) {
			$variation_resolution = $this->resolve_variation( $product, $args );
			if ( empty( $variation_resolution['success'] ) ) {
				return $this->append_pending_product_context( $variation_resolution, $product, $args );
			}
			$variation_id = intval( $variation_resolution['variation_id'] );
			$variation    = isset( $variation_resolution['variation'] ) && is_array( $variation_resolution['variation'] )
				? $variation_resolution['variation']
				: array();
			$add_product  = wc_get_product( $variation_id );
			if ( ! $add_product ) {
				return $this->error( 'Selected variation was not found.', 'variation_not_found' );
			}
		}

		$availability = $this->validate_purchasable( $add_product, $quantity );
		if ( empty( $availability['success'] ) ) {
			return $availability;
		}

		$cart_item_data = apply_filters( 'woocommerce_add_cart_item_data', array(), $product_id, $variation_id, $quantity );
		$cart_item_data = $this->append_niwa_attribution( $cart_item_data, $context );

		$added = WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variation, $cart_item_data );
		if ( ! $added ) {
			return $this->error( 'Could not add product to cart.', 'add_failed' );
		}

		do_action( 'woocommerce_ajax_added_to_cart', $product_id );

		$fragments = $this->build_cart_fragments_payload();
		$product_name = method_exists( $add_product, 'get_name' ) ? (string) $add_product->get_name() : '';
		if ( '' === $product_name && method_exists( $product, 'get_name' ) ) {
			$product_name = (string) $product->get_name();
		}

		return array(
			'success'      => true,
			'status'       => 'added',
			'message'      => 'Product added to cart.',
			'product_id'   => $product_id,
			'variation_id' => $variation_id,
			'quantity'     => $quantity,
			'product_name' => $product_name,
			'variation'    => $this->format_variation_for_response( $variation ),
			'cart_count'   => WC()->cart->get_cart_contents_count(),
			'cart_hash'    => isset( $fragments['cart_hash'] ) ? $fragments['cart_hash'] : '',
			'fragments'    => isset( $fragments['fragments'] ) ? $fragments['fragments'] : array(),
		);
	}

	private function resolve_product( $args ) {
		$variation_id = isset( $args['variation_id'] ) ? absint( $args['variation_id'] ) : 0;
		if ( $variation_id > 0 ) {
			$variation = wc_get_product( $variation_id );
			if ( ! $variation || ! method_exists( $variation, 'is_type' ) || ! $variation->is_type( 'variation' ) ) {
				return $this->error( 'Variation not found.', 'variation_not_found' );
			}

			$product_id = isset( $args['product_id'] ) ? absint( $args['product_id'] ) : 0;
			if ( $product_id > 0 && intval( $variation->get_parent_id() ) !== $product_id ) {
				return $this->error( 'Variation does not belong to the selected product.', 'variation_parent_mismatch' );
			}

			return array(
				'success'      => true,
				'product'      => $variation,
				'variation_id' => $variation_id,
			);
		}

		$product_id = isset( $args['product_id'] ) ? absint( $args['product_id'] ) : 0;
		if ( $product_id > 0 ) {
			$product = wc_get_product( $product_id );
			if ( ! $product ) {
				return $this->error( 'Product not found.', 'product_not_found' );
			}

			$pack_resolution = $this->resolve_requested_pack_product( $product, $args );
			if ( empty( $pack_resolution['success'] ) ) {
				return $pack_resolution;
			}

			return array(
				'success' => true,
				'product' => $pack_resolution['product'],
			);
		}

		$sku = isset( $args['sku'] ) ? sanitize_text_field( (string) $args['sku'] ) : '';
		if ( '' !== $sku && function_exists( 'wc_get_product_id_by_sku' ) ) {
			$sku_id = intval( wc_get_product_id_by_sku( $sku ) );
			if ( $sku_id > 0 ) {
				$product = wc_get_product( $sku_id );
				if ( $product ) {
					$pack_resolution = $this->resolve_requested_pack_product( $product, $args );
					if ( empty( $pack_resolution['success'] ) ) {
						return $pack_resolution;
					}

					return array(
						'success' => true,
						'product' => $pack_resolution['product'],
					);
				}
			}
		}

		$query = isset( $args['product_query'] ) ? trim( sanitize_text_field( (string) $args['product_query'] ) ) : '';
		if ( '' === $query ) {
			return $this->error( 'Product is required.', 'product_required' );
		}

		if ( ctype_digit( $query ) ) {
			$product = wc_get_product( intval( $query ) );
			if ( $product ) {
				$pack_resolution = $this->resolve_requested_pack_product( $product, $args );
				if ( empty( $pack_resolution['success'] ) ) {
					return $pack_resolution;
				}

				return array(
					'success' => true,
					'product' => $pack_resolution['product'],
				);
			}
		}

		if ( ! function_exists( 'wc_get_products' ) ) {
			return $this->error( 'Product search is not available.', 'product_search_unavailable' );
		}

		$products = wc_get_products(
			array(
				'status' => 'publish',
				'limit'  => 8,
				'search' => $query,
			)
		);

		if ( empty( $products ) ) {
			return $this->error( 'I could not find that product.', 'product_not_found' );
		}

		$query_norm = $this->normalize_text( $query );
		foreach ( $products as $candidate ) {
			$name_norm = $this->normalize_text( method_exists( $candidate, 'get_name' ) ? $candidate->get_name() : '' );
			if ( $name_norm === $query_norm ) {
				$pack_resolution = $this->resolve_requested_pack_product( $candidate, $args );
				if ( empty( $pack_resolution['success'] ) ) {
					return $pack_resolution;
				}

				return array(
					'success' => true,
					'product' => $pack_resolution['product'],
				);
			}
		}

		if ( count( $products ) > 1 ) {
			$matches = array();
			foreach ( $products as $candidate ) {
				$name = method_exists( $candidate, 'get_name' ) ? (string) $candidate->get_name() : '';
				if ( '' !== $name ) {
					$matches[] = array(
						'product_id' => intval( $candidate->get_id() ),
						'name'       => $name,
					);
				}
			}

			return array(
				'success' => false,
				'status'  => 'needs_selection',
				'code'    => 'ambiguous_product',
				'message' => 'I found more than one matching product. Ask the customer which one they want.',
				'matches' => array_slice( $matches, 0, 5 ),
			);
		}

		$pack_resolution = $this->resolve_requested_pack_product( $products[0], $args );
		if ( empty( $pack_resolution['success'] ) ) {
			return $pack_resolution;
		}

		return array(
			'success' => true,
			'product' => $pack_resolution['product'],
		);
	}

	private function resolve_variation( $product, $args ) {
		$definitions = $this->build_variation_definitions( $product );
		if ( empty( $definitions ) ) {
			return $this->error( 'This variable product has no available options.', 'variation_options_unavailable' );
		}

		$raw_attributes = $this->extract_raw_variation_attributes( $args );
		$normalized     = $this->normalize_selected_attributes( $definitions, $raw_attributes );
		if ( ! empty( $normalized['unknown'] ) ) {
			return array(
				'success'          => false,
				'status'           => 'needs_selection',
				'code'             => 'invalid_variation_attribute',
				'message'          => 'The selected option is not available. Ask the customer to choose from the listed options.',
				'unknown'          => $normalized['unknown'],
				'required_options' => $this->format_required_options( $definitions ),
			);
		}

		$selected = isset( $normalized['attributes'] ) ? $normalized['attributes'] : array();
		$missing  = array();
		foreach ( $definitions as $definition ) {
			$key = $definition['param_key'];
			if ( isset( $selected[ $key ] ) && '' !== (string) $selected[ $key ] ) {
				continue;
			}

			if ( 1 === count( $definition['options'] ) ) {
				$selected[ $key ] = (string) $definition['options'][0]['value'];
				continue;
			}

			$missing[] = $definition;
		}

		if ( ! empty( $missing ) ) {
			return array(
				'success'          => false,
				'status'           => 'needs_selection',
				'code'             => 'missing_variation_attributes',
				'message'          => 'This product has options. Ask the customer for the missing choices.',
				'selected'         => $this->format_variation_for_response( $selected ),
				'required_options' => $this->format_required_options( $missing ),
				'all_options'      => $this->format_required_options( $definitions ),
			);
		}

		$variation_id = $this->find_matching_variation_id( $product, $selected );
		if ( $variation_id <= 0 ) {
			return array(
				'success'              => false,
				'status'               => 'needs_selection',
				'code'                 => 'variation_not_available',
				'message'              => 'That exact combination is not available. Ask the customer to choose another option.',
				'selected'             => $this->format_variation_for_response( $selected ),
				'required_options'     => $this->format_required_options( $definitions ),
				'available_variations' => $this->get_available_variations_summary( $product, 20 ),
			);
		}

		return array(
			'success'      => true,
			'variation_id' => $variation_id,
			'variation'    => $selected,
		);
	}

	private function append_pending_product_context( $result, $product, $args ) {
		$result = is_array( $result ) ? $result : array();
		if ( 'needs_selection' !== ( isset( $result['status'] ) ? (string) $result['status'] : '' ) ) {
			return $result;
		}

		if ( empty( $result['product_id'] ) && $product && method_exists( $product, 'get_id' ) ) {
			$result['product_id'] = intval( $product->get_id() );
		}
		if ( empty( $result['product_name'] ) && $product && method_exists( $product, 'get_name' ) ) {
			$result['product_name'] = (string) $product->get_name();
		}
		if ( empty( $result['product_query'] ) && isset( $args['product_query'] ) && is_scalar( $args['product_query'] ) ) {
			$result['product_query'] = (string) $args['product_query'];
		}

		return $result;
	}

	private function extract_raw_variation_attributes( $args ) {
		$out = array();

		if ( isset( $args['variation_attributes'] ) && is_array( $args['variation_attributes'] ) ) {
			foreach ( $args['variation_attributes'] as $key => $value ) {
				$out[ (string) $key ] = (string) $value;
			}
		}

		foreach ( $args as $key => $value ) {
			if ( is_array( $value ) || is_object( $value ) ) {
				continue;
			}
			$key_string = (string) $key;
			if ( in_array( $key_string, array( 'product_id', 'variation_id', 'product_query', 'quantity', 'qty', 'sku', '_user_query' ), true ) ) {
				continue;
			}
			if ( 0 === strpos( $key_string, 'attribute_' ) || in_array( $this->normalize_text( $key_string ), array( 'size', 'sizes', 'broj', 'velicina', 'shoe_size', 'color', 'colour', 'boja' ), true ) ) {
				$out[ $key_string ] = (string) $value;
			}
		}

		return $out;
	}

	private function resolve_requested_pack_product( $product, $args ) {
		$requested_grams = $this->extract_requested_grams( $this->get_product_intent_text( $args ), $product );
		if ( $requested_grams <= 0 || $this->product_name_matches_grams( $product, $requested_grams ) ) {
			return array(
				'success' => true,
				'product' => $product,
			);
		}

		if ( ! $this->product_name_has_gram_pack( $product ) ) {
			return array(
				'success' => true,
				'product' => $product,
			);
		}

		$replacement = $this->find_product_by_pack_hint( $product, $requested_grams, $args );
		if ( $replacement ) {
			return array(
				'success' => true,
				'product' => $replacement,
			);
		}

		return array(
			'success'          => false,
			'status'           => 'needs_selection',
			'code'             => 'product_pack_mismatch',
			'message'          => 'The selected product does not match the requested pack size. Do not add the selected product; search for the requested pack or ask the customer to confirm.',
			'requested_pack'   => $requested_grams . 'g',
			'selected_product' => array(
				'product_id' => method_exists( $product, 'get_id' ) ? intval( $product->get_id() ) : 0,
				'name'       => method_exists( $product, 'get_name' ) ? (string) $product->get_name() : '',
			),
		);
	}

	private function get_product_intent_text( $args ) {
		$parts = array();
		foreach ( array( 'product_query', '_user_query' ) as $key ) {
			if ( isset( $args[ $key ] ) && is_scalar( $args[ $key ] ) ) {
				$value = trim( (string) $args[ $key ] );
				if ( '' !== $value ) {
					$parts[] = $value;
				}
			}
		}
		return implode( ' ', $parts );
	}

	private function extract_requested_grams( $text, $reference_product = null ) {
		$text = (string) $text;
		if ( preg_match( '/(?<!\d)(\d{1,4})\s*(?:g|gr|grama|gram|grams)\b/iu', $text, $m ) ) {
			return intval( $m[1] );
		}

		if ( $reference_product && $this->product_name_has_gram_pack( $reference_product ) && preg_match( '/\bod\s+(\d{1,4})\b/iu', $text, $m ) ) {
			return intval( $m[1] );
		}

		return 0;
	}

	private function product_name_has_gram_pack( $product ) {
		$name = method_exists( $product, 'get_name' ) ? (string) $product->get_name() : '';
		return (bool) preg_match( '/(?<!\d)\d{1,4}\s*(?:g|gr|grama|gram|grams)\b/iu', $name );
	}

	private function product_name_matches_grams( $product, $grams ) {
		$name  = method_exists( $product, 'get_name' ) ? (string) $product->get_name() : '';
		$grams = intval( $grams );
		if ( $grams <= 0 || '' === $name ) {
			return false;
		}

		$pattern = '/(?<!\d)' . preg_quote( (string) $grams, '/' ) . '\s*(?:g|gr|grama|gram|grams)\b/iu';
		return (bool) preg_match( $pattern, $name );
	}

	private function find_product_by_pack_hint( $base_product, $requested_grams, $args ) {
		if ( ! function_exists( 'wc_get_products' ) ) {
			return null;
		}

		$queries   = array();
		$base_name = method_exists( $base_product, 'get_name' ) ? (string) $base_product->get_name() : '';
		$replaced  = $this->replace_gram_pack_in_text( $base_name, $requested_grams );
		if ( '' !== $replaced && $replaced !== $base_name ) {
			$queries[] = $this->clean_pack_search_text( $replaced );
		}

		foreach ( array( 'product_query', '_user_query' ) as $key ) {
			if ( isset( $args[ $key ] ) && is_scalar( $args[ $key ] ) ) {
				$query = $this->replace_gram_pack_in_text( (string) $args[ $key ], $requested_grams );
				$query = $this->clean_pack_search_text( $query );
				if ( '' !== $query ) {
					$queries[] = $query;
				}
			}
		}

		$base_without_pack = $this->clean_pack_search_text( preg_replace( '/(?<!\d)\d{1,4}\s*(?:g|gr|grama|gram|grams)\b/iu', ' ', $base_name ) );
		if ( '' !== $base_without_pack ) {
			$queries[] = trim( $base_without_pack . ' ' . $requested_grams . 'g' );
			$queries[] = $base_without_pack;
		}

		$queries    = array_values( array_unique( array_filter( $queries ) ) );
		$candidates = array();
		foreach ( array_slice( $queries, 0, 5 ) as $query ) {
			$products = wc_get_products(
				array(
					'status' => 'publish',
					'limit'  => 12,
					'search' => $query,
				)
			);
			foreach ( (array) $products as $candidate ) {
				if ( ! $candidate || ! method_exists( $candidate, 'get_id' ) ) {
					continue;
				}
				$candidates[ intval( $candidate->get_id() ) ] = $candidate;
			}
		}

		$best       = null;
		$best_score = 0;
		foreach ( $candidates as $candidate ) {
			$score = $this->score_pack_candidate( $base_product, $candidate, $requested_grams );
			if ( $score > $best_score ) {
				$best       = $candidate;
				$best_score = $score;
			}
		}

		return $best_score > 0 ? $best : null;
	}

	private function replace_gram_pack_in_text( $text, $grams ) {
		$text     = (string) $text;
		$grams    = intval( $grams );
		$replaced = preg_replace( '/(?<!\d)\d{1,4}\s*(?:g|gr|grama|gram|grams)\b/iu', $grams . 'g', $text, 1, $count );
		if ( $count > 0 ) {
			return (string) $replaced;
		}
		return $text;
	}

	private function clean_pack_search_text( $text ) {
		$text = wp_strip_all_tags( (string) $text );
		$text = preg_replace( '/[^\p{L}\p{N}\s]/u', ' ', $text );
		$text = preg_replace( '/\s+/', ' ', $text );
		return trim( $text );
	}

	private function score_pack_candidate( $base_product, $candidate, $requested_grams ) {
		if ( intval( $base_product->get_id() ) === intval( $candidate->get_id() ) ) {
			return 0;
		}
		if ( ! $this->product_name_matches_grams( $candidate, $requested_grams ) ) {
			return 0;
		}

		$base_name      = method_exists( $base_product, 'get_name' ) ? (string) $base_product->get_name() : '';
		$candidate_name = method_exists( $candidate, 'get_name' ) ? (string) $candidate->get_name() : '';
		$base_tokens    = $this->pack_match_tokens( $base_name );
		$candidate_norm = ' ' . $this->normalize_text( $candidate_name ) . ' ';
		$overlap        = 0;
		foreach ( $base_tokens as $token ) {
			if ( false !== strpos( $candidate_norm, ' ' . $token . ' ' ) ) {
				$overlap++;
			}
		}

		$required_overlap = max( 1, min( 2, count( $base_tokens ) ) );
		if ( $overlap < $required_overlap ) {
			return 0;
		}

		return 100 + ( $overlap * 10 );
	}

	private function pack_match_tokens( $text ) {
		$text = preg_replace( '/(?<!\d)\d{1,4}\s*(?:g|gr|grama|gram|grams)\b/iu', ' ', (string) $text );
		$norm = $this->normalize_text( $text );
		$out  = array();
		foreach ( preg_split( '/\s+/', $norm ) as $token ) {
			$token = trim( (string) $token );
			if ( mb_strlen( $token ) >= 3 && ! ctype_digit( $token ) ) {
				$out[] = $token;
			}
		}
		return array_values( array_unique( $out ) );
	}

	private function build_variation_definitions( $product ) {
		if ( ! method_exists( $product, 'get_variation_attributes' ) ) {
			return array();
		}

		$attributes  = (array) $product->get_variation_attributes();
		$definitions = array();

		foreach ( $attributes as $raw_name => $options ) {
			$name      = (string) $raw_name;
			$param_key = $this->attribute_param_key( $name );
			$label     = function_exists( 'wc_attribute_label' ) ? wc_attribute_label( $name, $product ) : $name;
			$opts      = array();

			foreach ( (array) $options as $option ) {
				$value = (string) $option;
				if ( '' === $value ) {
					continue;
				}
				$opts[] = array(
					'value'       => $value,
					'label'       => $this->get_attribute_option_label( $name, $value ),
					'search_keys' => $this->get_attribute_option_search_keys( $name, $value ),
				);
			}

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

			$key_norm = $this->normalize_attribute_key( $name );
			$definitions[] = array(
				'name'        => $name,
				'param_key'   => $param_key,
				'label'       => $label,
				'key_norm'    => $key_norm,
				'search_keys' => array_values(
					array_unique(
						array_filter(
							array(
								$key_norm,
								$this->normalize_attribute_key( $param_key ),
								$this->normalize_attribute_key( $label ),
							)
						)
					)
				),
				'options'     => $opts,
			);
		}

		return $definitions;
	}

	private function normalize_selected_attributes( $definitions, $raw_attributes ) {
		$selected = array();
		$unknown  = array();

		foreach ( (array) $raw_attributes as $raw_key => $raw_value ) {
			$value = trim( (string) $raw_value );
			if ( '' === $value ) {
				continue;
			}

			$definition = $this->match_attribute_definition( $definitions, $raw_key );
			if ( empty( $definition ) ) {
				$unknown[] = array(
					'attribute' => (string) $raw_key,
					'value'     => $value,
				);
				continue;
			}

			$option = $this->match_attribute_option( $definition, $value );
			if ( null === $option ) {
				$unknown[] = array(
					'attribute' => $definition['label'],
					'value'     => $value,
				);
				continue;
			}

			$selected[ $definition['param_key'] ] = $option;
		}

		return array(
			'attributes' => $selected,
			'unknown'    => $unknown,
		);
	}

	private function match_attribute_definition( $definitions, $raw_key ) {
		$key = $this->normalize_attribute_key( $raw_key );
		$aliases = array(
			'size'      => array( 'size', 'sizes', 'broj', 'velicina', 'shoe_size', 'number' ),
			'color'     => array( 'color', 'colour', 'boja' ),
			'colour'    => array( 'color', 'colour', 'boja' ),
			'boja'      => array( 'color', 'colour', 'boja' ),
			'broj'      => array( 'size', 'sizes', 'broj', 'velicina', 'shoe_size', 'number' ),
			'velicina'  => array( 'size', 'sizes', 'broj', 'velicina', 'shoe_size', 'number' ),
			'shoe_size' => array( 'size', 'sizes', 'broj', 'velicina', 'shoe_size', 'number' ),
		);
		$candidates = isset( $aliases[ $key ] ) ? $aliases[ $key ] : array( $key );

		foreach ( $definitions as $definition ) {
			foreach ( $candidates as $candidate ) {
				if ( in_array( $candidate, $definition['search_keys'], true ) ) {
					return $definition;
				}
			}
		}

		foreach ( $definitions as $definition ) {
			foreach ( $definition['search_keys'] as $search_key ) {
				if ( '' !== $key && ( false !== strpos( $search_key, $key ) || false !== strpos( $key, $search_key ) ) ) {
					return $definition;
				}
			}
		}

		return array();
	}

	private function match_attribute_option( $definition, $raw_value ) {
		$value_norm = $this->normalize_text( $raw_value );
		foreach ( $definition['options'] as $option ) {
			foreach ( $option['search_keys'] as $search_key ) {
				if ( $value_norm === $search_key ) {
					return (string) $option['value'];
				}
			}
		}

		foreach ( $definition['options'] as $option ) {
			foreach ( $option['search_keys'] as $search_key ) {
				if ( '' !== $value_norm && ( false !== strpos( $search_key, $value_norm ) || false !== strpos( $value_norm, $search_key ) ) ) {
					return (string) $option['value'];
				}
			}
		}

		return null;
	}

	private function find_matching_variation_id( $product, $selected ) {
		if ( class_exists( 'WC_Data_Store' ) ) {
			try {
				$data_store = WC_Data_Store::load( 'product' );
				if ( $data_store && method_exists( $data_store, 'find_matching_product_variation' ) ) {
					$variation_id = intval( $data_store->find_matching_product_variation( $product, $selected ) );
					if ( $variation_id > 0 ) {
						return $variation_id;
					}
				}
			} catch ( \Throwable $e ) {
				// Fall through to manual matching below.
			}
		}

		if ( ! method_exists( $product, 'get_available_variations' ) ) {
			return 0;
		}

		foreach ( (array) $product->get_available_variations() as $variation ) {
			if ( empty( $variation['variation_id'] ) || empty( $variation['attributes'] ) || ! is_array( $variation['attributes'] ) ) {
				continue;
			}
			$matches = true;
			foreach ( $selected as $key => $value ) {
				$variation_value = isset( $variation['attributes'][ $key ] ) ? (string) $variation['attributes'][ $key ] : '';
				if ( '' !== $variation_value && $variation_value !== (string) $value ) {
					$matches = false;
					break;
				}
			}
			if ( $matches ) {
				return intval( $variation['variation_id'] );
			}
		}

		return 0;
	}

	private function validate_purchasable( $product, $quantity ) {
		if ( ! $product ) {
			return $this->error( 'Product not found.', 'product_not_found' );
		}

		if ( method_exists( $product, 'is_purchasable' ) && ! $product->is_purchasable() ) {
			return $this->error( 'This product cannot be purchased right now.', 'not_purchasable' );
		}

		if ( method_exists( $product, 'is_in_stock' ) && ! $product->is_in_stock() ) {
			return $this->error( 'This product is out of stock.', 'out_of_stock' );
		}

		if ( method_exists( $product, 'has_enough_stock' ) && ! $product->has_enough_stock( $quantity ) ) {
			return $this->error( 'There is not enough stock for the requested quantity.', 'insufficient_stock' );
		}

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

	private function append_niwa_attribution( $cart_item_data, $context ) {
		$cart_item_data = is_array( $cart_item_data ) ? $cart_item_data : array();
		if ( empty( $context['recommended'] ) ) {
			return $cart_item_data;
		}

		if ( class_exists( 'Niwa_AI_Analytics_Dashboard' ) ) {
			$session = isset( $context['session'] ) ? (string) $context['session'] : '';
			if ( '' === $session && class_exists( 'Niwa_AI_Session_Utils' ) ) {
				$session = Niwa_AI_Session_Utils::resolve_user_session();
			}

			$cart_item_data[ Niwa_AI_Analytics_Dashboard::RECOMMENDED_META_KEY ] = 'yes';
			$cart_item_data[ Niwa_AI_Analytics_Dashboard::SESSION_META_KEY ]     = substr( $session, 0, defined( 'Niwa_AI_Constants::SESSION_MAX_LENGTH' ) ? Niwa_AI_Constants::SESSION_MAX_LENGTH : 100 );
			$cart_item_data[ Niwa_AI_Analytics_Dashboard::SOURCE_META_KEY ]      = isset( $context['source'] ) ? sanitize_text_field( (string) $context['source'] ) : 'niwa_agent';
			$cart_item_data[ Niwa_AI_Analytics_Dashboard::PAGE_URL_META_KEY ]    = isset( $context['page_url'] ) ? esc_url_raw( (string) $context['page_url'] ) : home_url( '/' );
			$cart_item_data[ Niwa_AI_Analytics_Dashboard::CONTEXT_META_KEY ]     = isset( $context['cart_context'] ) ? sanitize_text_field( (string) $context['cart_context'] ) : 'agent_cart';
		}

		return $cart_item_data;
	}

	public function build_cart_fragments_payload() {
		if ( ! class_exists( 'WooCommerce' ) || ! function_exists( 'WC' ) || ! WC()->cart ) {
			return array(
				'fragments' => array(),
				'cart_hash' => '',
			);
		}

		ob_start();
		if ( function_exists( 'woocommerce_mini_cart' ) ) {
			woocommerce_mini_cart();
		}
		$mini_cart = ob_get_clean();

		$fragments = apply_filters(
			'woocommerce_add_to_cart_fragments',
			array(
				'div.widget_shopping_cart_content' => '<div class="widget_shopping_cart_content">' . $mini_cart . '</div>',
			)
		);

		return array(
			'fragments' => is_array( $fragments ) ? $fragments : array(),
			'cart_hash' => WC()->cart->get_cart_hash(),
		);
	}

	private function format_required_options( $definitions ) {
		$out = array();
		foreach ( (array) $definitions as $definition ) {
			$options = array();
			foreach ( (array) $definition['options'] as $option ) {
				$options[] = array(
					'value' => (string) $option['value'],
					'label' => (string) $option['label'],
				);
			}
			$out[] = array(
				'attribute' => (string) $definition['param_key'],
				'label'     => (string) $definition['label'],
				'options'   => $options,
			);
		}
		return $out;
	}

	private function get_available_variations_summary( $product, $limit = 20 ) {
		if ( ! method_exists( $product, 'get_available_variations' ) ) {
			return array();
		}

		$out = array();
		foreach ( (array) $product->get_available_variations() as $variation ) {
			if ( empty( $variation['variation_id'] ) ) {
				continue;
			}
			$out[] = array(
				'variation_id' => intval( $variation['variation_id'] ),
				'attributes'   => isset( $variation['attributes'] ) && is_array( $variation['attributes'] )
					? $this->format_variation_for_response( $variation['attributes'] )
					: array(),
				'in_stock'     => ! empty( $variation['is_in_stock'] ),
				'purchasable'  => ! empty( $variation['is_purchasable'] ),
			);
			if ( count( $out ) >= max( 1, intval( $limit ) ) ) {
				break;
			}
		}
		return $out;
	}

	private function format_variation_for_response( $variation ) {
		$out = array();
		foreach ( (array) $variation as $key => $value ) {
			$clean_key = preg_replace( '/^attribute_/', '', (string) $key );
			$out[ $clean_key ] = (string) $value;
		}
		return $out;
	}

	private function get_variation_attributes_for_cart( $variation_id ) {
		$variation = wc_get_product( $variation_id );
		if ( ! $variation || ! method_exists( $variation, 'get_variation_attributes' ) ) {
			return array();
		}
		return (array) $variation->get_variation_attributes();
	}

	private function attribute_param_key( $name ) {
		$name = (string) $name;
		if ( 0 === strpos( $name, 'attribute_' ) ) {
			return $name;
		}
		return 'attribute_' . sanitize_title( $name );
	}

	private function get_attribute_option_label( $attribute_name, $value ) {
		$attribute_name = (string) $attribute_name;
		$value          = (string) $value;
		if ( taxonomy_exists( $attribute_name ) ) {
			$term = get_term_by( 'slug', $value, $attribute_name );
			if ( $term && ! is_wp_error( $term ) && ! empty( $term->name ) ) {
				return (string) $term->name;
			}
		}
		return $value;
	}

	private function get_attribute_option_search_keys( $attribute_name, $value ) {
		$label = $this->get_attribute_option_label( $attribute_name, $value );
		return array_values(
			array_unique(
				array_filter(
					array(
						$this->normalize_text( $value ),
						$this->normalize_text( $label ),
					)
				)
			)
		);
	}

	private function normalize_attribute_key( $value ) {
		$value = preg_replace( '/^attribute_/', '', (string) $value );
		$value = preg_replace( '/^pa_/', '', $value );
		return $this->normalize_text( $value );
	}

	private function normalize_text( $value ) {
		$value = strtolower( trim( (string) $value ) );
		if ( function_exists( 'remove_accents' ) ) {
			$value = remove_accents( $value );
		}
		$value = str_replace( array( '-', '_' ), ' ', $value );
		$value = preg_replace( '/[^\p{L}\p{N}\s]/u', ' ', $value );
		$value = preg_replace( '/\s+/', ' ', $value );
		return trim( $value );
	}

	private function error( $message, $code ) {
		return array(
			'success' => false,
			'status'  => 'error',
			'code'    => (string) $code,
			'message' => (string) $message,
		);
	}
}
