<?php
/**
 * WooCommerce Enhanced Schema Filter
 *
 * Filters WooCommerce's native structured data to merge/enhance it
 * with our own product schema. Removes duplicate WC breadcrumb schema,
 * adds variation images, product attributes, and return policy to
 * organization schema.
 *
 * @package SEOvaultSEO\WooCommerce
 */

namespace SEOvaultSEO\WooCommerce;

use SEOvaultSEO\Core\Options;

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

class Schema_Filter {

	/**
	 * Singleton instance.
	 *
	 * @var Schema_Filter|null
	 */
	private static $instance = null;

	/**
	 * Collected variation image schema entries.
	 *
	 * @var array
	 */
	private $variation_images = array();

	/**
	 * True while enhance_product_schema is running (blocks re-entrant enhance).
	 *
	 * @var bool
	 */
	private static $enhancing = false;

	/**
	 * Get singleton.
	 *
	 * @return Schema_Filter
	 */
	public static function get_instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Constructor — registers hooks if enabled.
	 */
	private function __construct() {
		if ( ! Options::is_wc_enhanced_schema() ) {
			return;
		}

		// Filter WC's native product structured data.
		add_filter( 'woocommerce_structured_data_product', array( $this, 'enhance_product_schema' ), 10, 2 );

		// Remove WC breadcrumb schema in favor of ours.
		if ( Options::is_wc_remove_breadcrumb_schema() ) {
			add_filter( 'woocommerce_structured_data_type_for_page', array( $this, 'remove_wc_breadcrumbs' ) );
		}

		// Add return policy to organization schema on shop pages.
		add_filter( 'seovaultseo_set_schema', array( $this, 'add_return_policy_to_organization' ) );
	}

	/**
	 * Enhance WooCommerce's native product structured data with our fields.
	 *
	 * Merges brand, GTIN/MPN/ISBN, manufacturer, variation images,
	 * and allowed product attributes into WC's native schema output.
	 *
	 * @param array       $data    WC's schema product data.
	 * @param \WC_Product $product Product object.
	 * @return array Enhanced schema data.
	 */
	public function enhance_product_schema( $data, $product ) {
		if ( self::$enhancing ) {
			return $data;
		}

		if ( ! is_array( $data ) || empty( $data ) ) {
			return $data;
		}

		if ( ! is_object( $product ) ) {
			return $data;
		}

		self::$enhancing = true;

		try {
			$product_id = $product->get_id();

			// Fix WC's false image value.
			if ( isset( $data['image'] ) && false === $data['image'] ) {
				unset( $data['image'] );
			}

			// Remove SKU if empty (WC falls back to product ID).
			if ( empty( $product->get_sku() ) && isset( $data['sku'] ) ) {
				unset( $data['sku'] );
			}

			// Add brand.
			$data = $this->merge_brand( $data, $product_id );

			// Add manufacturer.
			$data = $this->merge_manufacturer( $data, $product_id );

			// Add GTIN / MPN / ISBN identifiers.
			$data = $this->merge_identifiers( $data, $product );

			// Add product attributes (color, pattern, material, size).
			if ( Options::is_wc_schema_product_attributes() ) {
				$data = $this->merge_product_attributes( $data, $product );
			}

			// Add variation images for variable products.
			if ( Options::is_wc_schema_variation_images() && method_exists( $product, 'get_available_variations' ) ) {
				$data = $this->merge_variation_images( $data, $product );
			}

			// Enhance offers with seller reference and price specifications.
			$data = $this->enhance_offers( $data, $product );

			// Enhance reviews with @id and product name.
			$data = $this->enhance_reviews( $data, $product );

			/**
			 * Filter: 'seovaultseo_wc_enhanced_product_schema' - Allow further changes to the enhanced product schema.
			 *
			 * @param array       $data    Schema product data.
			 * @param \WC_Product $product Product object.
			 */
			return apply_filters( 'seovaultseo_wc_enhanced_product_schema', $data, $product );
		} finally {
			self::$enhancing = false;
		}
	}

