<?php
namespace Mewz\WCAS\Util;

use Mewz\Framework\Util\WooCommerce;
use Mewz\WCAS\Aspects\Front\ProductLimits;
use Mewz\WCAS\Models\AttributeStock;

class Limits
{
	/**
	 * Checks if product limits are loaded and active.
	 *
	 * @return bool
	 */
	public static function product_limits_active()
	{
		static $loaded = false;

		if (!$loaded) {
			$loaded = class_exists(ProductLimits::class, false);
		}

		return $loaded && ProductLimits::$enabled;
	}

	/**
	 * Retrieves a list of matching attribute stock quantity data for a product or variation,
	 * sorted from lowest to highest stock.
	 *
	 * @param \WC_Product|int $product Product object or ID
	 * @param array $variation Selected variation key/value pairs
	 *
	 * @return array|false
	 */
	public static function get_stock_limits($product, $variation = null)
	{
		if (!$product instanceof \WC_Product && !$product = wc_get_product($product)) {
			return false;
		}

		if ($variation !== null && !is_array($variation)) {
			$variation = [];
		}

		if ($variation === null && $product_variation = Products::get_prop($product, 'variation')) {
			$variation = $product_variation;
		}

		$cache_key = 'product_stock_limits';
		$cache_tags = ['stock', 'match_rules', 'multipliers', 'product_' . $product->get_id()];

		if ($variation) {
			ksort($variation);
			$cache_key .= '_' . md5(json_encode($variation));
		}

		$stock_limits = Mewz_WCAS()->cache->get($cache_key, $cache_tags);

		if (!is_array($stock_limits)) {
			$stock_limits = [];
			$attributes = Products::get_product_attributes($product, $variation);
			$matches = Matches::match_product_stock($product, $attributes);

			if ($matches) {
				foreach ($matches as $match) {
					$stock = AttributeStock::instance($match['stock_id']);

					if (!$stock->internal()) {
						$match['limit_qty'] = Matches::calc_limit_qty($match['stock_qty'], $match['multiplier']);

						$stock_limits[$match['stock_id']] = $match;
					}
				}

				if ($stock_limits) {
					$comp_tree = Components::get_sorted_tree($stock_limits);

					if ($comp_tree instanceof \WP_Error) {
						trigger_error($comp_tree->get_error_message(), E_USER_WARNING);
					} elseif ($comp_tree) {
						$stock_limits = $comp_tree;
					} else {
						self::sort_by_qty($stock_limits);
					}
				}
			} elseif (
				$variation
				&& $product instanceof \WC_Product_Variation
				&& Settings::unmatched_any_variations(true) !== 'no'
				&& Attributes::has_catchall($product->get_attributes())
			) {
				$stock_limits[0] = [
					'stock_id' => 0,
					'multiplier' => 1,
					'stock_qty' => 0,
					'limit_qty' => 0,
				];
			}

			/**
			 * IMPORTANT: The results from this filter are cached for performance reasons.
			 * This means the input ($product, $variation) should always have the same output,
			 * or the cache must be invalidated accordingly.
			 */
			$stock_limits = apply_filters('mewz_wcas_product_stock_limits', $stock_limits, $product, $attributes, $variation, $matches);

			Mewz_WCAS()->cache->set($cache_key, $stock_limits, $cache_tags);
		}

		$stock_limits = apply_filters('mewz_wcas_product_stock_limits_uncached', $stock_limits, $product, $variation);

		return $stock_limits;
	}

	public static function sort_by_qty(&$stock_limits)
	{
		static $callback;

		if (count($stock_limits) <= 1) {
			return;
		}

		$callback ??= fn($a, $b) => $a['limit_qty'] <=> $b['limit_qty'];

	    uasort($stock_limits, $callback);
	}

	/**
	 * Retrieves a product's effective available stock based on matching attribute stock.
	 *
	 * @param \WC_Product|int $product Product object or ID
	 * @param array $variation Selected variation key/value pairs
	 *
	 * @return array|false
	 */
	public static function get_stock_limit($product, $variation = null)
	{
		if (!$variation) {
			$stock_limit = Products::get_prop($product, 'stock_limit');
			if ($stock_limit !== null) return $stock_limit;
		}

		$stock_limits = self::get_stock_limits($product, $variation);
		$limit_qty = self::calc_limit_quantity($stock_limits, Products::get_prop($product, 'any_stock_limit'));

		if ($limit_qty !== false) {
			$stock_limit = [
				'limit_qty' => $limit_qty,
				'image_id' => self::get_image_id($stock_limits),

				// @deprecated 2.0.0 - DO NOT USE
				'stock_id' => (int)key($stock_limits),
			];
		} else {
			$stock_limit = false;
		}

		$stock_limit = apply_filters('mewz_wcas_product_stock_limit', $stock_limit, $stock_limits, $product, $variation);

		// cache stock limit on product object (any changes should use a new object or delete the cache)
		if (!$variation) {
			Products::set_prop($product, 'stock_limit', $stock_limit);
		}

		return $stock_limit;
	}

