<?php
/**
 * Loader for Conditional Email Tags functionality.
 *
 * @package     EDD\Pro\Emails\ConditionalTags
 * @copyright   Copyright (c) 2026, Sandhills Development, LLC
 * @license     https://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       3.6.6
 */

namespace EDD\Pro\Emails\ConditionalTags;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore

use EDD\EventManagement\SubscriberInterface;
use EDD\Pro\Emails\ConditionalTags\Conditions\Category;
use EDD\Pro\Emails\ConditionalTags\Conditions\OrderDiscount;
use EDD\Pro\Emails\ConditionalTags\Conditions\Download;
use EDD\Pro\Emails\ConditionalTags\Conditions\FirstPurchase;
use EDD\Pro\Emails\ConditionalTags\Conditions\GuestPurchase;
use EDD\Pro\Emails\ConditionalTags\Conditions\PaymentMethod;
use EDD\Pro\Emails\ConditionalTags\Conditions\TagProxy;
use EDD\Pro\Emails\ConditionalTags\Utilities\LogicalExpression;
use EDD\Pro\Emails\ConditionalTags\Utilities\Tags;

/**
 * Class Loader
 *
 * @since 3.6.6
 */
class Loader implements SubscriberInterface {

	/**
	 * Condition registry instance.
	 *
	 * @since 3.6.6
	 * @var ConditionRegistry
	 */
	private $condition_registry;

	/**
	 * Whether conditions have been registered.
	 *
	 * @since 3.6.6
	 * @var bool
	 */
	private $conditions_registered = false;

	/**
	 * Whether tag proxies have been registered.
	 *
	 * @since 3.6.6
	 * @var bool
	 */
	private $proxies_registered = false;

	/**
	 * Loader constructor.
	 *
	 * @since 3.6.6
	 */
	public function __construct() {
		$this->condition_registry = new ConditionRegistry();
	}

	/**
	 * Returns an array of events that this subscriber wants to listen to.
	 *
	 * @since 3.6.6
	 * @return array
	 */
	public static function get_subscribed_events() {
		return array(
			'edd_add_email_tags'     => array( 'register_tag_proxies', 999 ),
			'edd_email_content_tags' => array( 'process_conditional_content', 99, 5 ),
			'edd_email_editor_top'   => array( 'setup_inserter', 8, 1 ),
		);
	}

	/**
	 * Register all built-in conditions and allow external extensions.
	 *
	 * @since 3.6.6
	 * @return void
	 */
	public function register_conditions() {
		if ( $this->conditions_registered ) {
			return;
		}

		$conditions = array(
			new PaymentMethod(),
			new OrderDiscount(),
			new Download(),
			new Category(),
			new GuestPurchase(),
			new FirstPurchase(),
		);

		foreach ( $conditions as $condition ) {
			$this->condition_registry->register( $condition );
		}

		/**
		 * Allow third-party code to register custom conditions.
		 *
		 * @since 3.6.6
		 *
		 * @param ConditionRegistry $registry The condition registry instance.
		 */
		do_action( 'edd_email_conditional_tags_register_conditions', $this->condition_registry );

		$this->conditions_registered = true;
	}

	/**
	 * Register TagProxy conditions for every EDD email tag.
	 *
	 * @since 3.6.6
	 * @return void
	 */
	public function register_tag_proxies() {
		if ( $this->proxies_registered ) {
			return;
		}

		$this->register_conditions();

		$supported_tags = TagProxy::get_supported_tags();
		foreach ( array_keys( $supported_tags ) as $key ) {
			$tag = Tags::get_tag_by_name( $key );
			if ( empty( $tag ) ) {
				continue;
			}

			$contexts = ! empty( $tag['contexts'] ) ? $tag['contexts'] : null;

			if ( $this->condition_registry->has( $key ) ) {
				continue;
			}

			$this->condition_registry->register( new TagProxy( $key, $contexts ) );
		}

		$this->proxies_registered = true;
	}

	/**
	 * Process conditional email content blocks.
	 *
	 * @since 3.6.6
	 *
	 * @param string $content    The email content.
	 * @param int    $object_id  The EDD object ID.
	 * @param object $edd_object The EDD object.
	 * @param object $email      The EDD email object.
	 * @param mixed  $context    The email context (order, customer, etc.).
	 * @return string
	 */
	public function process_conditional_content( $content, $object_id, $edd_object, $email, $context = '' ) {
		$pattern = '/\{if\s+([^\}]+)\}(.*?)\{\/if\}/is';
		// Skip all conditional parsing when content has no {if ...} blocks.
		if ( ! preg_match( $pattern, $content ) ) {
			return $content;
		}

		// Conditions require an EDD object to evaluate against. When the
		// object is null (e.g. the first order-context pass in a multi-pass
		// subscription email), skip processing entirely so a later pass
		// with the actual object can handle the blocks.
		if ( null === $edd_object ) {
			return $content;
		}

		$this->register_tag_proxies();

		$registry = $this->condition_registry;

		/*
		 * Note: Nested {if} blocks are not supported with the regex-based parser.
		 * The first closing {/if} will terminate the match.
		 */
		$content = preg_replace_callback(
			$pattern,
			function ( $matches ) use ( $registry, $object_id, $edd_object, $email, $context ) {
				$cond_str = $matches[1];
				$block    = $matches[2];

				try {
					$parsed = LogicalExpression::parse_expression( $cond_str );
					$result = LogicalExpression::evaluate_expression( $parsed, $registry, $object_id, $edd_object, $email, $context );
				} catch ( \Throwable $e ) {
					return '';
				}

				return $result ? $block : '';
			},
			$content
		);

		// Remove empty paragraphs left behind after conditional blocks are stripped.
		$content = preg_replace( '/<p>\s*<\/p>/i', '', $content );

		return $content;
	}

	/**
	 * Get first-class condition metadata for UI consumers.
	 *
	 * First-class conditions are built-in condition classes and custom
	 * conditions registered via the registration hook. TagProxy instances are
	 * excluded from this payload.
	 *
	 * @since 3.6.6
	 *
	 * @param string $context Optional email context for seed resolution.
	 * @return array<string, array> First-class metadata keyed by condition key.
	 */
	public function get_first_class_condition_metadata( $context = '' ) {
		$this->register_conditions();

		return $this->condition_registry->get_first_class_condition_metadata( $context );
	}

	/**
	 * Get first-class condition metadata without requiring an existing loader instance.
	 *
	 * Useful for UI initialization code that only needs condition metadata.
	 *
	 * @since 3.6.6
	 *
	 * @param string $context Optional email context for seed resolution.
	 * @return array<string, array> First-class metadata keyed by condition key.
	 */
	public static function get_first_class_condition_metadata_for_ui( $context = '' ) {
		$loader = new self();

		return $loader->get_first_class_condition_metadata( $context );
	}

	/**
	 * Sets up the conditional tags inserter on the email editor screen.
	 *
	 * @since 3.6.6
	 *
	 * @param \EDD\Emails\Templates\EmailTemplate $email The email object.
	 * @return void
	 */
	public function setup_inserter( $email ) {
		$inserter = new Inserter();
		$inserter->setup( $email );
	}
}