	/**
	 * Remove 'breadcrumblist' from WooCommerce's schema types.
	 *
	 * @param array $types Schema types WC will render.
	 * @return array Filtered types.
	 */
	public function remove_wc_breadcrumbs( $types ) {
		if ( ! is_array( $types ) ) {
			return $types;
		}

		foreach ( $types as $key => $type ) {
			if ( 'breadcrumblist' === strtolower( $type ) ) {
				unset( $types[ $key ] );
			}
		}

		return array_values( $types );
	}

	/**
	 * Add return policy to organization schema on shop pages.
	 *
	 * @param array $schemas Existing schema data.
	 * @return array
	 */
	public function add_return_policy_to_organization( $schemas ) {
		$return_policy_id = Options::get_wc_schema_return_policy_page();

		if ( ! $return_policy_id ) {
			return $schemas;
		}

		// Only include published pages.
		$post = get_post( $return_policy_id );
		if ( ! $post || 'publish' !== $post->post_status ) {
			return $schemas;
		}

		$url = get_permalink( $return_policy_id );

		if ( ! $url ) {
			return $schemas;
		}

		foreach ( $schemas as $key => $schema ) {
			if ( ! is_array( $schema ) ) {
				continue;
			}

			$type  = $schema['@type'] ?? '';
			$types = is_array( $type ) ? $type : array( $type );

			if ( in_array( 'Organization', $types, true ) || in_array( 'OnlineStore', $types, true ) ) {
				$schemas[ $key ]['hasMerchantReturnPolicy'] = array(
					'@type'              => 'MerchantReturnPolicy',
					'merchantReturnLink' => esc_url( $url ),
				);
			}
		}

		return $schemas;
	}

	/**
	 * Merge brand into the schema data.
	 *
	 * @param array $data       Schema data.
	 * @param int   $product_id Product ID.
	 * @return array
	 */
	protected function merge_brand( $data, $product_id ) {
		if ( isset( $data['brand'] ) && ! empty( $data['brand'] ) ) {
			return $data;
		}

		$brand = WooCommerce::get_brand( $product_id );

		if ( empty( $brand ) ) {
			$custom_brand = Options::get_wc_custom_brand();
			if ( ! empty( $custom_brand ) ) {
				$brand = $custom_brand;
			}
		}

		if ( ! empty( $brand ) ) {
			$data['brand'] = array(
				'@type' => 'Brand',
				'name'  => wp_strip_all_tags( $brand ),
			);
		}

		return $data;
	}

	/**
	 * Merge manufacturer reference into the schema data.
	 *
	 * @param array $data       Schema data.
	 * @param int   $product_id Product ID.
	 * @return array
	 */
	protected function merge_manufacturer( $data, $product_id ) {
		if ( isset( $data['manufacturer'] ) ) {
			return $data;
		}

		if ( Options::is_wc_additional_schema() ) {
			$data['manufacturer'] = array(
				'@id' => home_url( '/#organization' ),
			);
		}

		return $data;
	}

