<?php
/**
 * Safety checks applied to a RuleEngine-computed price (SPEC §5 "Guardrails").
 *
 * @package EmberPrice
 */

namespace EmberPrice\Pricing;

/**
 * Pure: runs after RuleEngine::apply() on the resulting regular_price.
 * Order matters -- floor first (protects against a price below the
 * supplier's own list price, almost always a rule/parsing error), then the
 * up/down direction mode (protects against the wrong kind of change even
 * when the magnitude looks plausible), then the max-change check against
 * whatever price survives those two (a floor/mode clamp can itself be a
 * huge jump from the old price, and that must still be caught).
 */
final class Guardrails {

	/**
	 * SPEC §5 default: a change bigger than this (in either direction)
	 * forces the row to `review` even if the match was perfect.
	 */
	private const DEFAULT_MAX_CHANGE_PERCENT = 0.40;

	/**
	 * Validates/clamps a computed price.
	 *
	 * @param float      $new_regular_price RuleEngine's computed regular_price.
	 * @param float      $list_price        Supplier's list price for this row (the anti-error floor).
	 * @param float|null $old_regular_price The product's current regular_price, or null if it has none yet
	 *                                      (e.g. first sync) -- direction/max-change checks are skipped then.
	 * @param array      $config            `{ max_change_percent?: float, mode?: 'both'|'increase_only'|'decrease_only' }`.
	 *
	 * @return array{regular_price: float, flags: string[], force_review: bool}
	 */
	public function apply( float $new_regular_price, float $list_price, ?float $old_regular_price, array $config = array() ): array {
		$price = $new_regular_price;
		$flags = array();

		if ( $price < $list_price ) {
			$price   = $list_price;
			$flags[] = 'price_below_list_floor';
		}

		$mode = (string) ( $config['mode'] ?? 'both' );

		if ( null !== $old_regular_price ) {
			if ( 'increase_only' === $mode && $price < $old_regular_price ) {
				$price   = $old_regular_price;
				$flags[] = 'price_direction_blocked';
			} elseif ( 'decrease_only' === $mode && $price > $old_regular_price ) {
				$price   = $old_regular_price;
				$flags[] = 'price_direction_blocked';
			}
		}

		$force_review = false;

		if ( null !== $old_regular_price && $old_regular_price > 0.0 ) {
			$change_ratio = abs( ( $price - $old_regular_price ) / $old_regular_price );
			$max_change   = (float) ( $config['max_change_percent'] ?? self::DEFAULT_MAX_CHANGE_PERCENT );

			if ( $change_ratio > $max_change ) {
				$flags[]      = 'max_change_exceeded';
				$force_review = true;
			}
		}

		return array(
			'regular_price' => $price,
			'flags'         => $flags,
			'force_review'  => $force_review,
		);
	}
}
