<?php
// phpcs:ignoreFile WordPress.Security.EscapeOutput.OutputNotEscaped
if ( ! defined( 'ABSPATH' ) ) {
	exit;
} // Exit if accessed directly

class SLB_Discount_Plugin {

	const POST_TYPE_DISCOUNT = 'sln_discount';

	/**
	 * @var SLB_Discount_Plugin
	 */
	private static $instance;

	/**
	 * @var SLN_Plugin
	 */
	private $plugin;

	public static function getInstance()
	{
		if (!self::$instance) {
			self::$instance = new self;
		}

		return self::$instance;
	}

	/**
	 * SLN_Plugin_Discount constructor.
	 */
	private function __construct() {
		add_action('plugins_loaded', array($this, 'hook_plugins_loaded'));
		add_action('init', array($this, 'hook_init'));
	}

    private function init_admin() {
        $this->init_ajax();
		SLB_Discount_Admin_ExportDiscountsCsv::init_hooks();
    }

    private function init_ajax() {
    	$enableDiscountSystem = $this->plugin->getSettings()->get('enable_discount_system');
        if (!$enableDiscountSystem) {
            return;
        }
        $callback = array($this, 'ajax');
        add_action('wp_ajax_salon_discount', $callback);
        add_action('wp_ajax_nopriv_salon_discount', $callback);
    }

    public function hook_plugins_loaded() {
		$plugin = SLN_Plugin::getInstance();
		$plugin->templating()->addPath(SLN_PLUGIN_DIR.'/views/discount/%s.php', 11);
		$this->plugin = $plugin;
	}

	public function hook_init() {
		// If the main plugin booted on 'init' (not 'plugins_loaded'), hook_plugins_loaded() will
		// never have fired because plugins_loaded was already past when this class was instantiated.
		// Calling hook_plugins_loaded() here is safe: by init priority 10, SLN_Plugin::getInstance()
		// returns the already-constructed singleton with no circular dependency.
		if ( is_null( $this->plugin ) ) {
			$this->hook_plugins_loaded();
		}
		$enableDiscountSystem = $this->plugin->getSettings()->get('enable_discount_system');
        if (!$enableDiscountSystem) {
            return;
        }
		$plugin = $this->getSlnPlugin();
		$plugin->addRepository(
			new SLB_Discount_Repository_DiscountRepository(
				$plugin,
				new SLB_Discount_PostType_Discount($plugin, self::POST_TYPE_DISCOUNT)
			)
		);
		add_action('admin_init', array($this, 'hook_admin_init'));

		add_action('admin_enqueue_scripts', array($this, 'hook_admin_enqueue_scripts'));
		add_action('wp_enqueue_scripts', array($this, 'hook_wp_enqueue_scripts'), 100000);

		add_filter('sln.func.isSalonPage', array($this, 'hook_isSalonPage'));

		add_action('sln.metabox.booking.pre_eval',array($this,'hook_metabox_pre_eval'));

		add_filter('sln.calc_booking_total.get_services', array($this, 'get_services_calc_booking_total'), 10, 3);
		add_filter('sln.calc_booking_total.get_discounts_html', array($this, 'get_discounts_html_calc_booking_total'));

		add_action('sln.booking_builder.create', array($this, 'hook_booking_builder_create'), 10, 1);

		add_action('sln.shortcode.summary.dispatchForm.before_booking_creation', array($this, 'hook_summary_dispatchForm_before_booking_creation'), 10, 2);

		add_action('sln.template.summary.before_total_amount', array($this, 'hook_summary_before_total_amount'), 10, 2);
		add_action('sln.template.summary.after_total_amount', array($this, 'hook_summary_after_total_amount'), 10, 2);

		add_filter('sln.template.metabox.booking.total_amount_label', array($this, 'hook_booking_total_amount_label'), 10, 2);
		add_action('sln.template.metabox.booking.total_amount_row', array($this, 'hook_booking_total_amount_row'));

		add_action('sln.booking.setStatus', array($this, 'hook_booking_setStatus'), 10, 3);

		add_action('sln.mail.summary_details', array($this, 'hook_mail_summary_details'));
		add_action('sln.mail.special', array($this, 'hook_mail_special_offer'), 10, 2);

		add_action('sln.my_account.nav', array($this, 'hook_history_nav'));
		add_action('sln.my_account.content', array($this, 'hook_history_content'));

		add_filter('sln.action.ajaxcalendar.wrapBooking.discountAmount', array($this, 'get_discount_amount_ajaxcalendar_booking'), 10, 2);
        add_action('sln.api.booking.pre_eval',array($this,'hook_api_pre_eval'), 10, 2);

        add_filter('sln.customer.fidelity_score.discounts_score', array($this, 'get_fidelity_score_discounts_score'), 10, 2);

        if (is_admin()) {
            $this->init_admin();
        }
	}