	/**
	 * Merge GTIN/MPN/ISBN identifiers into the schema data.
	 *
	 * @param array       $data    Schema data.
	 * @param \WC_Product $product Product object.
	 * @return array
	 */
	protected function merge_identifiers( $data, $product ) {
		// If per-product identifiers are enabled, use all available.
		if ( Options::is_wc_per_product_identifiers() ) {
			$pid    = Product_Identifiers::get_instance();
			$result = $pid->get_schema_identifiers( $product->get_id() );

			if ( ! empty( $result['properties'] ) ) {
				foreach ( $result['properties'] as $type => $value ) {
					if ( ! isset( $data[ $type ] ) || empty( $data[ $type ] ) ) {
						$data[ $type ] = $value;
					}
				}

				if ( $result['has_isbn'] ) {
					if ( ! isset( $data['@type'] ) ) {
						$data['@type'] = 'Product';
					}
					if ( ! is_array( $data['@type'] ) ) {
						$data['@type'] = array( $data['@type'] );
					}
					if ( ! in_array( 'Book', $data['@type'], true ) ) {
						$data['@type'] = array_merge( array( 'Book' ), $data['@type'] );
					}
				}
			}
		} else {
			// Single-type mode.
			$gtin_type = Options::get_wc_gtin_type();
			$wc        = WooCommerce::get_instance();
			$gtin      = $wc->get_gtin_value( $product );

			if ( ! empty( $gtin ) && ( ! isset( $data[ $gtin_type ] ) || empty( $data[ $gtin_type ] ) ) ) {
				$data[ $gtin_type ] = $gtin;

				if ( 'isbn' === $gtin_type ) {
					if ( ! isset( $data['@type'] ) ) {
						$data['@type'] = 'Product';
					}
					if ( ! is_array( $data['@type'] ) ) {
						$data['@type'] = array( $data['@type'] );
					}
					if ( ! in_array( 'Book', $data['@type'], true ) ) {
						$data['@type'] = array_merge( array( 'Book' ), $data['@type'] );
					}
				}
			}
		}

		return $data;
	}

	/**
	 * Merge allowed product attributes (color, pattern, material, size) into schema.
	 *
	 * @param array       $data    Schema data.
	 * @param \WC_Product $product Product object.
	 * @return array
	 */
	protected function merge_product_attributes( $data, $product ) {
		// Skip for variable products — attributes are on variants.
		if ( method_exists( $product, 'get_type' ) && 'variable' === $product->get_type() ) {
			return $data;
		}

		if ( ! method_exists( $product, 'get_attributes' ) ) {
			return $data;
		}

		$attributes    = $product->get_attributes();
		$allowed_attrs = Options::get_wc_allowed_product_attributes();

		if ( empty( $attributes ) || ! is_array( $attributes ) ) {
			return $data;
		}

		foreach ( $attributes as $attribute ) {
			if ( ! method_exists( $attribute, 'get_name' ) || ! method_exists( $attribute, 'get_options' ) ) {
				continue;
			}

			$attr_name = strtolower( $this->get_attribute_label( $attribute->get_name() ) );

			if ( ! in_array( $attr_name, $allowed_attrs, true ) ) {
				continue;
			}

			$options = $attribute->get_options();

			// Only include single-value attributes.
			if ( is_array( $options ) && count( $options ) !== 1 ) {
				continue;
			}

			if ( is_array( $options ) ) {
				$option_id = reset( $options );
			} else {
				$option_id = $options;
			}

			$label = $this->get_attribute_term_label( $option_id );

			if ( ! empty( $label ) && ! isset( $data[ $attr_name ] ) ) {
				$data[ $attr_name ] = wp_strip_all_tags( $label );
			}
		}

		return $data;
	}

	/**
	 * Merge variation images into the main product schema.
	 *
	 * @param array       $data    Schema data.
	 * @param \WC_Product $product Product object.
	 * @return array
	 */
	protected function merge_variation_images( $data, $product ) {
		if ( ! method_exists( $product, 'get_available_variations' ) ) {
			return $data;
		}

		$variations = $product->get_available_variations( 'object' );

		if ( empty( $variations ) || ! is_array( $variations ) ) {
			return $data;
		}

		$variation_image_urls = array();

		foreach ( $variations as $variation ) {
			if ( ! is_object( $variation ) || ! method_exists( $variation, 'get_image_id' ) ) {
				continue;
			}

			$image_id = $variation->get_image_id();

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

			$image_url = wp_get_attachment_image_url( $image_id, 'full' );

			if ( $image_url ) {
				$variation_image_urls[] = $image_url;
			}
		}

		if ( empty( $variation_image_urls ) ) {
			return $data;
		}

		// Build image list: main image + variation images.
		$image_list = array();

		if ( isset( $data['image'] ) && is_string( $data['image'] ) && ! empty( $data['image'] ) ) {
			$image_list[] = $data['image'];
		} elseif ( isset( $data['image'] ) && is_array( $data['image'] ) ) {
			$image_list = $data['image'];
		}

		foreach ( $variation_image_urls as $url ) {
			if ( ! in_array( $url, $image_list, true ) ) {
				$image_list[] = $url;
			}
		}

		if ( count( $image_list ) > 1 ) {
			$data['image'] = $image_list;
		}

		return $data;
	}

