<?php
/**
 * Accommodation Product Class.
 *
 * The accommodation product type.
 *
 * @package HBFWC
 */

defined( 'ABSPATH' ) || exit;

use HBFWC\Utils\DB;
use HBFWC\Utils\Constants;

if ( class_exists( 'HBFWC_WC_Product_Accommodation' ) ) {
	return;
}

/**
 * Accommodation product class.
 */
class HBFWC_WC_Product_Accommodation extends WC_Product {
	/**
	 * Database.
	 *
	 * @var object
	 */
	private $hbfwc_db;

	/**
	 * Initialize accommodation product.
	 *
	 * @param WC_Product|int $product Product instance or ID.
	 */
	public function __construct( $product = 0 ) {
		parent::__construct( $product );
		$this->hbfwc_db = new DB();
	}

	/**
	 * Get internal type.
	 *
	 * @return string
	 */
	public function get_type() {
		return 'accommodation';
	}

	/**
	 * This type dont need any shipping.
	 *
	 * @return bool
	 */
	public function needs_shipping() {
		return false;
	}

	/**
	 * Return if product manage stock.
	 *
	 * @param  string $context What the value is for. Valid values are view and edit.
	 * @return boolean
	 */
	public function get_manage_stock( $context = 'view' ) {
		return false;
	}

	/**
	 * Return the stock status.
	 *
	 * @param  string $context What the value is for. Valid values are view and edit.
	 * @return string
	 */
	public function get_stock_status( $context = 'view' ) {
		return 'instock';
	}