	// js
	// sconti cumulabili
	public function hook_metabox_pre_eval($booking){
		$enableDiscountSystem = $this->plugin->getSettings()->get('enable_discount_system');
		if(!$enableDiscountSystem) return;

		// Defense-in-depth: only touch stored discounts when the discount field was actually
		// part of this submission. If the booking edit form could not render the discount
		// dropdown at all (e.g. multi-shop catalog filtering hid the applied coupon and no
		// other coupons exist for the current shop), the field is absent from POST. In that
		// case do nothing rather than clearing the applied discount, decrementing its usage
		// counters and restoring the full (undiscounted) price.
		if ( ! array_key_exists( '_' . SLN_Plugin::POST_TYPE_BOOKING . '_discounts', $_POST ) ) {
			return;
		}

		$old_discounts = SLB_Discount_Helper_Booking::getBookingDiscountIds($booking);
		$data = array();
		$items = $booking->getServicesMeta();
		if(!empty($_POST['_sln_booking_discounts']) && !is_array($_POST['_sln_booking_discounts'])){
			if(intval($_POST['_sln_booking_discounts'])) $discounts = array(intval($_POST['_sln_booking_discounts']));
			else return;
		}
		if(!empty($_POST['_sln_booking_discounts']) && is_array($_POST['_sln_booking_discounts'])) $discounts = array_map('intval',$_POST['_sln_booking_discounts']);
		if (!isset($discounts)) {
			$discounts = array();
		}
		// Remove any 0 values that result from intval("") on an empty "No Discounts" selection.
		// Without this, empty([0]) = false in PHP, bypassing the clear path below and leaving
		// _sln_booking_discounts as [] while stale per-coupon _sln_booking_discount_X keys survive.
		$discounts = array_values(array_filter($discounts, function($id) { return $id > 0; }));
		$discounts_to_compare = empty($discounts) ? array() : $discounts;
		// Union canonical _sln_booking_discounts with per-coupon _sln_booking_discount_{id} meta so usage
		// adjustments and clears stay correct when the list was emptied but stale per-coupon keys remained.
		$old_discounts_safe           = is_array( $old_discounts ) ? $old_discounts : array();
		$meta_coupon_ids               = $this->get_coupon_ids_from_per_discount_meta_keys($booking->getId());
		$union_applied_discount_ids   = array_values(
			array_unique(
				array_filter(
					array_map( 'intval', array_merge( $old_discounts_safe, $meta_coupon_ids ) ),
					function ( $id ) {
						return $id > 0;
					}
				)
			)
		);
		$discounts_to_decrement       = array_diff($union_applied_discount_ids, $discounts_to_compare);
		$discounts_to_increment       = array_diff($discounts_to_compare, $union_applied_discount_ids);
		$dRepo = SLN_Plugin::getInstance()->getRepository(SLB_Discount_Plugin::POST_TYPE_DISCOUNT);
		if (empty($discounts)){
			foreach (['discounts',"discount_amount", "discount_score"] as $k) {
	            delete_post_meta($booking->getId(), '_'.SLN_Plugin::POST_TYPE_BOOKING.'_'.$k);
            }
			$this->delete_all_per_coupon_discount_meta_keys($booking->getId());
	    	foreach($items as $sId => &$atId) {
	    		$service = new SLN_Wrapper_Service($sId);
				$price       = $service->getPrice();
				if( is_array( $atId ) ){
					$items[$sId] = array_merge($atId,array(
						'price'     => $price
					));
				} else{
					$items[$sId] = array( 'service' => $sId,
										  'attendant' => $atId,
										  'price' => $price );
				}
			}
			foreach ($discounts_to_decrement as $discountId) {
				$discount = $this->get_discount_wrapper_or_null( $dRepo, $discountId );
				if ( ! $discount ) {
					continue;
				}
				$discount->decrementUsagesNumber($booking->getUserId());
				$discount->decrementTotalUsagesNumber();
			}
			update_post_meta($booking->getId(), '_'.SLN_Plugin::POST_TYPE_BOOKING.'_services', $items);
			return;
		}
		$bookingServices = $booking->getBookingServices();
		foreach ($bookingServices->getItems() as $bookingService) {
		    $service = new SLN_Wrapper_Service($bookingService->getService()->getId());
		    $price   = $service->getPrice();
		    $bookingService->setPrice($price);
		}
		$data["discounts"] = array();
		$discountValues = 0;
		$first = true;
		foreach ($discounts as $discountId) {
			$discount = $this->get_discount_wrapper_or_null( $dRepo, $discountId );
			if ( ! $discount ) {
				SLN_Plugin::addLog( sprintf( '[Discount] Skipping missing discount post %d (admin path) for booking %d', (int) $discountId, $booking->getId() ) );
				continue;
			}

			// Honor the admin's explicit selection. The back-end booking editor is an
			// authoritative context: when staff manually pick a discount it must be
			// applied and stored even if it is expired or has reached its usage limit
			// (e.g. a single-use coupon that THIS booking already consumed).
			//
			// Gating this on validateDiscount() (added in 22d743ca7, Apr 2026) caused the
			// coupon to be silently dropped from _sln_booking_discounts on save: the loop
			// hit `continue`, so the canonical list was written back as [] while the
			// discounted price (discount_amount) was left in place. Multi-shop made it
			// reproducible on every booking because it saves a second time right after
			// creation — by then the freshly-applied single-use coupon already failed
			// validation. Usage/expiry limits are enforced on the customer-facing path;
			// they must not strip an admin-chosen discount here.
			$discountValues  = $discount->applyDiscountToBookingServices($bookingServices, true,  $booking->getAttendantsIds());

			$data["discounts"][] = $discountId;
			$data["discount_{$discountId}"] = true;
			$data["discount_amount"] = array_merge(isset($data["discount_amount"]) ? $data["discount_amount"] : array(),$discountValues);

            $discountScores = array();

            $rules = $discount->getDiscountRules();
            if (!empty($rules)) {
                foreach($rules as $rule) {
                    if ($rule['mode'] === 'score') {
                        $discountScores[] = (int)$rule['score_number'];
                    }
                }
				if(!empty($discountScores)){
					$discountScores = array(max($discountScores));
				}
            }


            $data["discount_score"] = array_merge(isset($data["discount_score"]) ? $data["discount_score"] : array(), $discountScores);

			foreach($items as $sId => $atId) {
				if($first){
					$service = new SLN_Wrapper_Service($sId);
					$price       = $service->getPrice();
				}
				else $price      = isset($items[$sId]['price']) ? $items[$sId]['price'] : ($bookingServices->findByService($sId) ? $bookingServices->findByService($sId)->getPrice() : '');

				if( is_array( $atId ) ){
					$items[$sId] = array_merge($atId,array(
						'price'     => $price - $discountValues[$atId['service']]
					));
				} else{
					$items[$sId] = array( 'service' => $sId,
										  'attendant' => $atId,
										  'price' => $price );
				}
                                if ($bookingServices->findByService($sId)) {
                                    $bookingServices->findByService($sId)->setPrice($items[$sId]['price']);
                                }
			}
			if(in_array($discountId,$discounts_to_increment)){
					$discount->incrementUsagesNumber($booking->getUserId());
					$discount->incrementTotalUsagesNumber();
			}
			$first = false;
		}
		foreach ($discounts_to_decrement as $discountId) {
				$data["discount_{$discountId}"] = false;
				$discount = $this->get_discount_wrapper_or_null( $dRepo, $discountId );
				if ( ! $discount ) {
					continue;
				}
				$discount->decrementUsagesNumber($booking->getUserId());
				$discount->decrementTotalUsagesNumber();
		}

		$data["services"] = $items;

		foreach ($data as $k => $v) {
	            update_post_meta($booking->getId(), '_'.SLN_Plugin::POST_TYPE_BOOKING.'_'.$k, $v);
	    }
	}

