<?php
namespace Tooto_Elementor;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

class Product_Price_Range_Shortcode {

	/**
	 * Instance of this class.
	 *
	 * @var Product_Price_Range_Shortcode
	 */
	private static $instance = null;

	/**
	 * Get instance of this class.
	 *
	 * @return Product_Price_Range_Shortcode
	 */
	public static function get_instance() {
		if ( ! isset( self::$instance ) ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Constructor.
	 */
	public function __construct() {
		\add_shortcode( 'tooto_product_price_range', array( $this, 'render_shortcode' ) );
	}

	/**
	 * Render the shortcode.
	 *
	 * @param array $atts Shortcode attributes.
	 * @return string Shortcode output.
	 */
	public function render_shortcode( $atts ) {
		$atts = \shortcode_atts(
			array(
				'product_id' => '',
			),
			$atts,
			'tooto_product_price_range'
		);

		$product_id = $atts['product_id'];

		// If no product ID is provided, try to get the current global product.
		if ( empty( $product_id ) ) {
			global $product;
			if ( $product instanceof \WC_Product ) {
				$product_id = $product->get_id();
			}
		}

		if ( empty( $product_id ) ) {
			return '';
		}

		$product = \wc_get_product( $product_id );

		if ( ! $product ) {
			return '';
		}

		// Try to get prices from Discount Rules plugin first
		$range_html = $this->get_discount_price_range( $product );

		// Fallback to standard WooCommerce price if no discount range found
		if ( empty( $range_html ) ) {
			// For variable products, get_price_html() usually returns "Min - Max".
			// For simple products, it returns price.
			// We might want to enforce the styling.
			$range_html = $this->format_standard_price( $product );
		}

		if ( empty( $range_html ) ) {
			return '';
		}

		// Remove strikethrough price (original price) if present
		$range_html = preg_replace( '/<del>.*?<\/del>/s', '', $range_html );

		return '<div class="tooto-price-range">' . $range_html . '</div>';
	}

	/**
	 * Get price range from Discount Rules plugin.
	 *
	 * @param \WC_Product $product Product object.
	 * @return string|null Formatted range or null.
	 */
	private function get_discount_price_range( $product ) {
		// Capture the output of the Discount Rules plugin's table.
		$prices = array();
		
		// Priority 1: Try to get prices directly from the plugin logic (API)
		// This is more reliable than HTML parsing, especially on archive pages where HTML might be empty or incomplete.
		if ( class_exists( '\Wdr\App\Controllers\ManageDiscount' ) ) {
			// Initialize calculator if not set (which might happen on archive pages)
			if ( ! isset( \Wdr\App\Controllers\ManageDiscount::$calculator ) ) {
				// Trigger rule loading to initialize calculator
				$manage_discount = new \Wdr\App\Controllers\ManageDiscount();
				// Ensure rules are loaded
				$manage_discount->getDiscountRules();
			}

			// Check if calculator is available
			if ( isset( \Wdr\App\Controllers\ManageDiscount::$calculator ) && \Wdr\App\Controllers\ManageDiscount::$calculator instanceof \Wdr\App\Controllers\DiscountCalculator ) {
				// 1. Try to get Bulk Discount Ranges
				$ranges = \Wdr\App\Controllers\ManageDiscount::$calculator->getDefaultLayoutMessagesByRules( $product );
				
				if ( ! empty( $ranges ) && is_array( $ranges ) ) {
					foreach ( $ranges as $key => $range ) {
						if ( isset( $range['discounted_price'] ) ) {
							$prices[] = floatval( $range['discounted_price'] );
						}
					}
				}

				// 2. Try to get Single Quantity Discount (for simple discounts that don't appear in bulk table)
				// Only if we haven't found any ranges, OR we want to ensure we catch the base discount.
				// Actually, we should always add this because bulk table might not cover qty=1.
				$single_price_data = \Wdr\App\Controllers\ManageDiscount::calculateInitialAndDiscountedPrice( $product, 1 );
				if ( ! empty( $single_price_data ) && is_array( $single_price_data ) && isset( $single_price_data['discounted_price'] ) ) {
					$prices[] = floatval( $single_price_data['discounted_price'] );
				}
			}
		}

		// Priority 2: Fallback to HTML parsing if API returned no prices
		if ( empty( $prices ) ) {
			\ob_start();
			\do_action( 'advanced_woo_discount_rules_load_discount_table', $product );
			$html = \ob_get_clean();

			if ( ! empty( $html ) ) {
				// Parse the HTML to extract prices.
				$prices = $this->extract_prices_from_html( $html );
			}
		}

		if ( empty( $prices ) ) {
			return null;
		}

		// Add current product price to the list (in case discount is only for higher quantities)
		// But wait, the base price is usually the max price.
		// Let's just use the extracted prices + regular price.
		// $regular_price = $product->get_price();
		// if ( is_numeric( $regular_price ) ) {
		// 	$prices[] = floatval( $regular_price );
		// }

		$min_price = min( $prices );
		$max_price = max( $prices );

		// Format prices with currency
		if ( $min_price == $max_price ) {
			return $this->format_price_value( $min_price );
		} else {
			return $this->format_price_value( $min_price ) . ' ~ ' . $this->format_price_value( $max_price );
		}
	}

	/**
	 * Format price value.
	 *
	 * @param float|string $price Price value.
	 * @return string Formatted price or TBD.
	 */
	private function format_price_value( $price ) {
		if ( 0 == $price || '' === $price ) {
			return '<span class="woocommerce-Price-amount amount">TBD</span>';
		}
		return \wc_price( $price );
	}

	/**
	 * Extract numerical prices from the HTML table.
	 *
	 * @param string $html HTML content.
	 * @return array Array of float prices.
	 */
	private function extract_prices_from_html( $html ) {
		$prices = array();

		// Use DOMDocument to parse HTML.
		$dom = new \DOMDocument();
		// Suppress warnings for invalid HTML.
		libxml_use_internal_errors( true );
		// Hack to handle UTF-8 correctly.
		$dom->loadHTML( '<?xml encoding="utf-8" ?>' . $html );
		libxml_clear_errors();

		$xpath = new \DOMXPath( $dom );

		// Find price nodes.
		// Try .wdr_table_discounted_price first.
		$nodes = $xpath->query( '//*[contains(@class, "wdr_table_discounted_price")]' );
		
		foreach ( $nodes as $node ) {
			$text = trim( $node->textContent );
			if ( ! empty( $text ) ) {
				$price = $this->parse_price( $text );
				if ( $price !== null ) {
					$prices[] = $price;
				}
			}
		}

		// Fallback to .wdr_table_discounted_value
		if ( empty( $prices ) ) {
			$nodes = $xpath->query( '//*[contains(@class, "wdr_table_discounted_value")]' );
			foreach ( $nodes as $node ) {
				$text = trim( $node->textContent );
				if ( ! empty( $text ) ) {
					$price = $this->parse_price( $text );
					if ( $price !== null ) {
						$prices[] = $price;
					}
				}
			}
		}

		return $prices;
	}

	/**
	 * Parse price string to float.
	 * Removes currency symbols and separators.
	 *
	 * @param string $price_str Price string.
	 * @return float|null Price value.
	 */
	private function parse_price( $price_str ) {
		// Remove HTML entities
		$price_str = html_entity_decode( $price_str );
		// Remove non-breaking spaces
		$price_str = str_replace( "\xC2\xA0", ' ', $price_str );
		
		// Remove currency symbols and everything that is not a digit or decimal separator.
		// This is tricky because of different locales (comma vs dot).
		// Assuming standard WC setup or user's locale.
		// A robust way in WC is to use wc_format_decimal if we have the clean number,
		// but here we have a formatted string.
		
		// Remove everything except digits, dots and commas.
		$clean = preg_replace( '/[^0-9\.,]/', '', $price_str );
		
		if ( empty( $clean ) ) {
			return null;
		}

		// Detect decimal separator.
		// If there is a comma and a dot, the last one is decimal.
		// If only one, usually dot is decimal, unless it's like 1,000 (which is 1000).
		// But in prices like 1.000,00 it's comma.
		
		// Let's use a simpler heuristic:
		// If it contains both, replace the first one with empty, and the second with dot.
		// Actually, let's just use filter_var if possible, but that handles simple floats.
		
		// Let's rely on WC settings if possible, but we are in a shortcode.
		$decimal_sep = \wc_get_price_decimal_separator();
		$thousand_sep = \wc_get_price_thousand_separator();
		
		// Remove thousand separator
		if ( $thousand_sep ) {
			$clean = str_replace( $thousand_sep, '', $clean );
		}
		// Replace decimal separator with dot
		if ( $decimal_sep && $decimal_sep !== '.' ) {
			$clean = str_replace( $decimal_sep, '.', $clean );
		}
		
		return floatval( $clean );
	}

	/**
	 * Format standard product price.
	 *
	 * @param \WC_Product $product Product object.
	 * @return string HTML.
	 */
	private function format_standard_price( $product ) {
		if ( $product->is_type( 'variable' ) ) {
			// Use the lowest active price
			$min_price = $product->get_variation_price( 'min', true );
			$max_price = $product->get_variation_price( 'max', true );
			
			if ( $min_price == $max_price ) {
				return $this->format_price_value( $min_price );
			}
			
			return $this->format_price_value( $min_price ) . ' ~ ' . $this->format_price_value( $max_price );
		} else {
			// Apply TBD filter logic to standard price html as well if needed, 
			// but here we rely on the product method which might be filtered globally.
			// However, to ensure consistency within this shortcode, we should check for 0.
			
			if ( 0 == $product->get_price() ) {
				return $this->format_price_value( 0 );
			}
			
			return $product->get_price_html();
		}
	}
}
