<?php
/**
 * BitForgeNet Guest Tickets - Where a pricing module plugs in.
 *
 * The plugin knows there may be reasons a guest pays less than the list price.
 * It does not know what those reasons are, how they are configured, or whether
 * anything of the sort is installed at all — and that is the point. Everything
 * here forwards to a seam and answers sensibly when nobody is listening, so a
 * build without a pricing module is a build where a discount simply is not a
 * thing rather than one where the button is greyed out.
 *
 * @package BitForgeNet_Guest_Tickets
 * @since   1.8.2
 */

defined( 'ABSPATH' ) || exit;

/**
 * The seam between the checkout and whatever prices it.
 */
class BFNET_Offers {

	/**
	 * Try a code against a basket.
	 *
	 * Three answers, and callers must tell them apart:
	 *
	 * - `null`     no module is listening, so codes are not a thing here
	 * - `WP_Error` a module looked at it and refused it, with a reason
	 * - `array`    applied: amount_cents, label, and whatever the module needs
	 *              handing back to {@see self::used()}
	 *
	 * @param int    $event_id Event.
	 * @param string $code     What the guest typed.
	 * @param int    $subtotal Basket total in cents, before any reduction.
	 * @param int    $quantity How many tickets.
	 * @return null|array|WP_Error
	 */
	public static function apply( $event_id, $code, $subtotal, $quantity ) {
		return apply_filters( 'bfnet_apply_discount', null, (int) $event_id, (string) $code, (int) $subtotal, (int) $quantity );
	}

	/**
	 * The booking went through, so the code has now been spent.
	 *
	 * @param array $applied Whatever apply() returned.
	 * @param int   $order_id The booking it was spent on.
	 * @return void
	 */
	public static function used( $applied, $order_id ) {
		do_action( 'bfnet_discount_used', $applied, (int) $order_id );
	}

	/**
	 * Whether this event has anything to redeem, which is what decides if the
	 * booking form shows a box to type a code into. Asking for a code that
	 * cannot exist is a field that only ever disappoints.
	 *
	 * @param int $event_id Event.
	 * @return bool
	 */
	public static function offered( $event_id ) {
		return (bool) apply_filters( 'bfnet_offers_discounts', false, (int) $event_id );
	}

	/**
	 * A code as it is written down: upper case, no stray spaces.
	 *
	 * Kept in core rather than in the module, because it is what the checkout
	 * echoes back to a guest and that must read the same either way.
	 *
	 * @param string $code Raw input.
	 * @return string
	 */
	public static function normalise( $code ) {
		return strtoupper( preg_replace( '/\s+/', '', (string) $code ) );
	}

	/* --------------------------------------------------------------------- */
	/* Tiers                                                                  */
	/* --------------------------------------------------------------------- */

	/**
	 * What one ticket costs.
	 *
	 * An event has a price. A module may say that some tickets on it cost
	 * something else — VIP, a table, an early bird. With nothing listening, the
	 * event's own price is the only answer there is, which is the right answer
	 * and not a degraded one.
	 *
	 * @param object|null       $event The event.
	 * @param object|int|null   $tier  A tier, its id, or nothing.
	 * @return int Cents.
	 */
	public static function price( $event, $tier = null ) {
		$base = $event ? (int) $event->price_cents : 0;

		return (int) apply_filters( 'bfnet_ticket_price', $base, $event, $tier );
	}

	/**
	 * The tiers this event offers, in the order they should be shown.
	 *
	 * Empty when nothing tiers it — and an empty list is what makes the booking
	 * form show a price rather than a menu.
	 *
	 * @param int  $event_id Event.
	 * @param bool $selling  Only tiers a guest may still choose.
	 * @return array
	 */
	public static function tiers( $event_id, $selling = true ) {
		return (array) apply_filters( 'bfnet_ticket_tiers', array(), (int) $event_id, (bool) $selling );
	}

	/**
	 * One tier, for the label on a ticket or a booking.
	 *
	 * @param int $tier_id Tier id.
	 * @return object|null
	 */
	public static function tier( $tier_id ) {
		if ( ! $tier_id ) {
			return null;
		}

		return apply_filters( 'bfnet_ticket_tier', null, (int) $tier_id );
	}

	/**
	 * Places left in a tier, or null when the question does not apply.
	 *
	 * @param object $tier The tier.
	 * @return int|null
	 */
	public static function seats_left( $tier ) {
		return apply_filters( 'bfnet_tier_seats_left', null, $tier );
	}
}