	/**
	 * @param SLN_Repository_AbstractWrapperRepository $dRepo
	 * @param int                                     $discountId
	 * @return SLB_Discount_Wrapper_Discount|null
	 */
	private function get_discount_wrapper_or_null( $dRepo, $discountId ) {
		$discountId = (int) $discountId;
		if ( $discountId <= 0 ) {
			return null;
		}
		$discount = $dRepo->create( $discountId );
		if ( $discount->isEmpty() ) {
			return null;
		}
		return $discount;
	}

	/**
	 * Coupon post IDs referenced by _{booking_pt}_discount_{numeric_id} only (not discount_amount / discounts / discount_score).
	 *
	 * @param int $booking_id
	 * @return int[]
	 */
	private function get_coupon_ids_from_per_discount_meta_keys($booking_id) {
		$booking_id = (int) $booking_id;
		$pt         = SLN_Plugin::POST_TYPE_BOOKING;
		$prefix     = '_' . $pt . '_discount_';
		$custom     = get_post_custom($booking_id);
		$ids        = array();
		if (!is_array($custom)) {
			return $ids;
		}
		foreach (array_keys($custom) as $key) {
			if (preg_match('/^' . preg_quote($prefix, '/') . '(\d+)$/', $key, $m)) {
				$ids[] = (int) $m[1];
			}
		}
		return $ids;
	}