	/**
	 * Get the minimum stay.
	 *
	 * @return int
	 */
	public function get_minimum_stays() {
		_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '1.0.30', __CLASS__ . '::get_minimum_stay' );
		return $this->get_minimum_stay();
	}

	/**
	 * Get the minimum stay.
	 * @version 1.0.30
	 * @return int
	 */
	public function get_minimum_stay() {
		$legacy_stay = ! empty( $this->get_meta( '_min_stays' ) ) ? absint( $this->get_meta( '_min_stays' ) ) : 1;
		return ! empty( $this->get_meta( '_min_stay' ) ) ? absint( $this->get_meta( '_min_stay' ) ) : $legacy_stay;
	}

	/**
	 * Get the minimum stay for rate.
	 *
	 * @return int
	 */
	public function get_minimum_stay_for_rate( $rate_id ) {
		$default_min_stay = $this->get_minimum_stay();
		return apply_filters( 'hbfwc_get_minimum_stay_for_rate', $default_min_stay, $this, $rate_id );
	}

	/**
	 * Get max adults.
	 *
	 * @return mixed
	 */
	public function get_max_adults() {
		return ! empty( $this->get_meta( '_max_adults' ) ) ? absint( $this->get_meta( '_max_adults' ) ) : false;
	}

	/**
	 * Get max children.
	 *
	 * @return mixed
	 */
	public function get_max_children() {
		return ! empty( $this->get_meta( '_max_children' ) ) ? absint( $this->get_meta( '_max_children' ) ) : false;
	}

	/**
	 * Get the add to cart button text for the single page.
	 *
	 * @return string
	 */
	public function accommodation_add_to_cart_text() {
		return apply_filters( 'woocommerce_product_accommodation_add_to_cart_text', hbfwc_get_addtocart_button_label(), $this );
	}

	/**
	 * Get price html.
	 *
	 * @param string $price The default price.
	 *
	 * @return string
	 */
	public function get_price_html( $price = '' ) {
		$prices = $this->get_accommodation_prices();

		if ( empty( $prices['price'] ) ) {
			$price = apply_filters( 'woocommerce_accommodation_empty_price_html', '', $this );
		} else {
			$min_price = current( $prices['price'] );

			$from  = '<span class="hbfwc_bfr_price">' . __( 'From:', 'hotel-booking-for-woocommerce' ) . '</span>';
			$price = $from . ' ' . wc_price( $min_price );

			$price = apply_filters( 'woocommerce_accommodation_price_html', $price . $this->get_price_suffix(), $this );
		}

		return apply_filters( 'woocommerce_get_price_html', $price, $this );
	}

	/**
	 * Get WC price.
	 *
	 * @return string
	 */
	public function get_wc_price() {
		$def_price = $this->is_on_sale()
					? wc_format_sale_price( wc_price( $this->get_regular_price() ), wc_price( $this->get_sale_price() ) ) 
					: wc_price( $this->get_price() );

		return $def_price;
	}

	/**
	 * Get accommodation prices.
	 *
	 * @return array
	 */
	public function get_accommodation_prices() {
		$default_rate = hbfwc_get_default_rate();
		$custom_rates = $this->get_meta( '_hbfwc_rate_plans' );

		$prices = array();
		if ( $default_rate ) {
			$prices = array(
				'price'         => $this->is_on_sale() ? array( $this->get_sale_price() ) : array( $this->get_regular_price() ),
				'regular_price' => array( $this->get_regular_price() ),
				'sale_price'    => array( $this->get_sale_price() ),
			);
		}

		if ( ! empty( $custom_rates ) ) {
			foreach ( $custom_rates as $rate_db_id => $rate ) {
				$rate_db_id = str_replace( 'rate_', '', $rate_db_id );
				$rate_term  = get_term_by( 'term_id', absint( $rate_db_id ), 'rate_plan' );

				if ( ! $rate_term ) {
					continue;
				}

				switch ( $rate['pricing_method'] ) {
					case 'increase':
						if ( 'percent' === $rate['pricing_unit'] ) {
							$multiplier = bcdiv( 
								bcadd( 
									Constants::PERCENT_BASE, 
									wc_format_decimal($rate['pricing_value'], Constants::MATH_SCALE),
									Constants::MATH_SCALE
								),
								Constants::PERCENT_BASE,
								Constants::MATH_SCALE
							);

							$std  = bcmul( $multiplier, wc_format_decimal($this->get_regular_price(), Constants::MATH_SCALE), Constants::MATH_SCALE );
							$reg  = $std;
							$sale = $std;

						} else {

							$std  = bcadd(
								wc_format_decimal($rate['pricing_value'], Constants::MATH_SCALE),
								wc_format_decimal($this->get_regular_price(), Constants::MATH_SCALE),
								Constants::MATH_SCALE
							);
							$reg  = $std;
							$sale = $std;
						}

						break;

					case 'decrease':
						if ( 'percent' === $rate['pricing_unit'] ) {
							$multiplier = bcdiv( 
								bcsub( 
									Constants::PERCENT_BASE,
									wc_format_decimal($rate['pricing_value'], Constants::MATH_SCALE),
									Constants::MATH_SCALE
								),
								Constants::PERCENT_BASE,
								Constants::MATH_SCALE
							);

							$std  = bcmul($multiplier, wc_format_decimal($this->get_regular_price(), Constants::MATH_SCALE), Constants::MATH_SCALE);
							$reg  = $std;
							$sale = $std;

						} else {

							$std  = bcsub(wc_format_decimal($this->get_regular_price(), Constants::MATH_SCALE), wc_format_decimal($rate['pricing_value'], Constants::MATH_SCALE), Constants::MATH_SCALE);
							$reg  = $std;
							$sale = $std;

						}

						break;

					case 'fixed':
					default:
							$std  = wc_format_decimal( $rate['pricing_value'], Constants::MATH_SCALE );
							$reg  = $std;
							$sale = $std;
						break;
				}

				if ( 'none' !== $rate['pricing_sale_method'] ) {
					if ( 'decrease' === $rate['pricing_sale_method'] ) {
						if ( 'percent' === $rate['pricing_sale_unit'] ) {
							$sale_multiplier = bcdiv(
								bcsub(
									Constants::PERCENT_BASE,
									wc_format_decimal($rate['pricing_sale_value'], Constants::MATH_SCALE),
									Constants::MATH_SCALE
								),
								Constants::PERCENT_BASE,
								Constants::MATH_SCALE
							);
							$sale = bcmul( $sale_multiplier, $reg, Constants::MATH_SCALE );
						} else {
							$sale = bcsub( $reg, wc_format_decimal($rate['pricing_sale_value'], Constants::MATH_SCALE), Constants::MATH_SCALE );
						}
					} else {
						$sale = wc_format_decimal($rate['pricing_sale_value'], Constants::MATH_SCALE);
					}
					$std = $sale;
				}

				$prices['price'][]         = $std;
				$prices['regular_price'][] = $reg;
				$prices['sale_price'][]    = $sale;
			}
		}

		foreach ( $prices as $price_key => $accommodation_prices ) {
			$prices[ $price_key ] = $this->sort_accommodation_prices( $accommodation_prices );
		}

		return $prices;
	}

	/**
	 * Get the rate default price.
	 *
	 * @param int  $rate_id The rate id.
	 * @param bool $default_rate Should we get the default rate price.
	 * @param bool $formatted Should we format the price.
	 *
	 * @return string
	 */
	public function get_rate_default_price( $rate_id, $default_rate = false, $formatted = true ) {
		if ( $default_rate ) {
			$price = $this->is_on_sale()
					? $this->get_sale_price()
					: $this->get_price();

			return $formatted ? wc_price( $price ) : $price;
			
		} else {
			$custom_rates = $this->get_meta( '_hbfwc_rate_plans' );

			$rate_price = $formatted ? wc_price( 0 ) . $this->get_price_suffix() : '0';
			if ( ! empty( $custom_rates ) ) {
				foreach ( $custom_rates as $rate_db_id => $rate ) {
					$rate_db_id = str_replace( 'rate_', '', $rate_db_id );
					$rate_term  = get_term_by( 'term_id', absint( $rate_db_id ), 'rate_plan' );

					if ( absint( $rate_id ) !== absint( $rate_db_id ) ) {
						continue;
					}

					switch ( $rate['pricing_method'] ) {
						case 'increase':
							if ( 'percent' === $rate['pricing_unit'] ) {
								$multiplier = bcdiv( 
									bcadd( 
										Constants::PERCENT_BASE, 
										wc_format_decimal($rate['pricing_value'], Constants::MATH_SCALE),
										Constants::MATH_SCALE
									),
									Constants::PERCENT_BASE,
									Constants::MATH_SCALE
								);

								$reg_price  = bcmul( $multiplier, wc_format_decimal($this->get_regular_price(), Constants::MATH_SCALE), Constants::MATH_SCALE );
								$rate_price = $formatted ? wc_price( $reg_price ) . $this->get_price_suffix() : $reg_price;

							} else {

								$reg_price  = bcadd(
									wc_format_decimal( $rate['pricing_value'], Constants::MATH_SCALE ),
									wc_format_decimal( $this->get_regular_price(), Constants::MATH_SCALE ),
									Constants::MATH_SCALE
								);
								$rate_price = $formatted ? wc_price( $reg_price ) . $this->get_price_suffix() : $reg_price;

							}

							break;

						case 'decrease':
							if ( 'percent' === $rate['pricing_unit'] ) {
								$multiplier = bcdiv( 
									bcsub( 
										Constants::PERCENT_BASE,
										wc_format_decimal($rate['pricing_value'], Constants::MATH_SCALE),
										Constants::MATH_SCALE
									),
									Constants::PERCENT_BASE,
									Constants::MATH_SCALE
								);

								$reg_price  = bcmul( $multiplier, wc_format_decimal($this->get_regular_price(), Constants::MATH_SCALE), Constants::MATH_SCALE );
								$rate_price = $formatted ? wc_price( $reg_price ) . $this->get_price_suffix() : $reg_price;

							} else {

								$reg_price  = bcsub(
									wc_format_decimal($this->get_regular_price(), Constants::MATH_SCALE),
									wc_format_decimal( $rate['pricing_value'], Constants::MATH_SCALE ),
									Constants::MATH_SCALE
								);
								$rate_price = $formatted ? wc_price( $reg_price ) . $this->get_price_suffix() : $reg_price;
							}

							break;

						case 'fixed':
						default:
							$reg_price  = wc_format_decimal( $rate['pricing_value'], Constants::MATH_SCALE );
							$rate_price = $formatted ? wc_price( $reg_price ) . $this->get_price_suffix() : $reg_price;
							break;
					}

					if ( 'none' !== $rate['pricing_sale_method'] ) {
						if ( 'decrease' === $rate['pricing_sale_method'] ) {
							if ( 'percent' === $rate['pricing_sale_unit'] ) {
								$sale_multiplier = bcdiv( 
									bcsub( 
										Constants::PERCENT_BASE,
										wc_format_decimal($rate['pricing_sale_value'], Constants::MATH_SCALE),
										Constants::MATH_SCALE
									),
									Constants::PERCENT_BASE,
									Constants::MATH_SCALE
								);

								$sale = bcmul( $sale_multiplier, $reg_price, Constants::MATH_SCALE );
							} else {
								$sale = bcsub( $reg_price, wc_format_decimal( $rate['pricing_sale_value'], Constants::MATH_SCALE ), Constants::MATH_SCALE );
							}
						} else {
							$sale = wc_format_decimal( $rate['pricing_sale_value'], Constants::MATH_SCALE );
						}

						$rate_price = $formatted ? wc_price( $sale ) . $this->get_price_suffix() : $sale;
					}
				}
			}

			return $rate_price;
		}
	}

	/**
	 * Get rate price by date.
	 *
	 * @param int    $rate_id The rate id.
	 * @param string $date The date.
	 * @param bool   $default_rate Is it the default rate.
	 *
	 * @return string
	 */
	public function get_rate_price( $rate_id, $date, $default_rate = false ) {
		$price = $this->get_rate_default_price( $rate_id, $default_rate, false );

		if ( ! hbfwc_is_pro() && $default_rate ) {
			$rate_data = $this->get_default_rate_data_by_date( $date );
			$raw_price = $rate_data['price'] ?? $price;
			
			$price = $raw_price;
		}

		return apply_filters( 'woocommerce_hbfwc_get_rate_price', $price, $rate_id, $date, $default_rate, $this );
	}

	/**
	 * Get rate status by date.
	 *
	 * @param int    $rate_id The rate id.
	 * @param string $date The date.
	 * @param bool   $default_rate Is it the default rate.
	 *
	 * @return bool
	 */
	public function get_rate_status( $rate_id, $date, $default_rate = false ) {
		$status = $this->is_active_rate( $rate_id );

		if ( ! hbfwc_is_pro() && $default_rate ) {
			$rate_data = $this->get_default_rate_data_by_date( $date );
			$raw_status = absint( $rate_data['status'] ?? 1 );

			$status = $raw_status === 1;
		}

		return apply_filters( 'woocommerce_hbfwc_get_rate_status', $status, $rate_id, $date, $default_rate, $this );
	}

	private function get_default_rate_data_by_date( $date ) {
		$rate_data = $this->get_meta( '_default_rate_prices' );
		
		if ( ! is_array($rate_data) )
			$rate_data = [];

		$date_key = trim( str_replace('-', '', $date) );
		return $rate_data[$date_key] ?? [];
	}

	/**
	 * Get base price breakdown.
	 *
	 * @param int       $rate_id The rate id.
	 * @param timestamp $checkin The check in date.
	 * @param timestamp $checkout The check out date.
	 * @param int       $qty The check out date.
	 * @param bool      $default_rate Is it the default rate.
	 * @param bool      $reg_sale_price Is regular has sale price.
	 *
	 * @return string
	 */
	public function get_price_breakdown_html( $rate_id, $checkin, $checkout, $qty, $default_rate = false, $reg_sale_price = false ) {
		// If any, let the filter handle the output.
		if ( has_filter( 'woocommerce_hbfwc_get_price_breakdown_html' ) ) {
			return apply_filters( 'woocommerce_hbfwc_get_price_breakdown_html', '', $this, $rate_id, $checkin, $checkout, $qty, $default_rate, $reg_sale_price );
		}

		$ci_date = new \DateTime( wp_date( 'Y-m-d', $checkin ) );
		$co_date = new \DateTime( wp_date( 'Y-m-d', $checkout ) );
		$stay    = intval( $co_date->diff( $ci_date )->format( '%a' ) );

		$html = '<ul class="bpbd-items">' . "\n";

		$total_price = '0';
		while ( $ci_date < $co_date ) {
			$date_str = $ci_date->format( 'd/m/Y' );

			$price     = $this->get_rate_price( $rate_id, $ci_date->format( 'Y-m-d' ), $default_rate );
			$price_str = wc_price( $price );

			$html .= "<li><span class=\"bpbd-item-date\">{$date_str}</span><span class=\"bpbd-item-price\">{$price_str}</span></li>";

			$total_price = bcadd( $total_price, $price, Constants::MATH_SCALE );

			$ci_date->modify( '+1 day' );
		}

		if ( absint( $qty ) > 1 ) {
			$total_price_str = wc_price( $total_price );

			$html .= '</ul>' . "\n";
			$html .= '<ul class="bpbd-items">' . "\n";
			$html .= '<li class="bpbd-rooms"><span class="bpbd-item-date">' . __( 'Rooms', 'hotel-booking-for-woocommerce' ) . "</span><span class=\"bpbd-item-price\">{$qty} &times; {$total_price_str}</span></li>";

			$total_price = bcmul( $total_price, $qty, Constants::MATH_SCALE );
		}

		$final_total_str = wc_price( $total_price );

		$html .= '</ul>' . "\n";
		$html .= '<ul class="bpbd-items">' . "\n";
		$html .= '<li class="bpbd-totals"><span class="bpbd-item-date">' . __( 'Total Base Price', 'hotel-booking-for-woocommerce' ) . "</span><span class=\"bpbd-item-price\">{$final_total_str}</span></li>";
		$html .= '</ul>' . "\n";

		// If sale price is active but the total actual price is higher than regular price due to ability to set higher/lower price per date.
		// We should skip the sale price display.
		$total_header_price = ( $reg_sale_price && $reg_sale_price > $total_price ) ? wc_format_sale_price( $reg_sale_price, $total_price ) : $final_total_str;

		$returned  = $total_header_price . $this->get_price_suffix();
		$returned .= '<a class="hbfwc-info-indicator" href="#">i</a>' . "\n";
		$returned .= '<div class="hbfwc-base-price-bd" id="bpbd-' . esc_attr( $this->get_id() ) . '-' . esc_attr( $rate_id ) . '">' . "\n";
		$returned .= '<div class="bpbd-header"><span>' . __( 'Base Price Breakdown', 'hotel-booking-for-woocommerce' ) . '</span><span>{{stay_string}}</span></div>' . "\n";
		$returned .= '<div class="bpbd-body">' . "\n";
		$returned .= $html;
		$returned .= '</div>' . "\n";
		$returned .= '</div>' . "\n";

		return $returned;
	}

	/**
	 * Get single rate price details and breakdowns.
	 *
	 * @param int    $rate_id The rate id.
	 * @param string $checkin The checkin date.
	 * @param string $checkout The checkout date.
	 *
	 * @return string
	 */
	public function get_rate_price_breakdowns( $rate_id, $checkin, $checkout ) {
		$default_rate_id = (int) get_option( 'hbfwc_default_rate_id', 0 );

		$ci_date = new \DateTime( wp_date( 'Y-m-d', $checkin ) );
		$co_date = new \DateTime( wp_date( 'Y-m-d', $checkout ) );
		$stay    = intval( $co_date->diff( $ci_date )->format( '%a' ) );

		$stay_string = sprintf(
			/* translators: Number of nights */
			_n( '%s night', '%s nights', $stay, 'hotel-booking-for-woocommerce' ),
			number_format_i18n( $stay )
		);

		$regular_price = $this->get_regular_price_for_rate( $rate_id, $checkin, $checkout );

		$is_default_rate     = (int) $rate_id === $default_rate_id;
		$break_down_for_rate = $this->get_price_breakdown_html( $rate_id, $checkin, $checkout, 1, $is_default_rate, $regular_price );
		$break_down_for_rate = str_replace( '{{stay_string}}', $stay_string, $break_down_for_rate );

		$html  = '<div class="hbfwc-avail-rate-prices">' . "\n";
		$html .= '<p class="rate-price__info">' . sprintf(
			/* translators: The number of stay */
			__( 'Total price for %s', 'hotel-booking-for-woocommerce' ),
			$stay_string
		) . '</p>' . "\n";
		$html .= $break_down_for_rate;
		$html .= '</div>' . "\n";

		return apply_filters( 'hbfwc_get_rate_price_breakdowns', $html, $this, $rate_id, $checkin, $checkout );
	}

	/**
	 * Get normal regular price for rate.
	 * As we honor the status of "sale" if it was set in a rate.
	 * We use this to calculate if the actual price is lower from normal regular price.
	 *
	 * @param int    $rate_id The rate id.
	 * @param string $checkin The checkin date.
	 * @param string $checkout The checkout date.
	 *
	 * @return mixed.
	 */
	public function get_regular_price_for_rate( $rate_id, $checkin, $checkout ) {
		$default_rate_id = (int) get_option( 'hbfwc_default_rate_id', 0 );

		$ci_date = new \DateTime( wp_date( 'Y-m-d', $checkin ) );
		$co_date = new \DateTime( wp_date( 'Y-m-d', $checkout ) );
		$stay    = intval( $co_date->diff( $ci_date )->format( '%a' ) );

		if ( (int) $rate_id === $default_rate_id ) {
			return $this->is_on_sale() ? $this->get_regular_price() * $stay : false;
		} else {
			$rate_data           = $this->get_custom_rates();
			$sale_reg_rate_price = false;

			if ( is_array( $rate_data ) && isset( $rate_data[ "rate_{$rate_id}" ] ) ) {
				$rate = $rate_data[ "rate_{$rate_id}" ];

				if ( 'none' !== $rate['pricing_sale_method'] ) {
					switch ( $rate['pricing_method'] ) {
						case 'increase':
							if ( 'percent' === $rate['pricing_unit'] ) {
								$multiplier = bcdiv( 
									bcadd( 
										Constants::PERCENT_BASE, 
										wc_format_decimal($rate['pricing_value'], Constants::MATH_SCALE),
										Constants::MATH_SCALE
									),
									Constants::PERCENT_BASE,
									Constants::MATH_SCALE
								);

								$reg_price = bcmul( $multiplier, wc_format_decimal( $this->get_regular_price(), Constants::MATH_SCALE ), Constants::MATH_SCALE );
							} else {
								$reg_price = bcadd( wc_format_decimal( $rate['pricing_value'], Constants::MATH_SCALE ), wc_format_decimal( $this->get_regular_price(), Constants::MATH_SCALE ), Constants::MATH_SCALE );
							}
							break;

						case 'decrease':
							if ( 'percent' === $rate['pricing_unit'] ) {
								$multiplier = bcdiv( 
									bcsub( 
										Constants::PERCENT_BASE, 
										wc_format_decimal($rate['pricing_value'], Constants::MATH_SCALE),
										Constants::MATH_SCALE
									),
									Constants::PERCENT_BASE,
									Constants::MATH_SCALE
								);
								$reg_price = bcmul( $multiplier, wc_format_decimal( $this->get_regular_price(), Constants::MATH_SCALE ), Constants::MATH_SCALE );
							} else {
								$reg_price = bcsub( wc_format_decimal( $this->get_regular_price(), Constants::MATH_SCALE ), wc_format_decimal( $rate['pricing_value'], Constants::MATH_SCALE ), Constants::MATH_SCALE );
							}
							break;

						case 'fixed':
						default:
							$reg_price = wc_format_decimal( $rate['pricing_value'], Constants::MATH_SCALE );
							break;
					}

					$sale_reg_rate_price = bcmul( $reg_price, $stay, Constants::MATH_SCALE );
				}
			}

			return $sale_reg_rate_price;
		}
	}

	/**
	 * Get rate plans for single product page.
	 *
	 * @param string $checkin Check-in date.
	 * @param string $checkout Check-out date.
	 * @param int    $qty Room number.
	 *
	 * @return string
	 */
	public function get_accommodation_rates_html( $checkin, $checkout, $qty = 1 ) {
		$default_rate = hbfwc_get_default_rate();
		$custom_rates = $this->get_meta( '_hbfwc_rate_plans' );

		$html = '<ul class="hbfwc_rate_plans" id="hbfwc_rate_plans">' . "\n";

		$ci_date = new \DateTime( wp_date( 'Y-m-d', $checkin ) );
		$co_date = new \DateTime( wp_date( 'Y-m-d', $checkout ) );
		$stay    = intval( $co_date->diff( $ci_date )->format( '%a' ) );

		$stay_string = sprintf(
			/* translators: Number of nights */
			_n( '%s night', '%s nights', $stay, 'hotel-booking-for-woocommerce' ),
			number_format_i18n( $stay )
		);
		$room_string = sprintf(
			/* translators: Number of rooms */
			_n( '%s room', '%s rooms', $qty, 'hotel-booking-for-woocommerce' ),
			number_format_i18n( $qty )
		);

		$selected_rate = absint( wp_unslash( $_REQUEST['rate'] ?? ( $default_rate ? $default_rate->term_id : 0 ) ) );
		$show_min_stay = hbfwc_show_min_stay_info();

		if ( $default_rate ) {

			$sale_reg_rate_price = $this->is_on_sale() 
						? bcmul( 
							bcmul(
								wc_format_decimal( $this->get_regular_price(), Constants::MATH_SCALE ),
								$stay,
								Constants::MATH_SCALE
							),
							$qty,
							Constants::MATH_SCALE 
						) : false;

			$maybe_disabled   = '';
			$disabled_message = [];
			$extraclasses     = '';
			if ( ! in_array( $default_rate->term_id, $this->has_active_rates_by_date( $checkin, $checkout ), true ) ) {
				$maybe_disabled = ' disabled';
				$extraclasses .= ' rate-disabled rate-unavailable';
				$disabled_message[] = '<span class="rate-not-available"><i class="ion-ios-alert"></i> ' . __( 'This rate is not available for the selected dates.', 'hotel-booking-for-woocommerce' ) . '</span>';	
			}

			$break_down_default_rate = $this->get_price_breakdown_html( $default_rate->term_id, $checkin, $checkout, $qty, true, $sale_reg_rate_price );
			$break_down_default_rate = str_replace( '{{stay_string}}', $stay_string, $break_down_default_rate );

			$need_more_stay = intval( $this->get_minimum_stay() ) > $stay;
			if ( $need_more_stay ) {
				$maybe_disabled = ' disabled';
				$extraclasses  .= ' rate-disabled need-more-stays';
				$selected_rate  = 0;
			}
			
			$extra_messages   = [];
			$extra_messages[] = $show_min_stay ? '<span class="rate-minimum-stay"><i class="ion-md-clock"></i> ' . sprintf( /* translators: Number of stay */ __( 'Minimum %d-night stay', 'hotel-booking-for-woocommerce' ), intval( $this->get_minimum_stay() ) ) . '</span>' : '';
			$extra_messages = array_merge( $extra_messages, $disabled_message );

			$dr  = '<li class="room-rate-option' . esc_attr( $selected_rate === absint( $default_rate->term_id ) ? ' rate-selected' : '' ) . esc_attr( $extraclasses ) . '">' . "\n";
			$dr .= '<input type="radio" name="rate" id="rate-' . esc_attr( $default_rate->term_id ) . '" value="' . $default_rate->term_id . '" ' . checked( absint($default_rate->term_id), $selected_rate, false ) . $maybe_disabled . ' required />' . "\n";
			$dr .= '<div class="rate-details">' . "\n";
			$dr .= '<h4><label for="rate-' . esc_attr( $default_rate->term_id ) . '">' . $default_rate->name . '</label></h4>' . "\n";
			$dr .= ( ! empty($extra_messages) ? implode( "\n", $extra_messages ) : '' ) . "\n";
			$dr .= '<div class="rate-features">' . "\n";
			$dr .= hbfwc_template_show_rate_features( $default_rate->term_id, false );
			$dr .= '<a href="#" class="hbfwc-button__rate-popup" data-rate_id="' . esc_attr( $default_rate->term_id ) . '">' . __( 'Rate details', 'hotel-booking-for-woocommerce' ) . '</a>' . "\n";
			$dr .= '</div>' . "\n";
			$dr .= '</div>' . "\n";
			$dr .= '<div class="rate-value">' . "\n";
			$dr .= $break_down_default_rate;
			$dr .= "<p>( {$stay_string}, {$room_string} )</p>\n";
			$dr .= '</div>' . "\n";
			$dr .= '</li>' . "\n";

			$html .= $dr;
		}

		if ( ! empty( $custom_rates ) ) {
			foreach ( $custom_rates as $rate_db_id => $rate ) {
				$rate_db_id = str_replace( 'rate_', '', $rate_db_id );
				$rate_term  = get_term_by( 'term_id', absint( $rate_db_id ), 'rate_plan' );

				// The rate taxonomy term may be deleted while stale product meta remains.
				if ( ! $rate_term ) {
					$selected_rate = ( $selected_rate === absint( $rate_db_id ) ) ? 0 : $selected_rate;
					continue;
				}

				$maybe_disabled   = '';
				$disabled_message = [];
				$extraclasses     = '';
				if ( ! isset( $rate['status'] ) || 'yes' !== $rate['status'] ) {
					$maybe_disabled = ' disabled';
					$extraclasses  .= ' rate-disabled rate-unavailable';
					$disabled_message[] = '<span class="rate-not-available"><i class="ion-ios-alert"></i> ' . __( 'This rate is not available for the selected dates.', 'hotel-booking-for-woocommerce' ) . '</span>';
				}

				if ( ! in_array( $rate_term->term_id, $this->has_active_rates_by_date( $checkin, $checkout ), true ) ) {
					if ( '' === $maybe_disabled ) {
						$maybe_disabled = ' disabled';
						$extraclasses .= ' rate-disabled rate-unavailable';
						$disabled_message[] = '<span class="rate-not-available"><i class="ion-ios-alert"></i> ' . __( 'This rate is not available for the selected dates.', 'hotel-booking-for-woocommerce' ) . '</span>';
					}
				}

				$sale_reg_rate_price = false;

				if ( 'none' !== $rate['pricing_sale_method'] ) {
					switch ( $rate['pricing_method'] ) {
						case 'increase':
							if ( 'percent' === $rate['pricing_unit'] ) {
								$multiplier = bcdiv( 
									bcadd( 
										Constants::PERCENT_BASE, 
										wc_format_decimal($rate['pricing_value'], Constants::MATH_SCALE),
										Constants::MATH_SCALE
									),
									Constants::PERCENT_BASE,
									Constants::MATH_SCALE
								);


								$reg_price  = bcmul( $multiplier, wc_format_decimal($this->get_regular_price(), Constants::MATH_SCALE), Constants::MATH_SCALE );
								$regTotalPr = bcmul( 
									bcmul(
										$reg_price,
										$stay,
										Constants::MATH_SCALE
									),
									$qty,
								Constants::MATH_SCALE );
								$rate_price = wc_price( $regTotalPr ) . $this->get_price_suffix();

							} else {

								$reg_price  = bcadd( 
									wc_format_decimal( $rate['pricing_value'], Constants::MATH_SCALE ),
									wc_format_decimal( $this->get_regular_price(), Constants::MATH_SCALE ),
									Constants::MATH_SCALE
								);

								$regTotalPr = bcmul( 
									bcmul(
										$reg_price,
										$stay,
										Constants::MATH_SCALE
									),
									$qty,
								Constants::MATH_SCALE );

								$rate_price = wc_price( $regTotalPr ) . $this->get_price_suffix();

							}

							break;

						case 'decrease':
							if ( 'percent' === $rate['pricing_unit'] ) {
								$multiplier = bcdiv( 
									bcsub( 
										Constants::PERCENT_BASE,
										wc_format_decimal($rate['pricing_value'], Constants::MATH_SCALE),
										Constants::MATH_SCALE
									),
									Constants::PERCENT_BASE,
									Constants::MATH_SCALE
								);


								$reg_price  = bcmul( $multiplier, wc_format_decimal( $this->get_regular_price(), Constants::MATH_SCALE ), Constants::MATH_SCALE );
								$regTotalPr = bcmul( 
									bcmul(
										$reg_price,
										$stay,
										Constants::MATH_SCALE
									),
									$qty,
								Constants::MATH_SCALE );
								$rate_price = wc_price( $regTotalPr ) . $this->get_price_suffix();

							} else {

								$reg_price  = bcsub( 
									wc_format_decimal( $this->get_regular_price(), Constants::MATH_SCALE ),
									wc_format_decimal( $rate['pricing_value'], Constants::MATH_SCALE ),
									Constants::MATH_SCALE
								);

								$regTotalPr = bcmul( 
									bcmul(
										$reg_price,
										$stay,
										Constants::MATH_SCALE
									),
									$qty,
								Constants::MATH_SCALE );
								$rate_price = wc_price( $regTotalPr ) . $this->get_price_suffix();
							}

							break;

						case 'fixed':
						default:
							$reg_price  = wc_format_decimal( $rate['pricing_value'], Constants::MATH_SCALE );
							$regTotalPr = bcmul( 
								bcmul(
									$reg_price,
									$stay,
									Constants::MATH_SCALE
								),
								$qty,
							Constants::MATH_SCALE );
							$rate_price = wc_price( $regTotalPr ) . $this->get_price_suffix();
							break;
					}

					if ( 'decrease' === $rate['pricing_sale_method'] ) {
						if ( 'percent' === $rate['pricing_sale_unit'] ) {
							$sale_multiplier = bcdiv( 
								bcsub( 
									Constants::PERCENT_BASE,
									wc_format_decimal($rate['pricing_sale_value'], Constants::MATH_SCALE),
									Constants::MATH_SCALE
								),
								Constants::PERCENT_BASE,
								Constants::MATH_SCALE
							);

							$sale = bcmul( $sale_multiplier, $reg_price, Constants::MATH_SCALE );
						} else {
							$sale = bcsub( $reg_price, wc_format_decimal( $rate['pricing_sale_value'], Constants::MATH_SCALE ), Constants::MATH_SCALE );
						}
					} else {
						$sale = wc_format_decimal( $rate['pricing_sale_value'], Constants::MATH_SCALE );
					}

					$sale_reg_rate_price = bcmul( bcmul( $reg_price, $stay, Constants::MATH_SCALE ), $qty, Constants::MATH_SCALE );
				}

				$break_down_for_rate = $this->get_price_breakdown_html( $rate_term->term_id, $checkin, $checkout, $qty, false, $sale_reg_rate_price );
				$break_down_for_rate = str_replace( '{{stay_string}}', $stay_string, $break_down_for_rate );

				$need_more_stay = intval( $this->get_minimum_stay_for_rate( $rate_term->term_id ) ) > $stay;
				$maybe_disabled = ( $need_more_stay && '' === $maybe_disabled ) ? ' disabled' : $maybe_disabled;
				if ( $need_more_stay ) {
					$extraclasses .= ' rate-disabled need-more-stays';
				}
				
				$extra_messages   = [];
				$extra_messages[] = $show_min_stay ? '<span class="rate-minimum-stay"><i class="ion-md-clock"></i> ' . sprintf( /* translators: Number of stay */ __( 'Minimum %d-night stay', 'hotel-booking-for-woocommerce' ), intval( $this->get_minimum_stay_for_rate( $rate_term->term_id ) ) ) . '</span>' : '';
				$extra_messages = array_merge( $extra_messages, $disabled_message );

				if ( '' !== $maybe_disabled && $selected_rate === absint( $rate_term->term_id ) ) {
					$selected_rate = 0;
				}

				$html .= '<li class="room-rate-option' . esc_attr( $selected_rate === absint( $rate_term->term_id ) ? ' rate-selected' : '' ) . esc_attr( $extraclasses ) . '">' . "\n";
				$html .= '<input type="radio" name="rate" id="rate-' . esc_attr( $rate_term->term_id ) . '" value="' . $rate_term->term_id . '" ' . checked( $selected_rate, absint($rate_term->term_id), false ) . $maybe_disabled . ' required />' . "\n";
				$html .= '<div class="rate-details">' . "\n";
				$html .= '<h4><label for="rate-' . esc_attr( $rate_term->term_id ) . '">' . $rate_term->name . '</label></h4>' . "\n";
				$html .= ( ! empty($extra_messages) ? implode( "\n", $extra_messages ) : '' ) . "\n";
				$html .= '<div class="rate-features">' . "\n";
				$html .= hbfwc_template_show_rate_features( $rate_term->term_id, false );
				$html .= '<a href="#" class="hbfwc-button__rate-popup" data-rate_id="' . esc_attr( $rate_term->term_id ) . '">' . __( 'Rate details', 'hotel-booking-for-woocommerce' ) . '</a>' . "\n";
				$html .= '</div>' . "\n";
				$html .= '</div>' . "\n";
				$html .= '<div class="rate-value">' . "\n";
				$html .= $break_down_for_rate;
				$html .= "<p>( {$stay_string}, {$room_string} )</p>\n";
				$html .= '</div>' . "\n";
				$html .= '</li>' . "\n";
			}
		}

		$html .= '</ul>' . "\n";

		return $html;
	}

	/**
	 * Calculate the product price for cart.
	 *
	 * @param timestamp $start The start date.
	 * @param timestamp $end The end date.
	 * @param int       $rate_id The rate id.
	 *
	 * @return string
	 */
	public function get_cart_price( $start, $end, $rate_id ) {
		$default_rate = hbfwc_get_default_rate();
		$custom_rates = $this->get_meta( '_hbfwc_rate_plans' );

		$ci_date = new \DateTime( wp_date( 'Y-m-d', $start ) );
		$co_date = new \DateTime( wp_date( 'Y-m-d', $end ) );
		$stay    = absint( $co_date->diff( $ci_date )->format( '%a' ) );

		$rate_price = '0';
		if ( $default_rate && absint( $rate_id ) === $default_rate->term_id ) {

			while ( $ci_date < $co_date ) {
				$price      = $this->get_rate_price( $rate_id, $ci_date->format( 'Y-m-d' ), true );
				$rate_price = bcadd( $rate_price, $price, Constants::MATH_SCALE );

				$ci_date->modify( '+1 day' );
			}
		} else {
			if ( empty( $custom_rates ) ) {
				return '0';
			}

			while ( $ci_date < $co_date ) {
				$price      = $this->get_rate_price( $rate_id, $ci_date->format( 'Y-m-d' ), false );
				$rate_price = bcadd( $rate_price, $price, Constants::MATH_SCALE );

				$ci_date->modify( '+1 day' );
			}
		}
		
		$rate_price = wc_format_decimal( $rate_price, Constants::MATH_SCALE );

		return apply_filters( 'woocommerce_hbfwc_rate_price_for_cart', $rate_price, $this, $start, $end, $rate_id );
	}

	/**
	 * Check availability.
	 *
	 * @param timestamp $start The start date.
	 * @param timestamp $end The end date.
	 * @param int       $room The number of room.
	 *
	 * @return mixed
	 */
	public function check_availability( $start, $end, $room ) {
		$allotment = $this->get_allotment();
		// No allotment set.
		if ( empty( $allotment ) ) {
			return true;
		}

		if ( absint( $room ) > $allotment ) {
			$room_string = sprintf(
				/* translators: Number of rooms */
				_n( '%s room', '%s rooms', $allotment, 'hotel-booking-for-woocommerce' ),
				number_format_i18n( $allotment )
			);
			return new \WP_Error(
				'error',
				/* translators: Room count */
				sprintf( __( '%s available.', 'hotel-booking-for-woocommerce' ), $room_string )
			);
		}

		// Normalize the date.
		$date_start = wp_date( 'Y-m-d', $start );
		$date_end   = wp_date( 'Y-m-d', $end );

		$room_datas = $this->hbfwc_db->get_date_range_entries( $this->get_id(), $date_start, $date_end );

		$error_msg = array();
		$not_avail_dates = [];
		if ( $room_datas ) {
			foreach ( $room_datas as $room_data ) {
				if ( $room_data->booking_date === $date_end && $date_start !== $date_end ) {
					continue;
				}

				if ( 1 > absint( $room_data->status ) || 1 > absint( $room_data->qty ) ) {
					$not_avail_dates[] = wp_date( hbfwc_get_date_format(), strtotime( $room_data->booking_date ) );
				} else if ( absint( $room ) > absint( $room_data->qty ) ) {
					$room_string = sprintf(
						/* translators: Number of rooms */
						_n( '%s room', '%s rooms', $room_data->qty, 'hotel-booking-for-woocommerce' ),
						number_format_i18n( $room_data->qty )
					);
					$error_msg[] = sprintf(
						/* translators: 1. Room count 2. The date */
						__( 'Only %1$s left for %2$s.', 'hotel-booking-for-woocommerce' ),
						$room_string,
						wp_date( hbfwc_get_date_format(), strtotime( $room_data->booking_date ) )
					);
				}
			}
		}

		if ( ! empty( $not_avail_dates ) ) {
			$print_date = \hbfwc_natural_language_join( $not_avail_dates, __( 'and', 'hotel-booking-for-woocommerce' ) );

			$error_msg[] = sprintf(
				/* translators: Booking date */
				esc_html__( 'No rooms available on %s.', 'hotel-booking-for-woocommerce' ),
				esc_html( $print_date )
			);
		}

		if ( ! empty( $error_msg ) ) {
			$error_msg = implode( '<br/>', $error_msg );
			return new \WP_Error( 'error', $error_msg );
		}

		return true;
	}

	/**
	 * Get allotment.
	 */
	public function get_allotment() {
		return '' !== $this->get_meta( '_allotment' ) ? absint( $this->get_meta( '_allotment' ) ) : '';
	}

	/**
	 * Get active rates.
	 */
	public function get_active_rates() {
		$rates           = array();
		$default_rate_id = (int) get_option( 'hbfwc_default_rate_id', 0 );
		$custom_rates    = $this->get_custom_rates();

		$default_rate = get_term_by( 'term_id', absint( $default_rate_id ), 'rate_plan' );
		if ( $default_rate ) {
			$rates[] = $default_rate;
		}

		if ( ! empty( $custom_rates ) ) {
			foreach ( $custom_rates as $rate_id => $rate ) {
				$rate_id = str_replace( 'rate_', '', $rate_id );
				$n_rate  = get_term_by( 'term_id', absint( $rate_id ), 'rate_plan' );

				if ( ! $n_rate ) {
					continue;
				}

				$rates[] = $n_rate;
			}
		}

		return $rates;
	}

	/**
	 * Get custom rates data.
	 */
	public function get_custom_rates() {
		$rates = $this->get_meta( '_hbfwc_rate_plans' );
		return is_array( $rates ) ? $rates : [];
	}

	/**
	 * Get rate default status.
	 *
	 * @param int $rate_id The rate id to check.
	 * @return bool
	 */
	public function is_active_rate( $rate_id ) {
		$default_rate_id = (int) get_option( 'hbfwc_default_rate_id', 0 );
		$active          = false;

		if ( ! \hbfwc_hide_unavailable_items_in_search() ) {
			return apply_filters( 'hbfwc_is_active_rate', true, $rate_id, $this );
		}

		if ( (int) $rate_id === $default_rate_id ) {
			$active = true;
		}

		$rate_data = $this->get_custom_rates();

		if ( is_array( $rate_data ) && isset( $rate_data[ "rate_{$rate_id}" ] ) ) {
			$rate = $rate_data[ "rate_{$rate_id}" ];

			if ( ! isset( $rate['status'] ) || ( isset( $rate['status'] ) && 'yes' !== $rate['status'] ) ) {
				$active = false;
			} else {
				$active = true;
			}
		}

		return apply_filters( 'hbfwc_is_active_rate', $active, $rate_id, $this );
	}

	/**
	 * Get active rate plans by date.
	 * If no active rates on the given date range, this product should not available for sell.
	 *
	 * @param integer $from     Date from to check.
	 * @param integer $end      Date end to check.
	 *
	 * @return array
	 */
	public function has_active_rates_by_date( $from, $end ) {
		if ( ! is_numeric( $from ) ) {
			$from = strtotime( $from );
			if ( false === $from ) {
				return array();
			}
		}

		if ( ! is_numeric( $end ) ) {
			$end = strtotime( $end );
			if ( false === $end ) {
				return array();
			}
		}

		$rates           = $this->get_active_rates();
		$default_rate_id = (int) get_option( 'hbfwc_default_rate_id', 0 );

		// PRO has own method.
		if ( hbfwc_is_pro() ) {
			return apply_filters( 'hbfwc_has_active_rates_by_date', array(), $rates, $this, $from, $end );
		}

		$has = array();

		foreach ( $rates as $rate ) {
			if ( $rate->term_id === $default_rate_id ) {
				$def_rate = $this->get_meta( '_default_rate_prices' );
				$def_rate = is_array( $def_rate ) ? $def_rate : array();

				if ( empty( $def_rate ) ) {
					// Data is empty and the default rate default status is active, so we tell them that we have this rate active.
					$has[] = (int) $rate->term_id;
				} else {
					$start_date = new \DateTime( wp_date( 'Y-m-d', $from ) );
					$end_date   = new \DateTime( wp_date( 'Y-m-d', $end ) );

					$not_available = false;	
					
					while ( $start_date < $end_date ) {
						$key = $start_date->format( 'Ymd' );

						if ( isset( $def_rate[ $key ] ) ) {
							$rate_day_data = $def_rate[ $key ];

							if ( isset( $rate_day_data['status'] ) && 1 > absint( $rate_day_data['status'] ) ) {
								$not_available = true;
								break;
							}
						}

						$start_date->modify( '+1 day' );
					}

					if ( ! $not_available ) {
						$has[] = (int) $rate->term_id;
					}
				}
			} elseif ( $this->is_active_rate( $rate->term_id ) ) {
				$has[] = (int) $rate->term_id;
			}
		}

		return $has;
	}

	/**
	 * Get basic information.
	 */
	public function get_basic_information() {
		$bed          = '' !== $this->get_meta( '_no_of_beds' ) ? $this->get_meta( '_no_of_beds' ) : false;
		$dim          = '' !== $this->get_meta( '_room_size' ) ? $this->get_meta( '_room_size' ) : false;
		$no_of_guests = array(
			absint( $this->get_max_adults() ),
			absint( $this->get_max_children() ),
		);
		$no_of_guests = array_sum( $no_of_guests );

		$guest_adults_string = '';
		$guest_child_string  = '';
		$guests_string       = '';
		if ( $no_of_guests > 0 ) {
			$guest_adults_string = sprintf(
				/* translators: Number of adults */
				_n( '%d adult', '%d adults', absint( $this->get_max_adults() ), 'hotel-booking-for-woocommerce' ),
				number_format_i18n( absint( $this->get_max_adults() ) )
			);
			$guest_child_string = sprintf(
				/* translators: Number of children */
				_n( '%d child', '%d children', absint( $this->get_max_children() ), 'hotel-booking-for-woocommerce' ),
				number_format_i18n( absint( $this->get_max_children() ) )
			);

			$guests_string = "{$guest_adults_string}, {$guest_child_string}";
		}

		$html = false;

		if ( $bed || $dim || $no_of_guests > 0 ) {
			$html  = '<ul class="hbfwc-room-info__list">' . "\n";
			$html .= $bed ? '<li class="no_of__bed">' . $bed . '</li>' . "\n" : '';
			$html .= $no_of_guests > 0 ? '<li class="no_of__guest">' . sprintf(
				/* translators: Number of guests */
				_n( '%d Person', '%d Persons', $no_of_guests, 'hotel-booking-for-woocommerce' ),
				$no_of_guests
			) .
				'<span class="hbfwc-tooltip">' . esc_html( $guests_string ) . '</span></li>' . "\n" : '';
			$html .= $dim ? '<li class="room__dim">' . $dim . '</li>' . "\n" : '';
			$html .= '</ul>' . "\n";
		}

		return $html;
	}

	/**
	 * Get invalid dates in a year.
	 */
	public function get_invalid_dates() {
		$today        = wp_date( 'Y-m-d' );
		$max_year_end = wp_date( 'Y-m-d', strtotime( '+1 year' ) );

		$room_datas = $this->hbfwc_db->get_date_range_entries( $this->get_id(), $today, $max_year_end, 'ARRAY_A' );

		// Index by date once to avoid scanning the full array for every day in the loop.
		$room_data_by_date = array();
		if ( is_array( $room_datas ) ) {
			foreach ( $room_datas as $item ) {
				if ( ! isset( $item['booking_date'] ) ) {
					continue;
				}

				// Keep first row for a given date to preserve previous behavior.
				if ( ! isset( $room_data_by_date[ $item['booking_date'] ] ) ) {
					$room_data_by_date[ $item['booking_date'] ] = $item;
				}
			}
		}

		$start_date = new \DateTime( $today );
		$end_date   = new \DateTime( $max_year_end );

		$dates      = [];
		// Iterate through the date range.
		while ( $start_date <= $end_date ) {
			$date_str = $start_date->format( 'Y-m-d' );

			$this_date = $room_data_by_date[ $date_str ] ?? null;

			if ( $this_date ) {

				if ( 1 > absint( $this_date['qty'] ) || 1 > absint( $this_date['status'] ) ) {
					$dates[] = $this_date['booking_date'];
				} else {
					$has_rates = $this->has_active_rates_by_date( strtotime( $date_str ), strtotime( "+1 day", strtotime( $date_str ) ) );

					if ( empty( $has_rates ) ) {
						$dates[] = $this_date['booking_date'];
					}
				}
			} else {
				$has_rates = $this->has_active_rates_by_date( strtotime( $date_str ), strtotime( "+1 day", strtotime( $date_str ) ) );

				if ( empty( $has_rates ) ) {
					$dates[] = $date_str;
				}
			}

			$start_date->modify( '+1 day' );
		}

		return $dates;
	}

	/**
	 * Find the first available check-in date that satisfies the minimum stay requirement.
	 *
	 * Starts searching from the given start date and checks each day until a valid
	 * range of consecutive dates (equal to $min_stay) is found where none of the
	 * dates are in the blocked list.
	 *
	 * @param DateTime $start_date      Starting point for the search.
	 * @param int      $min_stay        Minimum number of consecutive nights required.
	 * @param int      $max_lookahead   Maximum number of days to look ahead.
	 * @return DateTime|null            A valid check-in date or null if none found.
	 */
	public function first_valid_checkin_date($start_date, $min_stay, $max_lookahead = 365) {
		$date          = clone $start_date;
		$blocked_dates = $this->get_invalid_dates();
		$tries         = 0;

		// If no dates are blocked.
		if ( empty($blocked_dates) ) {
			return $date;
		}

		while ($tries < $max_lookahead) {
			$valid = true;
			$check = clone $date;

			for ($i = 0; $i < $min_stay; $i++) {
				if (in_array($check->format('Y-m-d'), $blocked_dates)) {
					$valid = false;
					break;
				}
				$check->modify('+1 day');
			}

			if ($valid) return $date;

			$date->modify('+1 day');
			$tries++;
		}

		return null; // No valid dates in a year?.
	}

	/**
	 * Prints the available add-ons in single product.
	 */
	function get_available_addons() {
		$all_addon_posts = \hbfwc_get_available_booking_addon();
		$all_addon_posts = apply_filters( 'hbfwc_available_addons', $all_addon_posts, $this );

		if ( empty( $all_addon_posts ) ) {
			return;
		}

		$addon_html = '<div class="hbfwc-single-addons">' . "\n";
		$addon_html .= '<ul>' . "\n";
		foreach( $all_addon_posts as $addon ) {
			$addon_id = $addon->ID;
			$price    = \hbfwc_parse_price_input( get_post_meta( (int) $addon_id, 'price', true ) );
			$required = ( get_post_meta( (int) $addon_id, 'required', true ) === 'yes' ? true : false );
			$type     = get_post_meta( (int) $addon_id, '_addon_type', true );
			$title    = get_the_title( $addon );
			$desc     = get_post_meta( (int) $addon_id, 'description', true );
			$min      = absint( get_post_meta( (int) $addon_id, 'min', true ) );
			$max      = absint( get_post_meta( (int) $addon_id, 'max', true ) );
			$unit     = ( $type === 'quantity_based' && '' !== get_post_meta( (int) $addon_id, 'unit', true ) ) ? ' / ' . get_post_meta( (int) $addon_id, 'unit', true ) : '';

			$disabled = $required ? ' disabled' : '';
			$checked  = checked( true, $required, false );

			$addon_html .= '<li class="hbfwc-single-addon-item">' . "\n";
				$addon_html .= '<label for="hbfwc-addon-' . esc_attr( $addon_id ) . '">' . "\n";
				$addon_html .= '<input type="checkbox" id="hbfwc-addon-' . esc_attr( $addon_id ) . '" name="addon[' . esc_attr( $addon_id ) . ']" value="' . esc_attr( $addon_id ) . '" ' . $checked . $disabled . '/>';
				if ( $required ) {
					$addon_html .= '<input type="hidden" name="addon[' . esc_attr( $addon_id ) . ']" value="' . esc_attr( $addon_id ) . '" />';
				}
				$addon_html .= '</label>' . "\n";

				$addon_html .= '<div class="hbfwc-single-addon-item__info">' . "\n";
				$addon_html .= '<h4>' . esc_html( $title ) . '</h4>' . "\n";
				$addon_html .= '<p>' . wp_kses_post( $desc ) . '</p>' . "\n";
				$addon_html .= '<div class="hbfwc-single-addon-item__price">' . wc_price( $price ) . $unit . '</div>' . "\n";
				$addon_html .= '</div>' . "\n";

				if ( $type === 'quantity_based' ) {
					$addon_html .= '<div class="hbfwc-single-addon-item__qty">';
					$addon_html .= '<input type="number" name="addon_qty[' . esc_attr( $addon_id ) . ']" value="' . esc_attr( $min ) . '" class="hbfgwc-with-buttons" min="' . esc_attr( $min ) . '" max="' . esc_attr( $max ) . '" disabled />';
					$addon_html .= '</div>' . "\n";
				}
			$addon_html .= '</li>' . "\n";
		}
		$addon_html .= '</ul>' . "\n";
		$addon_html .= '</div>' . "\n";

		print apply_filters( 'hbfwc_single_available_addons_html', $addon_html, $all_addon_posts, $this );
	}

	/**
	 * Sort an associative array of $variation_id => $price pairs in order of min and max prices.
	 *
	 * @param array $prices associative array of $variation_id => $price pairs.
	 * @return array
	 */
	protected function sort_accommodation_prices( $prices ) {
		asort( $prices );

		return $prices;
	}
}