	/**
	 * Enhance offers with seller reference and availability adjustments.
	 *
	 * @param array       $data    Schema data.
	 * @param \WC_Product $product Product object.
	 * @return array
	 */
	protected function enhance_offers( $data, $product ) {
		if ( ! isset( $data['offers'] ) || empty( $data['offers'] ) ) {
			return $data;
		}

		$offers = $data['offers'];

		// Normalize to array of offers.
		if ( isset( $offers['@type'] ) ) {
			$offers = array( $offers );
		}

		$site_url = home_url( '/' );
		$org_hash = $site_url . '#organization';

		foreach ( $offers as $key => $offer ) {
			if ( ! is_array( $offer ) ) {
				continue;
			}

			// Add seller reference to organization.
			if ( ! isset( $offers[ $key ]['seller'] ) ) {
				$offers[ $key ]['seller'] = array( '@id' => $org_hash );
			}

			// Adjust availability for backorder products.
			if ( method_exists( $product, 'is_on_backorder' ) && $product->is_on_backorder() ) {
				$offers[ $key ]['availability'] = 'https://schema.org/PreOrder';
			}

			// Remove priceValidUntil for non-sale products.
			if ( method_exists( $product, 'is_on_sale' ) && method_exists( $product, 'get_date_on_sale_to' ) ) {
				if ( ! $product->is_on_sale() || ! $product->get_date_on_sale_to() ) {
					if ( isset( $offers[ $key ]['priceValidUntil'] ) ) {
						unset( $offers[ $key ]['priceValidUntil'] );
					}
				}
			}
		}

		$data['offers'] = array_values( $offers );

		return $data;
	}

	/**
	 * Enhance review data with @id and product name.
	 *
	 * @param array       $data    Schema data.
	 * @param \WC_Product $product Product object.
	 * @return array
	 */
	protected function enhance_reviews( $data, $product ) {
		if ( ! isset( $data['review'] ) || empty( $data['review'] ) ) {
			return $data;
		}

		if ( ! is_array( $data['review'] ) ) {
			return $data;
		}

		$product_id   = $product->get_id();
		$product_name = method_exists( $product, 'get_name' ) ? $product->get_name() : '';
		$site_url     = home_url( '/' );

		foreach ( $data['review'] as $key => $review ) {
			if ( ! is_array( $review ) ) {
				continue;
			}

			if ( ! isset( $data['review'][ $key ]['@id'] ) ) {
				$data['review'][ $key ]['@id'] = $site_url . '#/schema/review/' . $product_id . '-' . $key;
			}

			if ( ! empty( $product_name ) && ! isset( $data['review'][ $key ]['name'] ) ) {
				$data['review'][ $key ]['name'] = $product_name;
			}
		}

		return $data;
	}

	/**
	 * Get the label for an attribute name.
	 *
	 * @param string $name Attribute name (e.g. 'pa_color').
	 * @return string
	 */
	protected function get_attribute_label( $name ) {
		if ( function_exists( 'wc_attribute_label' ) ) {
			return wc_attribute_label( $name );
		}

		// Fallback: strip 'pa_' prefix and capitalize.
		$clean = str_replace( 'pa_', '', $name );
		return ucfirst( $clean );
	}

	/**
	 * Get the label for an attribute term by ID.
	 *
	 * @param int $term_id Term ID.
	 * @return string|null
	 */
	protected function get_attribute_term_label( $term_id ) {
		$term_id = absint( $term_id );

		if ( ! $term_id ) {
			return null;
		}

		$term = get_term( $term_id );

		if ( is_wp_error( $term ) || ! $term ) {
			return null;
		}

		return $term->name;
	}
}
