<?php
/**
 * Structured partner product extraction for WooCommerce import workflows.
 *
 * @package Niwa_AI
 */

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

if ( ! class_exists( 'Niwa_AI_Admin_Partner_Product_Image_Context' ) ) {
	require_once __DIR__ . '/class-admin-partner-product-image-context.php';
}
if ( ! class_exists( 'Niwa_AI_Admin_Partner_Product_Static_Variant_Media_Parser' ) ) {
	require_once __DIR__ . '/class-admin-partner-product-static-variant-media-parser.php';
}
if ( ! class_exists( 'Niwa_AI_Admin_Partner_Product_Shopify_Endpoint_Parser' ) ) {
	require_once __DIR__ . '/class-admin-partner-product-shopify-endpoint-parser.php';
}
if ( ! class_exists( 'Niwa_AI_Admin_Partner_Product_Variant_Media' ) ) {
	require_once __DIR__ . '/class-admin-partner-product-variant-media.php';
}

class Niwa_AI_Admin_Partner_Product_Extractor {
	private $max_html_bytes;
	private $image_context;
	private $static_variant_parser;
	private $shopify_endpoint_parser;
	private $variant_media;

	public function __construct( $max_html_bytes = 2500000, $image_context = null, $static_variant_parser = null, $variant_media = null, $shopify_endpoint_parser = null ) {
		$this->max_html_bytes = max( 500000, intval( $max_html_bytes ) );
		$this->image_context  = $image_context instanceof Niwa_AI_Admin_Partner_Product_Image_Context ? $image_context : new Niwa_AI_Admin_Partner_Product_Image_Context();
		$this->static_variant_parser = $static_variant_parser instanceof Niwa_AI_Admin_Partner_Product_Static_Variant_Media_Parser ? $static_variant_parser : new Niwa_AI_Admin_Partner_Product_Static_Variant_Media_Parser( $this->image_context );
		$this->shopify_endpoint_parser = $shopify_endpoint_parser instanceof Niwa_AI_Admin_Partner_Product_Shopify_Endpoint_Parser ? $shopify_endpoint_parser : new Niwa_AI_Admin_Partner_Product_Shopify_Endpoint_Parser( $this->static_variant_parser );
		$this->variant_media = $variant_media instanceof Niwa_AI_Admin_Partner_Product_Variant_Media ? $variant_media : new Niwa_AI_Admin_Partner_Product_Variant_Media( $this->image_context );
	}

	public function extract_from_url( $url, $args = array() ) {
		$url        = trim( (string) $url );
		$args       = is_array( $args ) ? $args : array();
		$max_images = isset( $args['max_images'] ) ? max( 1, min( 60, intval( $args['max_images'] ) ) ) : 30;

		if ( ! $this->is_safe_fetch_url( $url ) ) {
			return array( 'success' => false, 'error' => 'Partner product URL mora biti validan http/https URL.' );
		}
		$url = esc_url_raw( $url );

		$response = wp_safe_remote_get(
			$url,
			array(
				'timeout'             => 25,
				'redirection'         => 5,
				'limit_response_size' => $this->max_html_bytes + 1,
				'user-agent'          => 'Mozilla/5.0 (compatible; NiwaAIProductExtractor/' . ( defined( 'NIWA_AI_VERSION' ) ? NIWA_AI_VERSION : 'dev' ) . ')',
				'headers'             => array(
					'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
				),
			)
		);

		if ( is_wp_error( $response ) ) {
			return array( 'success' => false, 'error' => 'Ne mogu da otvorim URL: ' . $response->get_error_message() );
		}

		$status = intval( wp_remote_retrieve_response_code( $response ) );
		$html   = (string) wp_remote_retrieve_body( $response );
		if ( $status < 200 || $status >= 300 || '' === trim( $html ) ) {
			return array(
				'success' => false,
				'error'   => 'Partner stranica nije vratila citljiv HTML.',
				'status'  => $status,
			);
		}

		if ( strlen( $html ) > $this->max_html_bytes ) {
			return array( 'success' => false, 'error' => 'Partner stranica je veca od dozvoljenog response limita.' );
		}

		$result = $this->extract_from_html( $html, $url, array( 'max_images' => $max_images, 'http_status' => $status ) );
		return $this->maybe_merge_shopify_endpoint_variants( $result, $url, $max_images );
	}

	private function is_safe_fetch_url( $url ) {
		$parts = wp_parse_url( trim( (string) $url ) );
		if (
			! is_array( $parts )
			|| ! in_array( strtolower( (string) ( $parts['scheme'] ?? '' ) ), array( 'http', 'https' ), true )
			|| empty( $parts['host'] )
			|| ! empty( $parts['user'] )
			|| ! empty( $parts['pass'] )
		) {
			return false;
		}

		return (bool) wp_http_validate_url( (string) $url );
	}

