<?php
/**
 * Rental price: overrides displayed price, subtotal, and actual line-item
 * total in the cart and checkout for rental products.
 *
 * @package WooEquipmentRental
 */

declare( strict_types=1 );

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

/**
 * Class WCRent_Rental_Price
 */
class WCRent_Rental_Price {

	// -----------------------------------------------------------------------
	// Initialisation
	// -----------------------------------------------------------------------

	/**
	 * Registers all hooks.
	 */
	public static function init(): void {
		add_filter(
			'woocommerce_cart_item_price',
			array( self::class, 'filter_cart_item_price' ),
			10,
			3
		);

		add_filter(
			'woocommerce_cart_item_subtotal',
			array( self::class, 'filter_cart_item_subtotal' ),
			10,
			3
		);

		add_action(
			'woocommerce_before_calculate_totals',
			array( self::class, 'set_rental_item_prices' ),
			20,
			1
		);
	}

	// -----------------------------------------------------------------------
	// Filters
	// -----------------------------------------------------------------------

	/**
	 * Overrides the displayed price for a rental cart item.
	 *
	 * Shows "price × days" rather than the bare product price.
	 *
	 * @param string               $price         HTML-formatted price.
	 * @param array<string, mixed> $cart_item     Cart item data.
	 * @param string               $cart_item_key Cart item hash.
	 * @return string
	 */
	public static function filter_cart_item_price(
		string $price,
		array $cart_item,
		string $cart_item_key
	): string {
		if ( ! self::is_rental_cart_item( $cart_item ) ) {
			return $price;
		}

		$days    = (int) $cart_item['rental_days'];
		$product = $cart_item['data'];
		$total   = self::get_base_rental_price( $product, $cart_item, $days );

		if ( $total <= 0 ) {
			return $price;
		}

		$per_day = $days > 0 ? $total / $days : $total;

		return wc_price( $total )
			. '<br><small class="wcrent-per-day">'
			. sprintf(
				esc_html(
					/* translators: 1: formatted price, 2: number of days */
					_n(
						'%1$s × %2$d day',
						'%1$s × %2$d days',
						$days,
						'harbor-equipment-rental-for-woocommerce'
					)
				),
				wc_price( $per_day ),
				$days
			)
			. '</small>';
	}

	/**
	 * Overrides the subtotal for a rental cart item.
	 *
	 * @param string               $subtotal      HTML-formatted subtotal.
	 * @param array<string, mixed> $cart_item     Cart item data.
	 * @param string               $cart_item_key Cart item hash.
	 * @return string
	 */
	public static function filter_cart_item_subtotal(
		string $subtotal,
		array $cart_item,
		string $cart_item_key
	): string {
		if ( ! self::is_rental_cart_item( $cart_item ) ) {
			return $subtotal;
		}

		$days    = (int) $cart_item['rental_days'];
		$qty     = (int) $cart_item['quantity'];
		$product = $cart_item['data'];
		$total   = self::get_base_rental_price( $product, $cart_item, $days );

		if ( $total <= 0 ) {
			return $subtotal;
		}

		return wc_price( $total * $qty );
	}

	// -----------------------------------------------------------------------
	// Price mutation for totals
	// -----------------------------------------------------------------------

	/**
	 * Sets the internal WooCommerce price on each rental cart item so that
	 * order totals, tax calculations, and payment gateways all use the
	 * correct rental total rather than the base product price.
	 *
	 * Clones the product object before mutating it to avoid poisoning the
	 * shared WC product cache — multiple cart items for the same product
	 * would otherwise all end up priced at the last-processed item's rate.
	 *
	 * Runs on `woocommerce_before_calculate_totals`.
	 *
	 * @param WC_Cart $cart The WooCommerce cart object.
	 */
	public static function set_rental_item_prices( WC_Cart $cart ): void {
		if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
			return;
		}

		foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
			if ( ! self::is_rental_cart_item( $cart_item ) ) {
				continue;
			}

			$days    = (int) $cart_item['rental_days'];
			$product = $cart_item['data'];
			$total   = self::get_base_rental_price( $product, $cart_item, $days );

			if ( $total <= 0 ) {
				continue;
			}

			// Clone the product so we don't mutate the shared WC object cache —
			// without this, every cart item for the same product ID would be
			// overwritten with the last iteration's price.
			$product = clone $product;
			$product->set_price( $total );
			$cart->cart_contents[ $cart_item_key ]['data'] = $product;
		}
	}

	// -----------------------------------------------------------------------
	// Helpers
	// -----------------------------------------------------------------------

	/**
	 * Returns true when a cart item carries rental date data.
	 *
	 * @param array<string, mixed> $cart_item Cart item data.
	 * @return bool
	 */
	private static function is_rental_cart_item( array $cart_item ): bool {
		return ! empty( $cart_item['rental_days'] ) && ! empty( $cart_item['rental_start'] );
	}

	/**
	 * Returns the total rental price for the given number of days.
	 *
	 * Implements tiered pricing: if a weekly rate is configured and the rental
	 * spans 7+ days, full weeks are billed at the weekly rate and remaining
	 * days at the daily rate.
	 *
	 * If no daily rate meta key exists on the product, falls through to the
	 * WooCommerce product base price (treated as the total, not a daily rate).
	 *
	 * @param WC_Product           $product     Product object from the cart item.
	 * @param array<string, mixed> $cart_item   Cart item data.
	 * @param int                  $rental_days Number of rental days.
	 * @return float Total rental price (not per-day).
	 */
	private static function get_base_rental_price(
		WC_Product $product,
		array $cart_item,
		int $rental_days
	): float {
		$product_id = (int) $product->get_id();

		// Hourly pricing: applies when rental_hours is set on the cart item (same-day hourly booking).
		if ( ! empty( $cart_item['rental_hours'] ) ) {
			$rental_hours = (float) $cart_item['rental_hours'];
			$price_hour   = (float) get_post_meta( $product_id, '_rental_price_hour', true );
			if ( $rental_hours > 0 && $price_hour > 0 ) {
				return round( $rental_hours * $price_hour, 2 );
			}
		}

		// Use -1.0 as sentinel so we can distinguish "meta not set" from "meta = 0".
		$meta       = get_post_meta( $product_id, '_rental_price_day', true );
		$price_day  = ( '' !== $meta ) ? (float) $meta : -1.0;
		$price_week = (float) get_post_meta( $product_id, '_rental_price_week', true );

		if ( $price_day >= 0 ) {
			// Daily rate configured: bill full weeks at the weekly rate when one
			// exists and the rental spans a week or more, remaining days daily.
			if ( $rental_days >= 7 && $price_week > 0 ) {
				$full_weeks     = (int) floor( $rental_days / 7 );
				$remaining_days = $rental_days % 7;
				return round( $full_weeks * $price_week + $remaining_days * $price_day, 2 );
			}

			return $price_day * $rental_days;
		}

		// Weekly-only product (no daily rate): bill full weeks at the weekly rate
		// and prorate any remaining days at one-seventh of the weekly rate.
		if ( $price_week > 0 ) {
			$full_weeks     = (int) floor( $rental_days / 7 );
			$remaining_days = $rental_days % 7;
			return round( $full_weeks * $price_week + $remaining_days * ( $price_week / 7 ), 2 );
		}

		return (float) $product->get_price();
	}
}
