<?php
/**
 * Applies a warranty's resolved price to its cart line.
 *
 * @package WarrantyCart
 */

namespace WarrantyCart\Frontend;

use WarrantyCart\Core\Pricing;
use WC_Cart;

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

/**
 * Makes a percentage-priced warranty cost what it should.
 *
 * WooCommerce prices a cart line from the product, so a warranty charging 10%
 * of what it covers would otherwise be sold at whatever fixed price its product
 * carries. The resolved figure is stored on the cart item when it is added; this
 * puts it on the line every time totals are worked out.
 */
class CartPricing {

	/**
	 * Constructor
	 */
	public function __construct() {
		\add_action( 'woocommerce_before_calculate_totals', array( $this, 'apply_prices' ), 20 );

		/*
		 * Persist across page loads.
		 *
		 * WooCommerce serialises cart item data into the session, but restores
		 * it through this filter — without it the resolved price survives the
		 * request it was set in and is gone by the next one, so the line
		 * silently reverts to the product's own price somewhere between the
		 * product page and the checkout.
		 */
		\add_filter( 'woocommerce_get_cart_item_from_session', array( $this, 'restore_price' ), 20, 2 );
	}

	/**
	 * Put the stored price on every warranty line.
	 *
	 * @param WC_Cart $cart The cart.
	 *
	 * @return void
	 */
	public function apply_prices( $cart ): void {
		if ( ! $cart instanceof WC_Cart ) {
			return;
		}

		/*
		 * `did_action()` rather than an instance flag.
		 *
		 * WooCommerce calls `calculate_totals()` more than once per request, and
		 * on the second pass the line price has already been set — harmless
		 * here, since the value comes from the item rather than from the current
		 * price, but the guard keeps the intent explicit.
		 */
		foreach ( $cart->get_cart() as $item ) {
			if ( ! isset( $item[ Pricing::CART_ITEM_KEY ] ) || ! isset( $item['data'] ) ) {
				continue;
			}

			$price = (float) $item[ Pricing::CART_ITEM_KEY ];

			/*
			 * Zero is a real price and is left alone; a negative one is not, and
			 * would turn a warranty into a discount on the rest of the order.
			 */
			if ( $price < 0 ) {
				continue;
			}

			$item['data']->set_price( $price );
		}
	}

	/**
	 * Carry the resolved price back out of the session.
	 *
	 * @param array $item    The cart item being restored.
	 * @param array $values  The serialised values.
	 *
	 * @return array
	 */
	public function restore_price( $item, $values ): array {
		if ( is_array( $values ) && isset( $values[ Pricing::CART_ITEM_KEY ] ) ) {
			$item[ Pricing::CART_ITEM_KEY ] = $values[ Pricing::CART_ITEM_KEY ];
		}

		return is_array( $item ) ? $item : array();
	}
}