	private function maybe_merge_shopify_endpoint_variants( $result, $url, $max_images ) {
		if ( empty( $result['success'] ) || empty( $result['product'] ) || ! is_array( $result['product'] ) ) {
			return $result;
		}
		if ( ! empty( $result['product']['source_variations'] ) ) {
			return $result;
		}

		$endpoint = $this->shopify_endpoint_parser->extract_from_url( $url );
		if ( empty( $endpoint['source_variations'] ) ) {
			return $result;
		}

		$product = $result['product'];
		if ( '' === trim( (string) $product['title'] ) && ! empty( $endpoint['title'] ) ) {
			$product['title'] = (string) $endpoint['title'];
		}
		if ( '' === trim( (string) $product['sku_hint'] ) && ! empty( $endpoint['sku_hint'] ) ) {
			$product['sku_hint'] = (string) $endpoint['sku_hint'];
		}

		$merged_images = array();
		foreach ( isset( $product['images'] ) && is_array( $product['images'] ) ? $product['images'] : array() as $image ) {
			if ( ! empty( $image['url'] ) ) {
				$merged_images[] = $image;
			}
		}
		foreach ( isset( $endpoint['images'] ) && is_array( $endpoint['images'] ) ? $endpoint['images'] : array() as $image ) {
			if ( ! empty( $image['url'] ) ) {
				$merged_images[] = $image;
			}
		}

		$product['source_variations'] = array_values( (array) $endpoint['source_variations'] );
		$normalized = $this->variant_media->normalize_product_data(
			array(
				'title'             => isset( $product['title'] ) ? (string) $product['title'] : '',
				'images'            => $merged_images,
				'source_variations' => $product['source_variations'],
			)
		);
		$product['variant_media'] = isset( $normalized['variant_media'] ) ? $normalized['variant_media'] : array();
		$product['images']        = array_slice( isset( $normalized['images'] ) ? $normalized['images'] : array(), 0, max( 1, intval( $max_images ) ) );

		if ( empty( $product['price_hint']['amount'] ) ) {
			$price = $this->first_variation_value( $product['source_variations'], 'price' );
			if ( '' !== $price ) {
				$product['price_hint'] = array( 'amount' => $price, 'currency' => '' );
			}
		}
		$product['completeness']['has_variations']       = true;
		$product['completeness']['has_variation_images'] = $this->variations_have_images( $product['source_variations'] );
		$product['completeness']['has_gallery']          = count( $product['images'] ) > 1;
		$result['product']                               = $product;
		$result['confidence']                            = min( 100, intval( $result['confidence'] ) + 20 );
		$result['warnings'][]                            = 'Variation data was completed from Shopify product JSON endpoint: ' . ( isset( $endpoint['endpoint_url'] ) ? $endpoint['endpoint_url'] : '' );
		return $result;
	}

