<?php
/**
 * Builds conditional tag option metadata for the email editor UI.
 *
 * @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\Pro\Emails\ConditionalTags\Conditions\TagProxy;
use EDD\Pro\Emails\ConditionalTags\Utilities\Comparison;

/**
 * Creates UI options for the conditional tags inserter.
 *
 * @since 3.6.6
 */
class UiConditionOptions {
	/**
	 * Build UI condition options for the email editor.
	 *
	 * Uses normalized first-class condition metadata and augments it with
	 * comparable email-tag conditions available for the current email context.
	 *
	 * @since 3.6.6
	 *
	 * @param \EDD\Emails\Email $email Current email being edited.
	 * @return array<int, array> Condition options for the editor.
	 */
	public function get_conditions_for_email_editor( $email ) {
		$context             = $this->get_email_context( $email );
		$first_class_options = $this->get_first_class_options_indexed( $context );
		$first_class_options = $this->filter_options_by_context( $first_class_options, $context );
		$conditions          = $this->get_conditions_for_email_editor_from_options( $email, $first_class_options );
		$conditions          = $this->filter_options_by_context( $conditions, $context );

		return array_values( $conditions );
	}

	/**
	 * Build UI condition options starting from first-class options.
	 *
	 * @since 3.6.6
	 *
	 * @param \EDD\Emails\Email $email   Current email being edited.
	 * @param array             $options First-class options keyed by condition key.
	 * @return array<int, array> Condition options for the editor.
	 */
	private function get_conditions_for_email_editor_from_options( $email, $options ) {
		$context   = $this->get_email_context( $email );
		$recipient = $this->get_email_recipient( $email );
		$tags      = $this->get_available_tags( $context, $recipient );
		$supported = TagProxy::get_supported_tags();

		foreach ( $supported as $tag_key => $value_type ) {
			if ( isset( $options[ $tag_key ] ) || ! isset( $tags[ $tag_key ] ) ) {
				continue;
			}

			$options[ $tag_key ] = $this->normalize_ui_option(
				$tag_key,
				array(
					'key'       => $tag_key,
					'label'     => $this->get_tag_label( $tags[ $tag_key ] ),
					'operators' => $this->get_operators_for_value_type( (string) $value_type ),
					'contexts'  => isset( $tags[ $tag_key ]['contexts'] ) ? $tags[ $tag_key ]['contexts'] : array(),
					'inputType' => $this->get_ui_input_type_for_value_type( (string) $value_type ),
				)
			);
		}

		return array_values( $options );
	}

	/**
	 * Filter options to only those valid in the current email context.
	 *
	 * Empty contexts mean the option is available in all contexts.
	 *
	 * @since 3.6.6
	 *
	 * @param array  $options         Condition options.
	 * @param string $current_context Current email context.
	 * @return array Filtered condition options.
	 */
	private function filter_options_by_context( $options, $current_context ) {
		if ( empty( $current_context ) ) {
			return $options;
		}

		return array_filter(
			$options,
			function ( $option ) use ( $current_context ) {
				$contexts = isset( $option['contexts'] ) ? (array) $option['contexts'] : array();
				if ( empty( $contexts ) ) {
					return true;
				}

				return in_array( $current_context, $contexts, true );
			}
		);
	}

	/**
	 * Get first-class options indexed by condition key.
	 *
	 * @since 3.6.6
	 *
	 * @param string $context Optional email context for seed resolution.
	 * @return array<string, array> First-class options keyed by condition key.
	 */
	private function get_first_class_options_indexed( $context = '' ) {
		$options = Loader::get_first_class_condition_metadata_for_ui( $context );
		foreach ( $options as $key => $option ) {
			$options[ $key ] = $this->normalize_ui_option( $key, $option );
		}

		return $options;
	}

	/**
	 * Normalize a UI option to the canonical condition metadata shape.
	 *
	 * @since 3.6.6
	 *
	 * @param string $fallback_key Fallback key when not provided.
	 * @param array  $option       Raw option data.
	 * @return array Normalized condition option.
	 */
	private function normalize_ui_option( $fallback_key, $option ) {
		$key = isset( $option['key'] ) ? (string) $option['key'] : (string) $fallback_key;

		$normalized = array(
			'key'       => $key,
			'label'     => isset( $option['label'] ) ? (string) $option['label'] : '',
			'operators' => array_values( (array) ( isset( $option['operators'] ) ? $option['operators'] : array() ) ),
			'contexts'  => array_values( (array) ( isset( $option['contexts'] ) ? $option['contexts'] : array() ) ),
			'inputType' => $this->normalize_ui_input_type( isset( $option['inputType'] ) ? $option['inputType'] : null ),
		);

		$operator_labels = isset( $option['operatorLabels'] ) ? (array) $option['operatorLabels'] : array();
		if ( ! empty( $operator_labels ) ) {
			$sanitized = array();
			foreach ( $operator_labels as $op => $label ) {
				if ( in_array( $op, $normalized['operators'], true ) ) {
					$sanitized[ $op ] = sanitize_text_field( $label );
				}
			}
			if ( ! empty( $sanitized ) ) {
				$normalized['operatorLabels'] = $sanitized;
			}
		}

		return $normalized;
	}

