<?php
/**
 * WooCommerce Price Hooks.
 *
 * Hooks into WooCommerce price display filters to replace default prices
 * with Opus Price Builder layouts using Server-Side Rendering.
 *
 * @package OpusPriceBuilder\WooCommerce
 */

declare(strict_types=1);

namespace OpusPriceBuilder\WooCommerce;

use OpusPriceBuilder\Frontend\PriceRenderer;
use OpusPriceBuilder\Locations;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Hooks into WooCommerce price display filters.
 *
 * ## Render Mode
 *
 * By default, PBO uses Server-Side Rendering (SSR) for best performance.
 * You can switch to Web Component mode using the 'oppb_render_mode' filter:
 *
 * @example Switch to Web Component mode globally:
 * ```php
 * add_filter( 'oppb_render_mode', function() {
 *     return 'web_component';
 * });
 * ```
 *
 * @example Use Web Component mode for variable products only:
 * ```php
 * add_filter( 'oppb_render_mode', function( $mode, $product, $location ) {
 *     if ( $product && $product->is_type( 'variable' ) ) {
 *         return 'web_component';
 *     }
 *     return $mode;
 * }, 10, 3 );
 * ```
 *
 * ## Disabling PBO
 *
 * Developers can disable PBO layouts using the 'oppb_use_layout' filter:
 *
 * @example Disable PBO for a custom loop:
 * ```php
 * // Before the loop
 * add_filter( 'oppb_use_layout', '__return_false' );
 *
 * // Your custom product loop here
 * while ( $products->have_posts() ) { ... }
 *
 * // After the loop - restore PBO
 * remove_filter( 'oppb_use_layout', '__return_false' );
 * ```
 *
 * @example Disable PBO for specific products:
 * ```php
 * add_filter( 'oppb_use_layout', function( $use, $product, $location ) {
 *     // Disable for product ID 123
 *     if ( $product->get_id() === 123 ) {
 *         return false;
 *     }
 *     return $use;
 * }, 10, 3 );
 * ```
 */
class PriceHooks {

	/**
	 * Whether the hooks are registered.
	 *
	 * @var bool
	 */
	private static bool $registered = false;

	/**
	 * Register WooCommerce price hooks.
	 *
	 * @return void
	 */
	public static function register(): void {
		if ( self::$registered ) {
			return;
		}

		// Check if WooCommerce is active.
		if ( ! class_exists( 'WooCommerce' ) ) {
			return;
		}

		// Hook into price HTML filters.
		add_filter( 'woocommerce_get_price_html', array( __CLASS__, 'filterPriceHtml' ), 99, 2 );
		add_filter( 'woocommerce_variable_price_html', array( __CLASS__, 'filterVariablePriceHtml' ), 99, 2 );
		add_filter( 'woocommerce_variable_sale_price_html', array( __CLASS__, 'filterVariableSalePriceHtml' ), 99, 2 );

		// Cart and checkout hooks are only enabled if their locations are registered.
		if ( Locations::isValid( 'cart' ) ) {
			add_filter( 'woocommerce_cart_item_price', array( __CLASS__, 'filterCartItemPrice' ), 99, 3 );
		}

		if ( Locations::isValid( 'checkout' ) ) {
			add_filter( 'woocommerce_checkout_cart_item_quantity', array( __CLASS__, 'filterCheckoutItemPrice' ), 99, 3 );
		}

		self::$registered = true;
	}