	/**
	 * Calculates the effective stock limit quantity from a list of product stock limits.
	 *
	 * @see Limits::get_stock_limits()
	 *
	 * @param array|false $stock_limits Pre-sorted list of product stock limits
	 * @param bool $any_stock Return any positive limit quantity
	 *
	 * @return array|false
	 */
	public static function calc_limit_quantity($stock_limits, $any_stock = false)
	{
		if (!$stock_limits) {
			$quantity = false;
		} elseif (count($stock_limits) === 1) {
			$quantity = current($stock_limits)['limit_qty'];
		} elseif ($any_stock) {
			$quantity = end($stock_limits)['limit_qty'];
		} elseif (Components::is_sorted_tree($stock_limits)) {
			$quantity = Components::calc_limit_quantity($stock_limits);
		} else {
			$quantity = current($stock_limits)['limit_qty'];
		}

		$quantity = apply_filters('mewz_wcas_calc_stock_limit', $quantity, $stock_limits, $any_stock);

		return $quantity;
	}

	/**
	 * Gets an image ID from matching stock limits to use for a product.
	 *
	 * @see Limits::get_stock_limits()
	 *
	 * @param array|false $stock_limits List of product stock limits
	 *
	 * @return int|null
	 */
	public static function get_image_id($stock_limits)
	{
		$image_id = apply_filters('mewz_wcas_product_stock_image_id', null, $stock_limits);

		if ($image_id !== null) {
			return $image_id;
		}

		foreach ($stock_limits as $stock_id => $limit) {
			if (isset($limit['comp']['use']) && $limit['comp']['use'] <= 0) {
				continue;
			}

			$stock = AttributeStock::instance($stock_id);

			if ($stock->product_image() && $image_id = $stock->image_id()) {
				return $image_id;
			}
		}

		return null;
	}

	/**
	 * Gets all 9 variants of stock html messages for a (variable) product.
	 *
	 * @param \WC_Product $product
	 *
	 * @return array
	 */
	public static function get_variable_stock_html(\WC_Product $product)
	{
		// get a new disposable product object so its props can be set freely
		$product = wc_get_product($product->get_id());

		Products::incr_prop($product, 'bypass_limits');
		Products::set_prop($product, 'ignore_csr', true);

		$stock_html = [
			'in' => [
				'no' => self::get_stock_html($product, 'instock', 'no'),
				'yes' => self::get_stock_html($product, 'instock', 'yes'),
				'notify' => self::get_stock_html($product, 'instock', 'notify'),
			],
			'low' => [
				'no' => self::get_stock_html($product, 'lowstock', 'no'),
				'yes' => self::get_stock_html($product, 'lowstock', 'yes'),
				'notify' => self::get_stock_html($product, 'lowstock', 'notify'),
			],
			'out' => [
				'no' => self::get_stock_html($product, 'nostock', 'no'),
				'yes' => self::get_stock_html($product, 'nostock', 'yes'),
				'notify' => self::get_stock_html($product, 'nostock', 'notify'),
			],
		];

		Products::decr_prop($product, 'bypass_limits');

		return $stock_html;
	}

	/**
	 * Builds a template string for a particular product stock html message.
	 *
	 * WARNING: This sets props on the product object. Only pass in disposable objects!
	 *
	 * @param \WC_Product $product
	 * @param string $status 'instock', 'lowstock' or 'nostock'
	 * @param string $backorders 'yes', 'no', or 'notify'
	 *
	 * @return string
	 */
	public static function get_stock_html(\WC_Product $product, $status = 'instock', $backorders = 'no')
	{
		if ($status === 'instock') {
			$quantity = 999999999;
		} elseif ($status === 'lowstock') {
			$quantity = WooCommerce::low_stock_amount();
		} elseif ($status === 'nostock') {
			$quantity = 0;
		} else {
			return false;
		}

		$product->set_manage_stock(true);
		$product->set_stock_quantity($quantity);
		$product->set_backorders($backorders);
		$product->validate_props(); // sets 'stock_status'

		$display_qty = wc_format_stock_quantity_for_display($product->get_stock_quantity(), $product);

		$html = wc_get_stock_html($product);
		$html = str_replace($display_qty, '%s', $html);

		return $html;
	}
}