	/**
	 * Remove all per-coupon _{booking_pt}_discount_{id} meta rows (used when clearing discounts in admin).
	 *
	 * @param int $booking_id
	 */
	private function delete_all_per_coupon_discount_meta_keys($booking_id) {
		$booking_id = (int) $booking_id;
		$pt         = SLN_Plugin::POST_TYPE_BOOKING;
		$prefix     = '_' . $pt . '_discount_';
		$custom     = get_post_custom($booking_id);
		if (!is_array($custom)) {
			return;
		}
		foreach (array_keys($custom) as $key) {
			if (preg_match('/^' . preg_quote($prefix, '/') . '(\d+)$/', $key)) {
				delete_post_meta($booking_id, $key);
			}
		}
	}

	public function get_services_calc_booking_total($items, $bookingServices, $bookingAttendants) {

		$enableDiscountSystem = $this->plugin->getSettings()->get('enable_discount_system');

		if( ! $enableDiscountSystem ) {
		    return;
		}

		$discounts = !empty($_POST['_sln_booking_discounts']) ? $_POST['_sln_booking_discounts'] : array();

		if ( empty( $discounts ) ) {
		    return $items;
		}

		$dRepo = SLN_Plugin::getInstance()->getRepository(SLB_Discount_Plugin::POST_TYPE_DISCOUNT);

		$discountValues = array();
		$first = true;
		foreach ($discounts as $discountId) {
		    $discount        = $dRepo->create($discountId);
		    $discountValues  = $discount->applyDiscountToBookingServices($bookingServices, true, $bookingAttendants);
		    foreach($items as $sId => $atId) {
		    	$price = 0;
				if($first){
				    $service = new SLN_Wrapper_Service($sId);
	                $service = apply_filters('sln.booking_services.buildService', $service);
					if(!$service->getVariablePriceEnabled()){
					    $price = $service->getPrice();
					}else{
						$price = $service->getVariablePrice(isset($atId['attendant']) ? $atId['attendant'] : $atId);
					}
				} else {
					if(isset($items[$sId]['price'])){
						$price = $items[$sId]['price'];
					}else{
						$service = $bookingservices->findByService($sId);
						if(!$service->getVariablePriceEnabled()){
							$price = $service->getPrice();
						}else{
							$price = $service->getVariablePrice(isset($attId['attendant']) ? $atId['attendant'] : $atId);
						}
					}
				    $price = isset($items[$sId]['price']) ? $items[$sId]['price'] : $bookingServices->findByService($sId)->getPrice();
				}

				if( is_array( $atId ) ){
					$items[$sId] = array_merge($atId,array(
						'price' => $price - $discountValues[$sId]
					));
				} else{
					$items[$sId] = array( 'service' => $sId,
											  'attendant' => $atId,
											  'price' => $price );
				}
				$bookingServices->findByService($sId)->setPrice($items[$sId]['price']);
		    }
		    $first = false;
		}

		return $items;
	}

