<?php
/**
 * WooCommerce built-in source resolver.
 *
 * Resolves product IDs for the three WooCommerce native recommendation
 * sources: Related Products, Upsells, and Cross-sells.
 *
 * These bypass the rules engine entirely — products are fetched directly
 * from WooCommerce data structures.
 *
 * @package PRECO
 */

namespace PRECO\Includes\Frontend\Core\Engine;

defined( 'ABSPATH' ) || exit;

/**
 * WooCommerce built-in source resolver.
 */
class ProductRelationsResolver {

	/**
	 * Sources handled by this resolver.
	 */
	const SOURCES = array( 'related', 'upsells', 'cross_sells' );

	/**
	 * Whether this resolver handles the given source value.
	 *
	 * @param string $source Source value from list config.
	 * @return bool
	 */
	public function handles( $source ) {
		return in_array( (string) $source, self::SOURCES, true );
	}

	/**
	 * Resolve product IDs for a WooCommerce built-in source.
	 *
	 * @param string $source  'related' | 'upsells' | 'cross_sells'.
	 * @param array  $context Page context (product_id, exclude_ids, …).
	 * @param int    $limit   Maximum number of IDs to return.
	 * @return int[]
	 */
	public function resolve( $source, $context, $limit ) {
		$limit = max( 1, (int) $limit );

		switch ( (string) $source ) {
			case 'related':
				return $this->resolve_related( $context, $limit );

			case 'upsells':
				return $this->resolve_upsells( $context, $limit );

			case 'cross_sells':
				return $this->resolve_cross_sells( $context, $limit );

			default:
				return array();
		}
	}

	// -----------------------------------------------------------------------
	// Private resolvers
	// -----------------------------------------------------------------------

	/**
	 * Resolve WooCommerce related products for the current product.
	 *
	 * @param array $context Page context (product_id, exclude_ids, …).
	 * @param int   $limit   Maximum number of IDs to return.
	 * @return int[]
	 */
	private function resolve_related( $context, $limit ) {
		$product_id = isset( $context['product_id'] ) ? absint( $context['product_id'] ) : 0;
		if ( ! $product_id ) {
			return array();
		}

		$ids = wc_get_related_products( $product_id, $limit );
		return $this->sanitize_ids( $ids, $limit );
	}

	/**
	 * Resolve upsell IDs.
	 *
	 * On a product page, uses that product's own configured upsell IDs.
	 * Otherwise (e.g. the thank-you page), aggregates upsell IDs from every
	 * product in the resolved order, excluding products already in that order.
	 *
	 * @param array $context Page context (product_id, order_id, exclude_ids, …).
	 * @param int   $limit   Maximum number of IDs to return.
	 * @return int[]
	 */
	private function resolve_upsells( $context, $limit ) {
		$product_id = isset( $context['product_id'] ) ? absint( $context['product_id'] ) : 0;
		if ( $product_id ) {
			$product = wc_get_product( $product_id );
			if ( ! $product ) {
				return array();
			}

			return $this->sanitize_ids( $product->get_upsell_ids(), $limit );
		}

		$order_id = isset( $context['order_id'] ) ? absint( $context['order_id'] ) : 0;
		if ( ! $order_id || ! function_exists( 'wc_get_order' ) ) {
			return array();
		}

		$order = \wc_get_order( $order_id );
		if ( ! $order || ! method_exists( $order, 'get_items' ) ) {
			return array();
		}

		$ids = array();
		foreach ( $order->get_items() as $item ) {
			$item_id = ( $item && method_exists( $item, 'get_product_id' ) ) ? absint( $item->get_product_id() ) : 0;
			if ( ! $item_id ) {
				continue;
			}
			$product = wc_get_product( $item_id );
			if ( $product ) {
				$ids = array_merge( $ids, $product->get_upsell_ids() );
			}
		}

		return $this->sanitize_ids_excluding( $ids, $context, $limit );
	}

	/**
	 * Resolve cross-sell IDs.
	 *
	 * On a product page, uses that product's own configured cross-sell IDs.
	 * Otherwise (e.g. cart/checkout), aggregates cross-sell IDs from every
	 * product currently in the cart, excluding products already in the cart.
	 *
	 * @param array $context Page context (product_id, exclude_ids, …).
	 * @param int   $limit   Maximum number of IDs to return.
	 * @return int[]
	 */
	private function resolve_cross_sells( $context, $limit ) {
		$product_id = isset( $context['product_id'] ) ? absint( $context['product_id'] ) : 0;
		if ( $product_id ) {
			$product = wc_get_product( $product_id );
			if ( ! $product ) {
				return array();
			}

			return $this->sanitize_ids( $product->get_cross_sell_ids(), $limit );
		}

		if ( ! function_exists( 'WC' ) || ! \WC() || ! \WC()->cart ) {
			return array();
		}

		$cart = \WC()->cart;
		if ( ! method_exists( $cart, 'get_cart' ) ) {
			return array();
		}

		$ids = array();
		foreach ( $cart->get_cart() as $item ) {
			$item_id = isset( $item['product_id'] ) ? absint( $item['product_id'] ) : 0;
			if ( ! $item_id ) {
				continue;
			}
			$product = wc_get_product( $item_id );
			if ( $product ) {
				$ids = array_merge( $ids, $product->get_cross_sell_ids() );
			}
		}

		return $this->sanitize_ids_excluding( $ids, $context, $limit );
	}

	/**
	 * Sanitize, deduplicate and slice a raw product ID array.
	 *
	 * @param mixed $ids  Raw IDs (array or any).
	 * @param int   $limit Max items.
	 * @return int[]
	 */
	private function sanitize_ids( $ids, $limit ) {
		if ( ! is_array( $ids ) ) {
			return array();
		}
		$ids = array_values( array_unique( array_filter( array_map( 'absint', $ids ) ) ) );
		return array_slice( $ids, 0, $limit );
	}

	/**
	 * Sanitize, deduplicate, and slice a raw product ID array, then exclude
	 * IDs already present in $context['exclude_ids'] (e.g. cart/order items).
	 *
	 * @param mixed $ids     Raw IDs (array or any).
	 * @param array $context Page context (exclude_ids, …).
	 * @param int   $limit   Max items.
	 * @return int[]
	 */
	private function sanitize_ids_excluding( $ids, $context, $limit ) {
		$ids = $this->sanitize_ids( $ids, PHP_INT_MAX );

		$exclude = isset( $context['exclude_ids'] ) && is_array( $context['exclude_ids'] )
			? array_map( 'absint', $context['exclude_ids'] )
			: array();

		if ( ! empty( $exclude ) ) {
			$ids = array_values( array_diff( $ids, $exclude ) );
		}

		return array_slice( $ids, 0, $limit );
	}
}