	public function extract_from_html( $html, $source_url = '', $args = array() ) {
		$args       = is_array( $args ) ? $args : array();
		$max_images = isset( $args['max_images'] ) ? max( 1, min( 60, intval( $args['max_images'] ) ) ) : 30;
		$source_url = esc_url_raw( (string) $source_url );
		$html       = (string) $html;

		$dom   = $this->load_dom( $html );
		$xpath = $dom ? new DOMXPath( $dom ) : null;
		$meta  = $xpath ? $this->extract_meta( $xpath ) : array();
		$json  = $xpath ? $this->extract_jsonld_nodes( $xpath ) : array();
		$ld    = $this->pick_product_ld( $json );
		$base  = $this->base_url( $source_url, $xpath );

		$woo_variations = $this->extract_woo_variations( $html );
		$static_variant_source = $this->static_variant_parser->extract_from_html( $html, $base );
		if ( empty( $woo_variations ) && ! empty( $static_variant_source['source_variations'] ) ) {
			$woo_variations = $static_variant_source['source_variations'];
		}
		$variation_attribute_options = $this->extract_variation_attribute_options( $xpath );
		$title          = $this->clean_text(
			$this->first_non_empty(
				$this->ld_value( $ld, 'name' ),
				isset( $meta['og:title'] ) ? $meta['og:title'] : '',
				$xpath ? $this->first_xpath_text( $xpath, array(
					'//*[contains(concat(" ", normalize-space(@class), " "), " product_title ")]',
					'//*[@itemprop="name"]',
					'//h1',
				) ) : ''
			),
			180
		);
		$description    = $this->clean_text(
			$this->first_non_empty(
				$this->ld_value( $ld, 'description' ),
				isset( $meta['og:description'] ) ? $meta['og:description'] : '',
				isset( $meta['description'] ) ? $meta['description'] : '',
				$xpath ? $this->first_xpath_text( $xpath, array(
					'//*[@itemprop="description"]',
					'//*[contains(@class, "woocommerce-product-details__short-description")]',
					'//*[contains(@id, "tab-description")]',
				), 3500 ) : ''
			),
			5000
		);
		$breadcrumbs    = $this->extract_breadcrumbs( $json, $xpath );
		$attributes     = $this->extract_attributes( $ld, $xpath );
		$variants       = $this->build_variant_summary( $woo_variations, $ld, $xpath, $variation_attribute_options );
		$base_product_data = $this->variant_media->normalize_product_data(
			array(
				'title'             => $title,
				'images'            => ! empty( $static_variant_source['images'] ) ? $static_variant_source['images'] : array(),
				'source_variations' => $woo_variations,
			)
		);
		$images         = $this->extract_images( $ld, $meta, $xpath, $base, $title, $max_images, $woo_variations, $base_product_data['variant_media'] );
		$normalized_product_media = $this->variant_media->normalize_product_data(
			array(
				'title'             => $title,
				'images'            => $images,
				'source_variations' => $woo_variations,
			)
		);
		$images                = array_slice( isset( $normalized_product_media['images'] ) ? $normalized_product_media['images'] : $images, 0, $max_images );
		$normalized_variant_media = isset( $normalized_product_media['variant_media'] ) ? $normalized_product_media['variant_media'] : array();
		$price          = $this->extract_price( $ld, $meta, $xpath, $woo_variations );
		$sku            = $this->first_non_empty(
			$this->ld_value( $ld, 'sku' ),
			$this->ld_value( $ld, 'mpn' ),
			$this->first_variation_value( $woo_variations, 'sku' )
		);

		$confidence = 0;
		$confidence += '' !== $title ? 25 : 0;
		$confidence += '' !== $description ? 15 : 0;
		$confidence += ! empty( $images ) ? 20 : 0;
		$confidence += ! empty( $attributes ) ? 15 : 0;
		$confidence += ! empty( $woo_variations ) ? 25 : 0;

		return array(
			'success'           => true,
			'source_url'        => $source_url,
			'resolved_base_url' => $base,
			'http_status'       => isset( $args['http_status'] ) ? intval( $args['http_status'] ) : 0,
			'confidence'        => min( 100, $confidence ),
			'warnings'          => $this->build_warnings( $title, $images, $woo_variations ),
			'product'           => array(
				'title'                 => $title,
				'description_raw'       => $description,
				'short_description_raw' => $this->first_sentence( $description ),
				'brand'                 => $this->extract_brand( $ld, $breadcrumbs, $meta ),
				'sku_hint'              => $this->clean_text( $sku, 80 ),
				'price_hint'            => $price,
				'categories'            => $this->extract_structured_categories( $ld, $breadcrumbs, $meta ),
				'breadcrumbs'           => $breadcrumbs,
				'attributes'            => $attributes,
				'variants'              => $variants,
				'variation_attribute_options' => $variation_attribute_options,
				'source_variations'     => $woo_variations,
				'variant_media'         => $normalized_variant_media,
				'images'                => $images,
				'mapping_suggestions'   => array(),
				'completeness'          => array(
					'has_title'            => '' !== $title,
					'has_price'            => ! empty( $price['amount'] ),
					'has_variations'       => ! empty( $woo_variations ),
					'has_variation_images' => $this->variations_have_images( $woo_variations ),
					'has_gallery'          => count( $images ) > 1,
				),
			),
			'next_step_contract' => array(
				'required_before_publish' => array_values(
					array_filter(
						array(
							'' === $title ? 'title' : '',
							empty( $price['amount'] ) ? 'regular_price' : '',
							'' === trim( (string) $sku ) ? 'sku_or_permission_to_generate' : '',
						)
					)
				),
				'recommended_tools'       => array(
					'clone_partner_variable_product_from_url',
					'create_product',
					'manage_product_attributes',
					'add_product_variation',
					'set_product_featured_image_from_url',
					'manage_product_gallery',
					'woo_product_lookup',
				),
			),
		);
	}

	private function extract_woo_variations( $html ) {
		if ( ! preg_match( '/data-product_variations=(["\'])(.*?)\1/is', (string) $html, $match ) ) {
			return array();
		}

		$json = html_entity_decode( (string) $match[2], ENT_QUOTES | ENT_HTML5, 'UTF-8' );
		$data = json_decode( $json, true );
		if ( ! is_array( $data ) ) {
			return array();
		}

		$out = array();
		foreach ( $data as $item ) {
			if ( ! is_array( $item ) ) {
				continue;
			}
			$image = isset( $item['image'] ) && is_array( $item['image'] ) ? $item['image'] : array();
			$out[] = array(
				'source_variation_id'  => intval( isset( $item['variation_id'] ) ? $item['variation_id'] : 0 ),
				'attributes'           => isset( $item['attributes'] ) && is_array( $item['attributes'] ) ? $this->clean_attribute_map( $item['attributes'] ) : array(),
				'display_price'        => isset( $item['display_price'] ) ? $item['display_price'] : '',
				'display_regular_price'=> isset( $item['display_regular_price'] ) ? $item['display_regular_price'] : '',
				'price'                => isset( $item['display_regular_price'] ) && '' !== (string) $item['display_regular_price'] ? $item['display_regular_price'] : ( isset( $item['display_price'] ) ? $item['display_price'] : '' ),
				'sku'                  => isset( $item['sku'] ) ? $this->clean_text( $item['sku'], 80 ) : '',
				'is_in_stock'          => ! empty( $item['is_in_stock'] ),
				'is_purchasable'       => ! empty( $item['is_purchasable'] ),
				'variation_is_active'  => ! empty( $item['variation_is_active'] ),
				'variation_is_visible' => ! empty( $item['variation_is_visible'] ),
				'backorders_allowed'   => ! empty( $item['backorders_allowed'] ),
				'variation_description'=> isset( $item['variation_description'] ) ? $this->clean_text( $item['variation_description'], 1000 ) : '',
				'image'                => array(
					'title'    => isset( $image['title'] ) ? $this->clean_text( $image['title'], 160 ) : '',
					'alt'      => isset( $image['alt'] ) ? $this->clean_text( $image['alt'], 160 ) : '',
					'url'      => isset( $image['url'] ) ? esc_url_raw( $image['url'] ) : '',
					'full_src' => isset( $image['full_src'] ) ? esc_url_raw( $image['full_src'] ) : '',
					'src'      => isset( $image['src'] ) ? esc_url_raw( $image['src'] ) : '',
					'srcset'   => isset( $image['srcset'] ) ? (string) $image['srcset'] : '',
				),
				'gallery_images'       => $this->extract_source_variation_gallery_images( $item ),
			);
		}

		return $out;
	}