	public function hook_api_pre_eval($booking, $discounts){
		if(!$this->plugin->getSettings()->get('enable_discount_system')){
			return;
		}
		$old_discounts = SLB_Discount_Helper_Booking::getBookingDiscounts($booking);
		if(!is_array($discounts)){
		$discounts = $discounts instanceof SLB_Discount_Wrapper_Discount
					? array($discounts)
					: (
						intval($discounts)
						? array(new SLB_Discount_Wrapper_Discount(intval($discounts)))
						: array()
					);
		}
		$discounts = array_map(array($this, 'createDiscount'), $discounts);
		$dRepo = $this->plugin->getRepository(SLB_Discount_Plugin::POST_TYPE_DISCOUNT);
        if(!empty(array_intersect($old_discounts, $discounts))){
            $old_discounts = array();
        }
		$discounts_to_decrement = array_diff($old_discounts, $discounts);
		$discounts_to_increment = array_diff($discounts, $old_discounts);
		if(empty($discounts_to_decrement) && empty($discounts_to_increment)){
			return;
		}

		$bookingServices = $booking->getBookingServices();
		$countServices = $booking->getCountServices();
		// Reset prices for services
		$amount = 0;
		foreach($bookingServices->getItems() as $bookingService){
			$service = $bookingService->getService();
			$price = $service->getPrice() * $bookingService->getCountServices();
			$amount += $price;
			$bookingService->setPrice($price);
		}
		$booking->setMeta('amount', $amount);
		
		if(!empty($discounts_to_decrement)){
			// Remove data about old discounts
			foreach(['discount', 'discount_amount', 'discount_score'] as $k){
				delete_post_meta($booking->getId(), '_'.SLN_Plugin::POST_TYPE_BOOKING.'_'.$k);
			}
		foreach($discounts_to_decrement as $discount){
			$discount = $dRepo->create($discount);
			// Only adjust usage counters for committed bookings. DRAFT bookings are
			// temporary placeholders; their usage was never counted so must not be
			// decremented either (that would produce a negative usage count).
			if ( $booking->getStatus() !== SLN_Enum_BookingStatus::DRAFT ) {
				$discount->decrementUsagesNumber($booking->getUserId());
				$discount->decrementTotalUsagesNumber();
			}
			delete_post_meta($booking->getId(), '_'.SLN_Plugin::POST_TYPE_BOOKING.'_discount_'.$discount->getId());
		}
		}
		$data = array('discounts' => array());
		if(!empty($discounts_to_increment)){
			// Filter out expired or otherwise invalid discounts before applying them.
			$bookingTimestamp = $booking->getDate() ? $booking->getDate()->getTimestamp() : (new SLN_DateTime())->getTimestamp();
			$discounts_to_increment = array_filter($discounts_to_increment, function($discount) use ($bookingTimestamp) {
				$errors = $discount->validateDiscount($bookingTimestamp);
				if (!empty($errors)) {
					SLN_Plugin::addLog(sprintf('[Discount] Skipping invalid discount %d (API path): %s', $discount->getId(), implode(', ', $errors)));
					return false;
				}
				return true;
			});

			// Calculete discount data
			foreach($discounts_to_increment as $discount){
				$data['discounts'][] = $discount->getId();
				$data['discount_'.$discount->getId()] = 1;
				if(empty($data['discount_amount']) || !isset($data['discount_amount'])){
					$data['discount_amount'] = $discount->applyDiscountToBookingServices($bookingServices, true, $booking->getAttendantsIds());
				}else{
					foreach($discount->applyDiscountToBookingServices($bookingServices, true, $booking->getAttendantsIds()) as $sId => $value){
						$data['discount_amount'][$sId] += $value;
					}
				}
				$rules = $discount->getDiscountRules();
				if (!empty($rules)) {
					$discountScores = array();
					foreach($rules as $rule) {
						if ($rule['mode'] === 'score') {
							$discountScores[] = (int)$rule['score_number'];
						}
					}
					if(!empty($discountScores)){
						$discountScores = array(max($discountScores));
					}
					$data["discount_score"] = array_merge(isset($data["discount_score"]) ? $data["discount_score"] : array(), $discountScores);
				}
				// Only count usage for committed bookings. A DRAFT booking is a
			// temporary record created while the customer is still in checkout;
			// counting it here would burn the code before the booking is confirmed
			// and leave customers with an "already used" error if anything goes
			// wrong between apply and confirm.
			if ( $booking->getStatus() !== SLN_Enum_BookingStatus::DRAFT ) {
					$discount->incrementUsagesNumber($booking->getUserId());
					$discount->incrementTotalUsagesNumber();
				}
			}
			// Calculate new service price
			foreach($bookingServices->getItems() as $bookingService){
				$serviceID = $bookingService->getService()->getId();
				$price = $bookingService->getPrice() - $data['discount_amount'][$serviceID];
				$bookingService->setPrice($price);
			}
		}
		$data['services'] = $bookingServices->toArrayRecursive();
		foreach($data as $key => $value){
			$booking->setMeta($key, $value);
		}
	}

	public function hook_admin_init() {
		new SLB_Discount_Metabox_Discount($this->getSlnPlugin(), self::POST_TYPE_DISCOUNT);
	}