	/**
	 * Normalize and validate a UI input type descriptor.
	 *
	 * Ensures the descriptor has a valid 'type' key and sanitizes
	 * select options when present.
	 *
	 * @since 3.6.6
	 *
	 * @param mixed $input_type Raw UI input type data.
	 * @return array{type: string, options?: array} Normalized descriptor.
	 */
	private function normalize_ui_input_type( $input_type ) {
		if ( ! is_array( $input_type ) || empty( $input_type['type'] ) ) {
			return array( 'type' => 'text' );
		}

		$allowed_types = array( 'text', 'date', 'select', 'number', 'currency', 'hidden' );
		$type          = in_array( $input_type['type'], $allowed_types, true )
			? $input_type['type']
			: 'text';

		$normalized = array( 'type' => $type );

		if ( 'select' === $type && ! empty( $input_type['options'] ) && is_array( $input_type['options'] ) ) {
			$normalized['options'] = array();
			foreach ( $input_type['options'] as $option ) {
				if ( ! is_array( $option ) || ! isset( $option['value'] ) || '' === (string) $option['value'] ) {
					continue;
				}
				$normalized['options'][] = array(
					'value' => (string) $option['value'],
					'label' => esc_html( isset( $option['label'] ) ? (string) $option['label'] : (string) $option['value'] ),
				);
			}
		}

		if ( 'select' === $type && ! empty( $input_type['search'] ) ) {
			$normalized['search'] = sanitize_key( $input_type['search'] );
		}

		if ( 'select' === $type && ! empty( $input_type['multiple'] ) ) {
			$normalized['multiple'] = true;
		}

		if ( 'select' === $type && ! empty( $input_type['chosen'] ) ) {
			$normalized['chosen'] = true;
		}

		return $normalized;
	}

	/**
	 * Get available email tags keyed by tag slug for a context and recipient.
	 *
	 * @since 3.6.6
	 *
	 * @param string $context   Email context.
	 * @param string $recipient Email recipient.
	 * @return array<string, array> Available email tags keyed by tag slug.
	 */
	private function get_available_tags( $context, $recipient ) {
		$tags    = (array) edd_get_email_tags( $context, $recipient );
		$indexed = array();
		foreach ( $tags as $tag ) {
			if ( empty( $tag['tag'] ) ) {
				continue;
			}

			$indexed[ $tag['tag'] ] = $tag;
		}

		return $indexed;
	}

	/**
	 * Get a UI label for an email tag definition.
	 *
	 * @since 3.6.6
	 *
	 * @param array $tag Email tag definition.
	 * @return string UI label for the email tag.
	 */
	private function get_tag_label( $tag ) {
		if ( ! empty( $tag['label'] ) ) {
			return (string) $tag['label'];
		}

		if ( ! empty( $tag['tag'] ) ) {
			return ucwords( str_replace( '_', ' ', (string) $tag['tag'] ) );
		}

		return '';
	}

	/**
	 * Get supported operators for a comparison value type.
	 *
	 * @since 3.6.6
	 *
	 * @param string $value_type Comparison value type.
	 * @return string[] Supported operators for the value type.
	 */
	private function get_operators_for_value_type( $value_type ) {
		if ( Comparison::TYPE_TEXT === $value_type ) {
			return array( '=', '!=' );
		}

		return Comparison::OPERATORS;
	}

	/**
	 * Get the UI input type descriptor for a comparison value type.
	 *
	 * @since 3.6.6
	 *
	 * @param string $value_type Comparison value type.
	 * @return array{type: string} UI input type descriptor.
	 */
	private function get_ui_input_type_for_value_type( $value_type ) {
		if ( Comparison::TYPE_CURRENCY === $value_type ) {
			return array( 'type' => 'currency' );
		}
		if ( Comparison::TYPE_NUMBER === $value_type ) {
			return array( 'type' => 'number' );
		}

		return array( 'type' => 'text' );
	}

	/**
	 * Resolve the email context from the editor email object.
	 *
	 * @since 3.6.6
	 *
	 * @param \EDD\Emails\Email $email Current email being edited.
	 * @return string Email context.
	 */
	private function get_email_context( $email ) {
		$context = $email->context;

		return ! empty( $context ) ? (string) $context : 'order';
	}

	/**
	 * Resolve the email recipient from the editor email object.
	 *
	 * @since 3.6.6
	 *
	 * @param \EDD\Emails\Email $email Current email being edited.
	 * @return string Email recipient.
	 */
	private function get_email_recipient( $email ) {
		$recipient = $email->recipient;

		return ! empty( $recipient ) ? (string) $recipient : '';
	}
}