	private function extract_source_variation_gallery_images( $item ) {
		$gallery = array();
		foreach ( array( 'gallery_images', 'variation_gallery_images', 'variation_gallery', 'additional_images', 'media' ) as $key ) {
			if ( empty( $item[ $key ] ) ) {
				continue;
			}
			foreach ( (array) $item[ $key ] as $image ) {
				$url = $this->image_url_from_mixed( $image );
				if ( '' !== $url ) {
					$gallery[] = array( 'url' => $url, 'source' => 'woocommerce_variation_gallery' );
				}
			}
		}
		return array_values( $gallery );
	}

	private function image_url_from_mixed( $image ) {
		if ( is_string( $image ) ) {
			return esc_url_raw( $image );
		}
		if ( ! is_array( $image ) ) {
			return '';
		}
		foreach ( array( 'full_src', 'originalSrc', 'original_src', 'url', 'src' ) as $key ) {
			if ( ! empty( $image[ $key ] ) && is_string( $image[ $key ] ) ) {
				return esc_url_raw( $image[ $key ] );
			}
		}
		foreach ( array( 'preview_image', 'image' ) as $key ) {
			if ( ! empty( $image[ $key ] ) ) {
				$nested = $this->image_url_from_mixed( $image[ $key ] );
				if ( '' !== $nested ) {
					return $nested;
				}
			}
		}
		return '';
	}

	private function is_preferred_image_url( $candidate, $current ) {
		$candidate = (string) wp_parse_url( $candidate, PHP_URL_PATH );
		$current   = (string) wp_parse_url( $current, PHP_URL_PATH );
		$candidate_is_derivative = (bool) preg_match( '/-\d+x\d+(?=\.(?:jpe?g|png|webp)$)/i', $candidate );
		$current_is_derivative   = (bool) preg_match( '/-\d+x\d+(?=\.(?:jpe?g|png|webp)$)/i', $current );
		return ! $candidate_is_derivative && $current_is_derivative;
	}

	private function build_variant_summary( $woo_variations, $product_ld, $xpath, $variation_attribute_options = array() ) {
		$select_options = array();
		foreach ( (array) $variation_attribute_options as $definition ) {
			$name = isset( $definition['attribute'] ) ? (string) $definition['attribute'] : '';
			if ( '' === $name || empty( $definition['options'] ) || ! is_array( $definition['options'] ) ) {
				continue;
			}
			$select_options[ $name ] = array_values( array_unique( array_map( 'strval', $definition['options'] ) ) );
		}

		if ( ! empty( $woo_variations ) ) {
			$by_attr = array();
			foreach ( $woo_variations as $variation ) {
				foreach ( $variation['attributes'] as $name => $value ) {
					if ( '' === (string) $value ) {
						continue;
					}
					if ( ! isset( $by_attr[ $name ] ) ) {
						$by_attr[ $name ] = array();
					}
					$by_attr[ $name ][] = (string) $value;
				}
			}
			foreach ( $select_options as $name => $options ) {
				if ( ! isset( $by_attr[ $name ] ) ) {
					$by_attr[ $name ] = array();
				}
				$by_attr[ $name ] = array_merge( $by_attr[ $name ], $options );
			}
			$out = array();
			foreach ( $by_attr as $name => $values ) {
				$out[] = array(
					'attribute' => $name,
					'options'   => array_values( array_unique( $values ) ),
					'source'    => isset( $select_options[ $name ] ) ? 'woocommerce_data_product_variations_and_dom_select' : 'woocommerce_data_product_variations',
				);
			}
			return $out;
		}

		$variants = array();
		foreach ( array( 'hasVariant', 'isVariantOf' ) as $key ) {
			if ( empty( $product_ld[ $key ] ) || ! is_array( $product_ld[ $key ] ) ) {
				continue;
			}
			$items = isset( $product_ld[ $key ][0] ) ? $product_ld[ $key ] : array( $product_ld[ $key ] );
			foreach ( $items as $variant ) {
				if ( is_array( $variant ) ) {
					$variants[] = array(
						'title' => $this->clean_text( isset( $variant['name'] ) ? $variant['name'] : '', 160 ),
						'sku'   => $this->clean_text( isset( $variant['sku'] ) ? $variant['sku'] : '', 80 ),
						'source'=> 'jsonld',
					);
				}
			}
		}
		foreach ( $select_options as $name => $opts ) {
			$variants[] = array(
				'attribute' => $name,
				'options'   => array_values( array_unique( $opts ) ),
				'source'    => 'dom_select',
			);
		}
		return array_slice( $variants, 0, 30 );
	}