	public function hook_admin_enqueue_scripts() {
		wp_enqueue_script('admin-discount', SLN_PLUGIN_URL.'/js/discount/admin-discount.js', array('jquery'), false, true);

		// Enqueue the rules-collection script only on discount create/edit screens
		global $pagenow;
		$is_discount_edit = false;
		if ($pagenow === 'post-new.php' && isset($_GET['post_type']) && sanitize_text_field(wp_unslash($_GET['post_type'])) === self::POST_TYPE_DISCOUNT) {
			$is_discount_edit = true;
		} elseif ($pagenow === 'post.php' && isset($_GET['post'])) {
			$post_id = intval($_GET['post']);
			if ($post_id && get_post_type($post_id) === self::POST_TYPE_DISCOUNT) {
				$is_discount_edit = true;
			}
		}

		if ($is_discount_edit) {
			wp_enqueue_script(
				'salon-customRulesCollections',
				SLN_PLUGIN_URL . '/js/admin/customRulesCollections.js',
				array('jquery'),
				false,
				true
			);
		}
	}

	public function hook_wp_enqueue_scripts() {
		// salon-discount.js is now enqueued via SLN_Action_InitScripts::preloadScripts()
		// which covers both the wp_enqueue_scripts path and the late preloadEnqueueScript()
		// path used by page builders (Kadence, Elementor, etc.).
	}

	public function hook_isSalonPage($ret) {
		global $pagenow, $post;
		$page = sanitize_text_field( $pagenow );
		$_post = $post;
		return (
			$ret ||
	        isset($page) && $page === self::POST_TYPE_DISCOUNT ||
			!empty($_post) && get_post_type($_post) === self::POST_TYPE_DISCOUNT
		);
	}

	public function hook_booking_builder_getTotal($total, $bb){
		$discountData = $bb->get('discount');
		if(!empty($discountData)){
			$total -= $discountData['amount'];
		}
		return $total;
	}

	/**
	 * @param SLN_Wrapper_Booking_Builder $bb
	 */
	public function hook_booking_builder_create($bb) {
		$discountData = $bb->get('discount');

        if (empty($discountData)) {
            $discountItems = SLB_Discount_Helper_DiscountItems::buildDiscountItems();
            $discount      = $discountItems->getDiscountForBB($bb);
            if($discount){
                $discountValue = $discount->applyDiscountToBookingServices($bb->getBookingServices(), false, $bb->getAttendantsIds());
                if($discountValue){
                    $discount      = $discountItems->getDiscountForBB($bb);
                    $discountData['id'] = $discount->getId();
                }
            }
        }
		if (!empty($discountData)) {
			$discountId = $discountData['id'];
			$items      = $bb->get('services');

			if ($items) {
				$discount        = $this->createDiscount($discountId);
				$bookingServices = $bb->getBookingServices();
				$discountValues  = $discount->applyDiscountToBookingServices($bookingServices, true, $bb->getAttendantsIds());
				foreach($items as $i => $item) {
                    if (is_array($item) && array_intersect(array_keys($item), array('service', 'attendant', 'price', 'duration', 'break_duration', 'break_duration_data', 'resource'))) {
                        $sId = $item['service'];
                        $atId = $item['attendant'];
                    } else {
                        $sId = $i;
                        $atId = $item;
                    }
					$price       = $bookingServices->findByService($sId)->getPrice();
					$items[$sId] = array(
						'service'   => $sId,
						'attendant' => $atId,
						'price'     => $price - $discountValues[$sId],
					);
				}

                $discountScores = array();

                $rules = $discount->getDiscountRules();
                if (!empty($rules)) {
                    foreach($rules as $rule) {
                        if ($rule['mode'] === 'score') {
                            $discountScores[] = (int)$rule['score_number'];
                        }
                    }
					if(!empty($discountScores)){
						$discountScores = array(max($discountScores));
					}
                }

			$bb->set('services', $items);
			$bb->set("discount_{$discountId}", true);
			$bb->set("discount_amount", $discountValues);
			$bb->set("discounts", array($discountId));
                $bb->set('discount_score', $discountScores);

			// Do NOT increment usage here: the booking is always DRAFT at this
			// point. Usage is counted in SummaryStep::dispatchForm() once the
			// customer actually confirms, preventing orphaned DRAFTs from
			// consuming a single-use code permanently.
		}
	}

	$bb->set('discount', null);
}

	/**
	 * @param SLN_Shortcode_Salon_SummaryStep $step
	 * @param SLN_Wrapper_Booking_Builder $bb
	 */
	public function hook_summary_dispatchForm_before_booking_creation($step, $bb) {
		$discountData = $bb->get('discount');
		if (!empty($discountData)) {
			$discountId = $discountData['id'];
			$discount   = $this->createDiscount($discountId);

			$errors     = $discount->validateDiscountFullForBB($bb);
			if (!empty($errors)) {
				$bb->set('discount', null);
				$bb->save();
				$step->addError(reset($errors));
			}
		}
	}

