<?php
/**
 * Condition: Order contains specific download(s).
 *
 * Evaluates whether the order contains one or more of the specified
 * download IDs. Supports comma-separated IDs for checking multiple
 * downloads at once.
 *
 * Uses set-membership semantics:
 *   = (equals)     : Returns true if the order contains ANY of the specified downloads.
 *   != (not equals) : Returns true if the order contains NONE of the specified downloads.
 *
 * Ordering operators (>, >=, <, <=) are not applicable and return false.
 *
 * @package EDD\Pro\Emails\ConditionalTags\Conditions
 * @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\Conditions;

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

use EDD\Pro\Emails\ConditionalTags\Conditions\Traits\ParseData;

/**
 * Class Download
 *
 * @since 3.6.6
 */
class Download extends AbstractCondition {

	use ParseData;

	/**
	 * Supported operators.
	 *
	 * @since 3.6.6
	 * @var string[]
	 */
	protected $supported_operators = array( '=', '!=' );

	/**
	 * Supported contexts.
	 *
	 * @since 3.6.6
	 * @var string[]
	 */
	protected $supported_contexts = array( 'order' );

	/**
	 * Get the key for this condition.
	 *
	 * @since 3.6.6
	 *
	 * @return string Condition key.
	 */
	public function get_key() {
		return 'download';
	}

	/**
	 * Get the label for this condition.
	 *
	 * @since 3.6.6
	 *
	 * @return string Condition label.
	 */
	public function get_label() {
		return edd_get_label_plural();
	}

	/**
	 * Get custom operator labels for the UI.
	 *
	 * @since 3.6.6
	 *
	 * @return array<string, string>
	 */
	public function get_operator_labels() {
		return array(
			'='  => __( 'contains', 'easy-digital-downloads' ),
			'!=' => __( 'does not contain', 'easy-digital-downloads' ),
		);
	}

	/**
	 * Get the UI input type descriptor for this condition.
	 *
	 * Returns a searchable select dropdown seeded with published downloads.
	 * The 'search' key signals that Chosen/Tom Select AJAX search should
	 * be enabled using the `edd_download_search` endpoint.
	 *
	 * @since 3.6.6
	 *
	 * @return array{type: string, options: array, search: string} UI input type descriptor.
	 */
	public function get_ui_input_type() {
		$downloads = get_posts(
			array(
				'post_type'      => 'download',
				'posts_per_page' => 30,
				'post_status'    => 'publish',
				'orderby'        => 'title',
				'order'          => 'ASC',
			)
		);

		$options = array();
		foreach ( $downloads as $download ) {
			$options[] = array(
				'value' => (string) $download->ID,
				'label' => $download->post_title,
			);
		}

		return array(
			'type'     => 'select',
			'options'  => $options,
			'search'   => 'download',
			'multiple' => true,
		);
	}

	/**
	 * Evaluate if the order contains (or does not contain) the given download IDs.
	 *
	 * @since 3.6.6
	 *
	 * @param int    $object_id  The EDD object ID.
	 * @param object $edd_object The EDD object.
	 * @param object $email      The EDD email object.
	 * @param mixed  $value      Download ID(s) to check for (comma-separated).
	 * @param string $operator   The comparison operator (= or !=).
	 * @param mixed  $context    Additional contextual data for evaluation.
	 * @return bool
	 */
	public function evaluate( $object_id, $edd_object, $email, $value, $operator = '=', $context = '' ) {
		if ( ! $this->is_context_allowed( $context ) ) {
			return false;
		}

		if ( ! in_array( $operator, $this->supported_operators, true ) ) {
			return false;
		}

		$expected_ids = $this->parse_ids( $value );
		$expected_ids = $this->get_existing_download_ids( $expected_ids );
		if ( empty( $expected_ids ) ) {
			return '!=' === $operator;
		}

		$actual_ids = $this->get_order_product_ids( $edd_object );
		if ( empty( $actual_ids ) ) {
			return '!=' === $operator;
		}

		$has_match = ! empty( array_intersect( $expected_ids, $actual_ids ) );

		return '!=' === $operator ? ! $has_match : $has_match;
	}

	/**
	 * Keep only IDs that currently exist as EDD download products.
	 *
	 * Validates each ID against the database to ensure the download
	 * hasn't been deleted since the condition was written.
	 *
	 * @since 3.6.6
	 *
	 * @param int[] $ids Parsed IDs from the condition value.
	 * @return int[]
	 */
	private function get_existing_download_ids( $ids ) {
		if ( empty( $ids ) ) {
			return array();
		}

		$existing_ids = array();
		foreach ( $ids as $id ) {
			$download = edd_get_download( $id );
			if ( ! $download || empty( $download->ID ) ) {
				continue;
			}

			$existing_ids[] = absint( $download->ID );
		}

		return array_values( array_unique( $existing_ids ) );
	}
}