	private function extract_variation_attribute_options( $xpath ) {
		$options = array();
		if ( ! $xpath ) {
			return $options;
		}

		$selects = $xpath->query( '(//form[contains(concat(" ", normalize-space(@class), " "), " variations_form ")])[1]//select[starts-with(@name, "attribute_") or starts-with(@id, "pa_") or contains(concat(" ", normalize-space(@class), " "), " variation ")]' );
		if ( ! $selects || 0 === $selects->length ) {
			$selects = $xpath->query( '//select[starts-with(@name, "attribute_") or starts-with(@id, "pa_") or contains(concat(" ", normalize-space(@class), " "), " variation ")]' );
		}
		if ( ! $selects ) {
			return $options;
		}

		foreach ( $selects as $select ) {
			$name = $select->getAttribute( 'name' ) ? $select->getAttribute( 'name' ) : $select->getAttribute( 'id' );
			$name = $this->normalize_variation_attribute_key( $name );
			if ( '' === $name ) {
				continue;
			}

			$values = array();
			foreach ( $select->getElementsByTagName( 'option' ) as $option ) {
				$value = trim( (string) $option->getAttribute( 'value' ) );
				if ( '' === $value ) {
					continue;
				}
				$value = sanitize_title( $value );
				if ( '' !== $value ) {
					$values[] = $value;
				}
			}
			$values = array_values( array_unique( array_filter( $values ) ) );
			if ( empty( $values ) ) {
				continue;
			}

			$options[] = array(
				'attribute' => $name,
				'options'   => $values,
				'source'    => 'dom_select',
			);
		}

		return $options;
	}

	private function extract_images( $product_ld, $meta, $xpath, $base_url, $title, $max_images, $woo_variations, $variant_media = array() ) {
		$raw = array();
		foreach ( $woo_variations as $variation ) {
			foreach ( array( 'url', 'full_src', 'src' ) as $key ) {
				if ( ! empty( $variation['image'][ $key ] ) ) {
					$raw[] = $variation['image'][ $key ];
				}
			}
			if ( ! empty( $variation['image']['srcset'] ) ) {
				foreach ( explode( ',', (string) $variation['image']['srcset'] ) as $srcset_part ) {
					$piece = trim( $srcset_part );
					if ( '' !== $piece ) {
						$raw[] = preg_split( '/\s+/', $piece )[0];
					}
				}
			}
		}
		foreach ( (array) $variant_media as $media ) {
			foreach ( isset( $media['gallery_images'] ) && is_array( $media['gallery_images'] ) ? $media['gallery_images'] : array() as $image ) {
				if ( ! empty( $image['url'] ) ) {
					$raw[] = $image['url'];
				}
			}
		}

		$ld_images = isset( $product_ld['image'] ) ? $product_ld['image'] : array();
		foreach ( is_array( $ld_images ) ? $ld_images : array( $ld_images ) as $img ) {
			if ( is_array( $img ) && ! empty( $img['url'] ) ) {
				$raw[] = $img['url'];
			} elseif ( is_string( $img ) ) {
				$raw[] = $img;
			}
		}
		foreach ( array( 'og:image', 'og:image:url', 'twitter:image', 'twitter:image:src' ) as $key ) {
			if ( ! empty( $meta[ $key ] ) ) {
				$raw[] = $meta[ $key ];
			}
		}
		if ( $xpath ) {
			$nodes = $xpath->query( '//img[@src or @data-src or @data-large_image or @data-zoom-image or @srcset]' );
			if ( $nodes ) {
				foreach ( $nodes as $img ) {
					if ( ! $this->image_context->is_product_gallery_image_node( $img ) ) {
						continue;
					}
					foreach ( array( 'data-large_image', 'data-zoom-image', 'data-src', 'src' ) as $attr ) {
						$value = trim( (string) $img->getAttribute( $attr ) );
						if ( '' !== $value ) {
							$raw[] = $value;
						}
					}
				}
			}
			$links = $xpath->query( '//a[@href]' );
			if ( $links ) {
				foreach ( $links as $link ) {
					if ( ! $this->image_context->is_product_gallery_link_node( $link ) ) {
						continue;
					}
					$raw[] = trim( (string) $link->getAttribute( 'href' ) );
				}
			}
		}

		$images = array();
		foreach ( $raw as $idx => $image_url ) {
			$absolute = $this->absolute_url( $image_url, $base_url );
			if ( '' === $absolute || ! $this->image_context->is_product_image_url( $absolute ) ) {
				continue;
			}
			$key = $this->image_context->canonical_image_key( $absolute );
			if ( isset( $images[ $key ] ) ) {
				if ( $this->is_preferred_image_url( $absolute, $images[ $key ]['url'] ) ) {
					$images[ $key ]['url'] = $absolute;
				}
				continue;
			}
			$images[ $key ] = array(
				'url'                => $absolute,
				'recommended_role'   => empty( $images ) ? 'featured' : 'gallery',
				'alt_text_suggested' => $this->clean_text( $title, 140 ),
				'source_rank'        => $idx + 1,
			);
			if ( count( $images ) >= $max_images ) {
				break;
			}
		}
		return array_values( $images );
	}