	/**
	 * @param SLN_Wrapper_Booking $bb
	 * @param int $size
	 */
	public function hook_summary_before_total_amount($bb, $size) {
		$plugin   = $this->plugin;
        $discountAmount = $bb->getMeta('discount_amount');
        $discountAmount = is_array($discountAmount) ? $discountAmount : [];

		if (!empty($discountAmount)) {
			$discountValue = array_sum($discountAmount);
		} else {
			$discountValue = 0;
		}

		echo $plugin->loadView('shortcode/_salon_summary_before_total_amount', compact('discountValue', 'size'));
	}

	/**
	 * @param SLN_Wrapper_Booking_Builder $bb
	 * @param int $size
	 */
	public function hook_summary_after_total_amount($bb, $size) {
		$plugin   = $this->plugin;

		echo $plugin->loadView('shortcode/_salon_summary_after_total_amount', compact('size'));
	}

	public function hook_history_nav(){
		echo '<li class="sln-account__nav__item sln-account__nav__discounts" role="presentation"><a href="#sln-account__discount__content" data-target="#sln-account__discount__content" aria-controls="sln-account__discount__content" role="tab" data-toggle="tab"><span>'. esc_html__('Discounts', 'salon-booking-system').'</span></a></li>';
	}

	public function hook_history_content($booking_history){
		$customer = new SLN_Wrapper_Customer(get_current_user_id(), false);
		echo $this->plugin->loadView('shortcode/_salon_my_account_content', compact('booking_history', 'customer'));
	}

	/**
	 * @param string $label
	 * @param SLN_Wrapper_Booking $booking
	 *
	 * @return string
	 */
	public function hook_booking_total_amount_label($label, $booking) {
		if (SLB_Discount_Helper_Booking::hasAppliedDiscount($booking)) {
			return __('Discounted price', 'salon-booking-system');
		}

		return $label;
	}

	/**
	 * @param SLN_Wrapper_Booking $booking
	 */
	public function hook_booking_total_amount_row($booking) {
		$plugin = $this->plugin;

		if (SLB_Discount_Helper_Booking::hasAppliedDiscount($booking)) {
			$discounts = SLB_Discount_Helper_Booking::getBookingDiscounts($booking);
			echo $plugin->loadView('metabox/_booking_total_amount_row', compact('discounts'));
		}
	}

	public function get_discounts_html_calc_booking_total($html) {

	    $discountIds = !empty($_POST['_sln_booking_discounts']) ? $_POST['_sln_booking_discounts'] : array();

	    if (empty($discountIds)) {
		return $html;
	    }

	    $discounts = array();
	    foreach ($discountIds as $discountId) {
		$discounts[$discountId] = new SLB_Discount_Wrapper_Discount($discountId);
	    }

	    return $this->plugin->loadView('metabox/_booking_total_amount_row', compact('discounts'));
	}

	/**
	 * @param SLN_Wrapper_Booking $booking
	 * @param string $oldStatus
	 * @param string $newStatus
	 */
	public function hook_booking_setStatus($booking, $oldStatus, $newStatus)
	{
		// Statuses for which usage was actually counted (i.e. the customer confirmed).
		// DRAFT is excluded: it is a temporary placeholder and its usage is never
		// incremented (see hook_api_pre_eval and hook_booking_builder_create guards),
		// so cancelling a DRAFT must not decrement the counter.
		$committedStatuses = array(
			SLN_Enum_BookingStatus::CONFIRMED,
			SLN_Enum_BookingStatus::PAID,
			SLN_Enum_BookingStatus::PAY_LATER,
			SLN_Enum_BookingStatus::PENDING,
			SLN_Enum_BookingStatus::PENDING_PAYMENT,
		);

		if ($oldStatus !== SLN_Enum_BookingStatus::CANCELED
			&& $newStatus === SLN_Enum_BookingStatus::CANCELED
			&& in_array( $oldStatus, $committedStatuses, true )
		) {
			if (SLB_Discount_Helper_Booking::hasAppliedDiscount($booking)) {
				$pt = $booking->getPostType();

				foreach(SLB_Discount_Helper_Booking::getBookingDiscounts($booking) as $discount) {
					$discount->decrementUsagesNumber($booking->getUserId());
					$discount->decrementTotalUsagesNumber();
					delete_post_meta($booking->getId(), "_{$pt}_discount_{$discount->getId()}");
				}

				$discountAmount = $booking->getMeta('discount_amount');

				$bookingServicesArray = $booking->getBookingServices()->toArrayRecursive();
				foreach($bookingServicesArray as &$item) {
					$serviceId      = $item['service'];
					$item['price'] += isset($discountAmount[$serviceId]) ? $discountAmount[$serviceId] : 0;
				}
				unset($item);
				$booking->setMeta('services', $bookingServicesArray);
				$booking->evalBookingServices();
				$booking->evalTotal();

				delete_post_meta($booking->getId(), "_{$pt}_discount_amount");
				delete_post_meta($booking->getId(), "_{$pt}_discounts");
                delete_post_meta($booking->getId(), "_{$pt}_discount_score");
			}
		}
	}