	/**
	 * Detect the current display location context.
	 *
	 * @param \WC_Product|null $product The product being rendered (optional).
	 * @return string The location key.
	 */
	private static function detectLocation( ?\WC_Product $product = null ): string {
		// Check for cart context (only if cart location is available).
		if ( Locations::isValid( 'cart' ) ) {
			if ( is_cart() || ( defined( 'WOOCOMMERCE_CART' ) && WOOCOMMERCE_CART ) ) {
				return 'cart';
			}
		}

		// Check for checkout context (only if checkout location is available).
		if ( Locations::isValid( 'checkout' ) ) {
			if ( is_checkout() || ( defined( 'WOOCOMMERCE_CHECKOUT' ) && WOOCOMMERCE_CHECKOUT ) ) {
				return 'checkout';
			}
		}

		// On single product page - use simple ID comparison to determine location.
		if ( is_product() || is_singular( 'product' ) ) {
			if ( $product instanceof \WC_Product ) {
				$queried_id = get_queried_object_id();
				// If product ID matches the page's product ID, this is the main product display.
				if ( $queried_id > 0 && $product->get_id() === $queried_id ) {
					return 'single_product';
				}
				// Product ID doesn't match - it's a secondary product in a widget/block/related.
				return 'loop';
			}
			// No product passed - assume single_product context (fallback).
			return 'single_product';
		}

		// Check for shop/archive pages (product loops).
		if (
			is_shop() ||
			is_product_category() ||
			is_product_tag() ||
			is_product_taxonomy() ||
			is_archive()
		) {
			return 'loop';
		}

		// Check if we're in a WooCommerce loop context (related, upsells, cross-sells, etc.).
		if ( function_exists( 'wc_get_loop_prop' ) ) {
			$loop_name = wc_get_loop_prop( 'name' );
			if ( $loop_name ) {
				return 'loop';
			}
		}

		// Not on a typical WooCommerce page - likely a block, shortcode, or widget.
		// Default to loop context for these cases.
		return apply_filters( 'oppb_detect_location', 'loop' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
	}

	/**
	 * Check if PBO layout should be used.
	 *
	 * This is the main control point for developers to disable PBO layouts.
	 *
	 * @param \WC_Product $product  The product.
	 * @param string      $location The display location.
	 * @param string      $scenario The price scenario.
	 * @return bool True if PBO layout should be used.
	 */
	private static function shouldUseLayout( \WC_Product $product, string $location, string $scenario ): bool {
		// Only apply PBO layouts to simple and variable products.
		// Variations are excluded automatically (they have type 'variation').
		// This prevents our layouts from appearing in variation JSON data (price_html field).
		if ( ! in_array( $product->get_type(), array( 'simple', 'variable' ), true ) ) {
			return false;
		}

		/**
		 * Filter whether to use PBO layout for this price display.
		 *
		 * This is the main filter for developers to control PBO usage.
		 * Return false to use the default WooCommerce price display instead.
		 *
		 * @param bool        $use      Whether to use PBO layout. Default true.
		 * @param \WC_Product $product  The product being displayed.
		 * @param string      $location The display location (single_product, loop, cart, checkout).
		 * @return bool Whether to use PBO layout.
		 *
		 * @example Disable PBO for a specific context:
		 * ```php
		 * // Before your custom loop
		 * add_filter( 'oppb_use_layout', '__return_false' );
		 *
		 * // Your product loop...
		 *
		 * // After your loop
		 * remove_filter( 'oppb_use_layout', '__return_false' );
		 * ```
		 */
		$use_layout = apply_filters( 'oppb_use_layout', true, $product, $location ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound

		if ( ! $use_layout ) {
			return false;
		}

		// Check if location is valid/available.
		if ( ! Locations::isValid( $location ) ) {
			return false;
		}

		// Check global setting.
		$global_enabled = get_option( 'oppb_enabled', true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
		if ( ! $global_enabled ) {
			return false;
		}

		// Check if there are any published layouts for this location.
		if ( ! self::hasPublishedLayoutsForLocation( $location, $scenario ) ) {
			return false;
		}

		// Check product-level disable.
		$product_disabled = get_post_meta( $product->get_id(), '_oppb_disabled', true );
		if ( $product_disabled === 'yes' ) {
			return false;
		}

		// Legacy filter for backward compatibility.
		return (bool) apply_filters( 'oppb_is_enabled_for_product', true, $product, $location ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
	}

	/**
	 * Get the render mode for this price display.
	 *
	 * @param \WC_Product $product  The product.
	 * @param string      $location The display location.
	 * @return string 'ssr' or 'web_component'.
	 */
	private static function getRenderMode( \WC_Product $product, string $location ): string {
		/**
		 * Filter the render mode for price display.
		 *
		 * Use this filter to switch between Server-Side Rendering (SSR) and
		 * Web Component rendering modes.
		 *
		 * SSR (default): Prices are fully rendered on the server. Best for SEO
		 * and initial load performance. Styles are scoped per layout.
		 *
		 * Web Component: Prices are rendered via JavaScript. Useful for dynamic
		 * price updates (e.g., variable product selections, AJAX carts).
		 *
		 * @param string      $mode     The render mode. Default 'ssr'.
		 *                              Accepts: 'ssr', 'web_component'.
		 * @param \WC_Product $product  The product being displayed.
		 * @param string      $location The display location.
		 * @return string The render mode to use.
		 *
		 * @example Switch to Web Component mode for variable products:
		 * ```php
		 * add_filter( 'oppb_render_mode', function( $mode, $product, $location ) {
		 *     if ( $product->is_type( 'variable' ) ) {
		 *         return 'web_component';
		 *     }
		 *     return $mode;
		 * }, 10, 3 );
		 * ```
		 *
		 * @example Use Web Component mode globally:
		 * ```php
		 * add_filter( 'oppb_render_mode', function() {
		 *     return 'web_component';
		 * });
		 * ```
		 */
		$mode = apply_filters( 'oppb_render_mode', 'ssr', $product, $location ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound

		// Validate mode.
		if ( ! in_array( $mode, array( 'ssr', 'web_component' ), true ) ) {
			$mode = 'ssr';
		}

		return $mode;
	}

	/**
	 * Render price using the appropriate render mode.
	 *
	 * @param \WC_Product $product  The product.
	 * @param string      $scenario The price scenario.
	 * @param string      $location The display location.
	 * @return string The rendered price HTML.
	 */
	private static function renderPrice( \WC_Product $product, string $scenario, string $location ): string {
		$mode = self::getRenderMode( $product, $location );

		if ( 'web_component' === $mode ) {
			return PriceRenderer::renderWebComponent( $product, $scenario, $location );
		}

		return PriceRenderer::render( $product, $scenario, $location );
	}


	/**
	 * Filter the main price HTML.
	 *
	 * @param string      $price_html The original price HTML.
	 * @param \WC_Product $product    The product.
	 * @return string
	 */
	public static function filterPriceHtml( string $price_html, \WC_Product $product ): string {
		// Return empty price untouched.
		if ( '' === $price_html ) {
			return $price_html;
		}

		// Don't filter in admin unless explicitly enabled.
		if ( is_admin() && ! apply_filters( 'oppb_filter_admin_prices', false ) ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
			return $price_html;
		}

		$location = self::detectLocation( $product );

		$scenario = PriceRenderer::determineScenario( $product );

		// Check if we should use PBO layout.
		if ( ! self::shouldUseLayout( $product, $location, $scenario ) ) {
			return $price_html;
		}

		$oppb_price_html = self::renderPrice( $product, $scenario, $location );
		return $oppb_price_html === '' ? $price_html : $oppb_price_html;
	}

	/**
	 * Filter variable product price HTML.
	 *
	 * @param string               $price_html The original price HTML.
	 * @param \WC_Product_Variable $product    The variable product.
	 * @return string
	 */
	public static function filterVariablePriceHtml( string $price_html, \WC_Product_Variable $product ): string {
		// Return empty price untouched.
		if ( '' === $price_html ) {
			return $price_html;
		}

		if ( is_admin() && ! apply_filters( 'oppb_filter_admin_prices', false ) ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
			return $price_html;
		}

		$location = self::detectLocation( $product );

		$scenario = $product->is_on_sale() ? 'variable_sale' : 'variable_simple';
		if ( ! self::shouldUseLayout( $product, $location, $scenario ) ) {
			return $price_html;
		}

		$oppb_price_html = self::renderPrice( $product, $scenario, $location );
		return $oppb_price_html === '' ? $price_html : $oppb_price_html;
	}

	/**
	 * Filter variable product sale price HTML.
	 *
	 * @param string               $price_html The original price HTML.
	 * @param \WC_Product_Variable $product    The variable product.
	 * @return string
	 */
	public static function filterVariableSalePriceHtml( string $price_html, \WC_Product_Variable $product ): string {
		// Return empty price untouched.
		if ( '' === $price_html ) {
			return $price_html;
		}

		if ( is_admin() && ! apply_filters( 'oppb_filter_admin_prices', false ) ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
			return $price_html;
		}

		$location = self::detectLocation( $product );

		if ( ! self::shouldUseLayout( $product, $location, 'variable_sale' ) ) {
			return $price_html;
		}

		$oppb_price_html = self::renderPrice( $product, 'variable_sale', $location );
		return $oppb_price_html === '' ? $price_html : $oppb_price_html;
	}

	/**
	 * Filter cart item price.
	 *
	 * @param string $price_html    The original price HTML.
	 * @param array  $cart_item     The cart item data.
	 * @param string $cart_item_key The cart item key.
	 * @return string
	 */
	public static function filterCartItemPrice( string $price_html, array $cart_item, string $cart_item_key ): string {
		// Return empty price untouched.
		if ( '' === $price_html ) {
			return $price_html;
		}

		if ( ! isset( $cart_item['data'] ) || ! $cart_item['data'] instanceof \WC_Product ) {
			return $price_html;
		}

		$product = $cart_item['data'];

		$scenario = PriceRenderer::determineScenario( $product );

		if ( ! self::shouldUseLayout( $product, 'cart', $scenario ) ) {
			return $price_html;
		}

		$oppb_price_html = self::renderPrice( $product, $scenario, 'cart' );
		return $oppb_price_html === '' ? $price_html : $oppb_price_html;
	}

	/**
	 * Filter checkout item price/quantity.
	 *
	 * @param string $quantity_html The quantity HTML.
	 * @param array  $cart_item     The cart item data.
	 * @param string $cart_item_key The cart item key.
	 * @return string
	 */
	public static function filterCheckoutItemPrice( string $quantity_html, array $cart_item, string $cart_item_key ): string {
		// Return empty quantity HTML untouched.
		if ( '' === $quantity_html ) {
			return $quantity_html;
		}

		if ( ! isset( $cart_item['data'] ) || ! $cart_item['data'] instanceof \WC_Product ) {
			return $quantity_html;
		}

		$product  = $cart_item['data'];
		$scenario = PriceRenderer::determineScenario( $product );
		if ( ! self::shouldUseLayout( $product, 'checkout', $scenario ) ) {
			return $quantity_html;
		}

		$oppb_price_html = self::renderPrice( $product, $scenario, 'checkout' );

		// Append price to quantity.
		return $oppb_price_html === '' ? $quantity_html : $quantity_html . ' <span class="oppb-checkout-price">' . $price_html . '</span>';
	}

	/**
	 * Check if there are any layouts active for a specific location.
	 *
	 * @param string $location The display location.
	 * @param string $scenario The price scenario.
	 * @return bool
	 */
	private static function hasPublishedLayoutsForLocation( string $location, string $scenario ): bool {
		static $cache = array();

		if ( isset( $cache[ $location ] ) ) {
			return $cache[ $location ];
		}

		global $wpdb;
		$table = $wpdb->prefix . 'oppb_layouts';

		// Get all layouts and check active_locations JSON.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$layouts = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT active_locations FROM {$table} WHERE scenario = %s",
				$scenario
			)
		);

		$has_active = false;
		if ( $layouts ) {
			foreach ( $layouts as $layout ) {
				if ( ! empty( $layout->active_locations ) ) {
					$active_locations = json_decode( $layout->active_locations, true );
					if ( is_array( $active_locations ) && ! empty( $active_locations[ $location ] ) && $active_locations[ $location ] === true ) {
						$has_active = true;
						break;
					}
				}
			}
		}

		$cache[ $location ] = $has_active;

		return $cache[ $location ];
	}


	/**
	 * Unregister hooks.
	 *
	 * @return void
	 */
	public static function unregister(): void {
		remove_filter( 'woocommerce_get_price_html', array( __CLASS__, 'filterPriceHtml' ), 99 );
		remove_filter( 'woocommerce_variable_price_html', array( __CLASS__, 'filterVariablePriceHtml' ), 99 );
		remove_filter( 'woocommerce_variable_sale_price_html', array( __CLASS__, 'filterVariableSalePriceHtml' ), 99 );
		remove_filter( 'woocommerce_cart_item_price', array( __CLASS__, 'filterCartItemPrice' ), 99 );
		remove_filter( 'woocommerce_checkout_cart_item_quantity', array( __CLASS__, 'filterCheckoutItemPrice' ), 99 );

		self::$registered = false;
	}
}