	private function extract_attributes( $product_ld, $xpath ) {
		$attributes = array();
		if ( ! empty( $product_ld['additionalProperty'] ) && is_array( $product_ld['additionalProperty'] ) ) {
			foreach ( $product_ld['additionalProperty'] as $prop ) {
				if ( ! is_array( $prop ) ) {
					continue;
				}
				$name  = $this->clean_text( isset( $prop['name'] ) ? $prop['name'] : '', 80 );
				$value = $this->clean_text( isset( $prop['value'] ) ? $prop['value'] : '', 180 );
				if ( '' !== $name && '' !== $value ) {
					$attributes[] = array( 'name' => $name, 'values' => array( $value ), 'source' => 'jsonld' );
				}
			}
		}
		if ( $xpath ) {
			$rows = $xpath->query( '//table//tr[th and td] | //table//tr[td[1] and td[2]] | //dl//*[self::dt or self::dd]' );
			$pending = '';
			if ( $rows ) {
				foreach ( $rows as $row ) {
					$tag = strtolower( $row->nodeName );
					if ( 'dt' === $tag ) {
						$pending = $this->clean_text( $row->textContent, 80 );
						continue;
					}
					if ( 'dd' === $tag && '' !== $pending ) {
						$value = $this->clean_text( $row->textContent, 180 );
						if ( '' !== $value ) {
							$attributes[] = array( 'name' => $pending, 'values' => $this->split_attribute_values( $value ), 'source' => 'dom' );
						}
						$pending = '';
						continue;
					}
					$cells = $row->getElementsByTagName( 'td' );
					$heads = $row->getElementsByTagName( 'th' );
					$name  = $heads->length > 0 ? $heads->item( 0 )->textContent : ( $cells->length > 1 ? $cells->item( 0 )->textContent : '' );
					$value = $cells->length > 0 ? $cells->item( $cells->length > 1 ? 1 : 0 )->textContent : '';
					$name  = $this->clean_text( $name, 80 );
					$value = $this->clean_text( $value, 300 );
					if ( '' !== $name && '' !== $value ) {
						$attributes[] = array( 'name' => $name, 'values' => $this->split_attribute_values( $value ), 'source' => 'dom' );
					}
				}
			}
		}
		return $this->merge_attributes( $attributes );
	}

	private function extract_structured_categories( $product_ld, $breadcrumbs, $meta ) {
		$candidates = array();
		if ( ! empty( $product_ld['category'] ) ) {
			$category = is_array( $product_ld['category'] ) ? implode( ' ', array_map( 'strval', $product_ld['category'] ) ) : (string) $product_ld['category'];
			$candidates[] = $category;
		}
		foreach ( (array) $breadcrumbs as $crumb ) {
			$candidates[] = $crumb;
		}
		if ( ! empty( $meta['article:section'] ) ) {
			$candidates[] = $meta['article:section'];
		}

		$clean = array();
		foreach ( $candidates as $candidate ) {
			$candidate = $this->clean_text( $candidate, 100 );
			if ( '' !== $candidate ) {
				$clean[] = $candidate;
			}
		}
		return array_values( array_unique( $clean ) );
	}

	private function extract_price( $product_ld, $meta, $xpath, $woo_variations ) {
		$amount = $this->first_variation_value( $woo_variations, 'price' );
		$currency = '';
		if ( empty( $amount ) && ! empty( $product_ld['offers'] ) ) {
			$offer = isset( $product_ld['offers'][0] ) ? $product_ld['offers'][0] : $product_ld['offers'];
			if ( is_array( $offer ) ) {
				$amount   = isset( $offer['price'] ) ? $offer['price'] : ( isset( $offer['lowPrice'] ) ? $offer['lowPrice'] : '' );
				$currency = isset( $offer['priceCurrency'] ) ? $offer['priceCurrency'] : '';
			}
		}
		if ( empty( $amount ) ) {
			foreach ( array( 'product:price:amount', 'og:price:amount' ) as $key ) {
				if ( ! empty( $meta[ $key ] ) ) {
					$amount = $meta[ $key ];
					break;
				}
			}
		}
		if ( '' === $currency ) {
			foreach ( array( 'product:price:currency', 'og:price:currency' ) as $key ) {
				if ( ! empty( $meta[ $key ] ) ) {
					$currency = $meta[ $key ];
					break;
				}
			}
		}
		return array(
			'amount'   => $this->clean_text( $amount, 40 ),
			'currency' => $this->clean_text( $currency, 12 ),
		);
	}

	private function load_dom( $html ) {
		if ( ! class_exists( 'DOMDocument' ) ) {
			return null;
		}
		$dom      = new DOMDocument();
		$previous = libxml_use_internal_errors( true );
		$loaded   = $dom->loadHTML( '<?xml encoding="utf-8" ?>' . (string) $html );
		libxml_clear_errors();
		libxml_use_internal_errors( $previous );
		return $loaded ? $dom : null;
	}