	public function hook_mail_summary_details($booking) {
		$plugin = $this->plugin;

		if (SLB_Discount_Helper_Booking::hasAppliedDiscount($booking)) {
			$discounts  = SLB_Discount_Helper_Booking::getBookingDiscounts($booking);
			$_discounts = array();
			foreach ($discounts as $discount) {
                            $discountAmount = $discount->getAmount();
                            if (!$discountAmount) {
                                continue;
                            }
			    $_discounts[] = $discount->getAmountString(true);
			}
			$discountText = implode(', ', $_discounts);
			echo $plugin->loadView('metabox/_mail_summary_details', compact('discountText'));
		}
	}

	public function hook_mail_special_offer($booking, $customer){
		$criteria = array(
			'@wp_query' => array(
				'meta_query' => array(
					array(
						'key'   => '_' . SLB_Discount_Plugin::POST_TYPE_DISCOUNT . '_email_notify',
						'value' => 1,
					),
				),
			),
                        'post_status' => 'publish',
		);
		$discounts = $this->plugin->getRepository(SLB_Discount_Plugin::POST_TYPE_DISCOUNT)->get($criteria);
		if(empty($discounts)){
			return;
		}
		foreach($discounts as $iter => $discount){
			$error = $discount->validateDiscountForMail((new DateTime())->getTimestamp(), $customer);
			if(!empty($error)){
				unset($discounts[$iter]);
			}
		}
		if(empty($discounts)){
			return;
		}
		$settings = $this->plugin->getSettings();
		echo $this->plugin->loadView('metabox/_mail_special_offer', compact('discounts', 'settings'));
	}

	public function get_discount_amount_ajaxcalendar_booking($discount, $booking) {
		$plugin = $this->plugin;

		if (SLB_Discount_Helper_Booking::hasAppliedDiscount($booking)) {
			$discounts  = $booking->getMeta('discount_amount');
			$_discount  = 0;
			foreach ($discounts as $discount) {
			    $_discount += $discount;
			}
			return $_discount;
		}

		return $discount;
	}

    public function get_fidelity_score_discounts_score($score, $customer) {

        $plugin     = $this->plugin;
        $bookings = $customer->getCompletedBookings();

        foreach($bookings as $booking) {
            if (SLB_Discount_Helper_Booking::hasAppliedDiscount($booking)) {
                $discountScores = $booking->getMeta('discount_score');
                $discountScores = $discountScores && is_array($discountScores) ? $discountScores : array();
                $score += array_sum($discountScores);
            }
        }

		return $score;
	}

	/**
	 * @param $discount
	 * @return SLB_Discount_Wrapper_Discount
	 * @throws Exception
	 */
	public function createDiscount($discount)
	{
		if($discount instanceof SLB_Discount_Wrapper_Discount){
			return $discount;
		}
		return $this->getSlnPlugin()->getRepository(self::POST_TYPE_DISCOUNT)->create($discount);
	}

	public function getSlnPlugin() {
		return $this->plugin;
	}

        public function ajax()
        {
            SLN_TimeFunc::startRealTimezone();

            $method = sanitize_text_field(wp_unslash( $_REQUEST['method'] ));

            $className = 'SLB_Discount_Action_Ajax_'.ucwords($method);

            if ( ! class_exists($className) ) {
                throw new Exception("discount ajax method not found '$method'");
            }

            SLN_Plugin::addLog('discount calling ajax '.$className);

            $obj = new $className($this->getSlnPlugin());

            $ret = $obj->execute();

            SLN_Plugin::addLog("$className returned:\r\n".wp_json_encode($ret));

            if (is_array($ret)) {
                header('Content-Type: application/json');
                echo wp_json_encode($ret);
            } elseif (is_string($ret)) {
                echo $ret;
            } else {
                throw new Exception("no content returned from $className");
            }

            exit();
        }
}