	private function extract_meta( $xpath ) {
		$meta  = array();
		$nodes = $xpath->query( '//meta[@content]' );
		if ( ! $nodes ) {
			return $meta;
		}
		foreach ( $nodes as $node ) {
			$key = trim( (string) $node->getAttribute( 'property' ) );
			if ( '' === $key ) {
				$key = trim( (string) $node->getAttribute( 'name' ) );
			}
			$value = trim( (string) $node->getAttribute( 'content' ) );
			if ( '' !== $key && '' !== $value && ! isset( $meta[ $key ] ) ) {
				$meta[ $key ] = $value;
			}
		}
		return $meta;
	}

	private function extract_jsonld_nodes( $xpath ) {
		$out   = array();
		$nodes = $xpath->query( '//script[@type="application/ld+json"]' );
		if ( ! $nodes ) {
			return $out;
		}
		foreach ( $nodes as $node ) {
			$decoded = json_decode( trim( (string) $node->textContent ), true );
			if ( is_array( $decoded ) ) {
				$out[] = $decoded;
			}
		}
		return $out;
	}

	private function pick_product_ld( $nodes ) {
		$queue = (array) $nodes;
		while ( ! empty( $queue ) ) {
			$item = array_shift( $queue );
			if ( ! is_array( $item ) ) {
				continue;
			}
			if ( ! empty( $item['@graph'] ) && is_array( $item['@graph'] ) ) {
				foreach ( $item['@graph'] as $child ) {
					$queue[] = $child;
				}
			}
			$type = isset( $item['@type'] ) ? $item['@type'] : '';
			$types = is_array( $type ) ? $type : array( $type );
			if ( in_array( 'Product', $types, true ) ) {
				return $item;
			}
		}
		return array();
	}

	private function extract_breadcrumbs( $jsonld_nodes, $xpath ) {
		$crumbs = array();
		$queue  = (array) $jsonld_nodes;
		while ( ! empty( $queue ) ) {
			$item = array_shift( $queue );
			if ( ! is_array( $item ) ) {
				continue;
			}
			if ( ! empty( $item['@graph'] ) && is_array( $item['@graph'] ) ) {
				foreach ( $item['@graph'] as $child ) {
					$queue[] = $child;
				}
			}
			$type = isset( $item['@type'] ) ? $item['@type'] : '';
			if ( 'BreadcrumbList' !== $type || empty( $item['itemListElement'] ) || ! is_array( $item['itemListElement'] ) ) {
				continue;
			}
			foreach ( $item['itemListElement'] as $element ) {
				if ( is_array( $element ) && ! empty( $element['name'] ) ) {
					$crumbs[] = $this->clean_text( $element['name'], 120 );
				}
			}
		}
		if ( empty( $crumbs ) && $xpath ) {
			$nodes = $xpath->query( '//*[contains(@class, "breadcrumb")]//a | //nav[contains(@class, "breadcrumb")]//*[self::a or self::span]' );
			if ( $nodes ) {
				foreach ( $nodes as $node ) {
					$crumbs[] = $this->clean_text( $node->textContent, 120 );
				}
			}
		}
		return array_values( array_unique( array_filter( $crumbs ) ) );
	}

	private function base_url( $fallback_url, $xpath ) {
		if ( $xpath ) {
			$nodes = $xpath->query( '//base[@href]' );
			if ( $nodes && $nodes->length > 0 ) {
				$href = trim( (string) $nodes->item( 0 )->getAttribute( 'href' ) );
				if ( '' !== $href ) {
					return $href;
				}
			}
		}
		return $fallback_url;
	}

	private function extract_brand( $ld, $breadcrumbs, $meta ) {
		$brand = $this->ld_value( $ld, 'brand' );
		if ( '' !== $brand ) {
			return $this->clean_text( $brand, 120 );
		}
		if ( ! empty( $meta['product:brand'] ) ) {
			return $this->clean_text( $meta['product:brand'], 120 );
		}
		return ! empty( $breadcrumbs[0] ) ? $this->clean_text( $breadcrumbs[0], 120 ) : '';
	}

	private function ld_value( $ld, $key ) {
		if ( ! is_array( $ld ) || ! isset( $ld[ $key ] ) ) {
			return '';
		}
		$value = $ld[ $key ];
		if ( is_array( $value ) ) {
			return isset( $value['name'] ) ? (string) $value['name'] : implode( ' ', array_map( 'strval', array_filter( $value, 'is_scalar' ) ) );
		}
		return (string) $value;
	}

	private function first_xpath_text( $xpath, $queries, $limit = 1200 ) {
		foreach ( (array) $queries as $query ) {
			$nodes = $xpath->query( $query );
			if ( ! $nodes || $nodes->length <= 0 ) {
				continue;
			}
			foreach ( $nodes as $node ) {
				$text = $this->clean_text( $node->textContent, $limit );
				if ( '' !== $text ) {
					return $text;
				}
			}
		}
		return '';
	}

	private function clean_attribute_map( $attributes ) {
		$out = array();
		foreach ( (array) $attributes as $key => $value ) {
			$key = $this->normalize_variation_attribute_key( $key );
			if ( '' !== $key ) {
				$out[ $key ] = sanitize_title( (string) $value );
			}
		}
		return $out;
	}

	private function normalize_variation_attribute_key( $key ) {
		$key = sanitize_key( preg_replace( '/^attribute_/', '', (string) $key ) );
		return 0 === strpos( $key, 'pa_' ) ? $key : ( '' !== $key ? 'pa_' . $key : '' );
	}

	private function split_attribute_values( $value ) {
		$parts = preg_split( '/\s*,\s*/u', (string) $value );
		return array_values( array_unique( array_filter( array_map( array( $this, 'clean_text' ), $parts ) ) ) );
	}

	private function merge_attributes( $attributes ) {
		$merged = array();
		foreach ( (array) $attributes as $attr ) {
			$name   = isset( $attr['name'] ) ? $this->clean_text( $attr['name'], 80 ) : '';
			$values = isset( $attr['values'] ) && is_array( $attr['values'] ) ? $attr['values'] : array();
			if ( '' === $name || empty( $values ) ) {
				continue;
			}
			$key = sanitize_title( $name );
			if ( ! isset( $merged[ $key ] ) ) {
				$merged[ $key ] = array(
					'name'   => $name,
					'values' => array(),
					'source' => isset( $attr['source'] ) ? (string) $attr['source'] : 'unknown',
				);
			}
			foreach ( $values as $value ) {
				$value = $this->clean_text( $value, 180 );
				if ( '' !== $value ) {
					$merged[ $key ]['values'][] = $value;
				}
			}
			$merged[ $key ]['values'] = array_values( array_unique( $merged[ $key ]['values'] ) );
		}
		return array_values( $merged );
	}

	private function first_variation_value( $variations, $key ) {
		foreach ( (array) $variations as $variation ) {
			if ( isset( $variation[ $key ] ) && '' !== trim( (string) $variation[ $key ] ) ) {
				return $variation[ $key ];
			}
		}
		return '';
	}

	private function variations_have_images( $variations ) {
		foreach ( (array) $variations as $variation ) {
			$image = isset( $variation['image'] ) && is_array( $variation['image'] ) ? $variation['image'] : array();
			if ( empty( $image['url'] ) && empty( $image['full_src'] ) ) {
				return false;
			}
		}
		return ! empty( $variations );
	}

	private function build_warnings( $title, $images, $variations ) {
		$warnings = array();
		if ( '' === $title ) {
			$warnings[] = 'Naziv proizvoda nije pouzdano pronadjen.';
		}
		if ( empty( $images ) ) {
			$warnings[] = 'Nisu pronadjene pouzdane product/gallery slike.';
		}
		if ( empty( $variations ) ) {
			$warnings[] = 'Nema structured Woo variation payload-a; variable clone treba dodatnu proveru.';
		}
		return $warnings;
	}

	private function first_non_empty() {
		foreach ( func_get_args() as $value ) {
			$value = trim( (string) $value );
			if ( '' !== $value ) {
				return $value;
			}
		}
		return '';
	}

	private function first_sentence( $text ) {
		$text = $this->clean_text( $text, 800 );
		if ( preg_match( '/^(.{40,260}?[.!?])\s/u', $text, $m ) ) {
			return $m[1];
		}
		return mb_substr( $text, 0, 260 );
	}

	private function clean_text( $text, $limit = 1000 ) {
		$text = html_entity_decode( wp_strip_all_tags( (string) $text ), ENT_QUOTES | ENT_HTML5, 'UTF-8' );
		$text = preg_replace( '/\s+/u', ' ', $text );
		$text = trim( (string) $text );
		if ( $limit > 0 && mb_strlen( $text ) > $limit ) {
			$text = mb_substr( $text, 0, $limit - 1 ) . '...';
		}
		return $text;
	}

	private function absolute_url( $url, $base_url ) {
		$url = trim( html_entity_decode( (string) $url, ENT_QUOTES | ENT_HTML5, 'UTF-8' ) );
		if ( '' === $url || 0 === strpos( $url, 'data:' ) ) {
			return '';
		}
		if ( preg_match( '#^https?://#i', $url ) ) {
			return esc_url_raw( $url );
		}
		$base = wp_parse_url( $base_url );
		if ( empty( $base['scheme'] ) || empty( $base['host'] ) ) {
			return '';
		}
		if ( 0 === strpos( $url, '//' ) ) {
			return esc_url_raw( $base['scheme'] . ':' . $url );
		}
		$root = $base['scheme'] . '://' . $base['host'] . ( ! empty( $base['port'] ) ? ':' . $base['port'] : '' );
		if ( 0 === strpos( $url, '/' ) ) {
			return esc_url_raw( $root . $url );
		}
		$path = isset( $base['path'] ) ? preg_replace( '#/[^/]*$#', '/', $base['path'] ) : '/';
		return esc_url_raw( $root . $path . $url );
	}

	private function is_product_image_url( $url ) {
		return $this->image_context->is_product_image_url( $url );
	}

	private function is_product_gallery_image_node( $img ) {
		return $this->image_context->is_product_gallery_image_node( $img );
	}

	private function canonical_image_key( $url ) {
		return $this->image_context->canonical_image_key( $url );
	}
}
