<?php
/**
 * Registers the cart recovery email tags.
 *
 * @package EDD\Pro\CartRecovery\Emails
 * @copyright Copyright (c) 2026, Sandhills Development, LLC
 * @license https://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since 3.6.5
 */

namespace EDD\Pro\CartRecovery\Emails;

use EDD\EventManagement\SubscriberInterface;
use EDD\Pro\CartRecovery\Config\Emails;
use EDD\Pro\CartRecovery\Database\Queries\RecoveryCart;

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

/**
 * Registers the cart recovery email tags.
 */
class Tags implements SubscriberInterface {

	/**
	 * Events subscribed to.
	 *
	 * @since 3.6.5
	 * @return array
	 */
	public static function get_subscribed_events(): array {
		return array(
			'edd_add_email_tags' => 'register',
		);
	}

	/**
	 * Registers the cart recovery email tags.
	 *
	 * @since 3.6.5
	 * @return void
	 */
	public function register(): void {
		$tags = array(
			'cart_link'          => array(
				'label'       => __( 'Cart Link', 'easy-digital-downloads' ),
				'description' => __( 'The link to the cart recovery page.', 'easy-digital-downloads' ),
				'function'    => array( $this, 'cart_link' ),
			),
			'cart_items'         => array(
				'label'       => __( 'Cart Items', 'easy-digital-downloads' ),
				'description' => __( 'The items in the cart.', 'easy-digital-downloads' ),
				'function'    => array( $this, 'cart_items' ),
			),
			'recovery_expires'   => array(
				'label'       => __( 'Cart Expiration Date', 'easy-digital-downloads' ),
				'description' => __( 'The date and time the cart will expire.', 'easy-digital-downloads' ),
				'function'    => array( $this, 'recovery_expires' ),
			),
			'customer_firstname' => array(
				'label'       => __( 'Customer First Name', 'easy-digital-downloads' ),
				'description' => __( 'Customer\'s first name.', 'easy-digital-downloads' ),
				'function'    => array( $this, 'customer_firstname' ),
			),
			'customer_lastname'  => array(
				'label'       => __( 'Customer Last Name', 'easy-digital-downloads' ),
				'description' => __( 'Customer\'s last name.', 'easy-digital-downloads' ),
				'function'    => array( $this, 'customer_lastname' ),
			),
			'customer_email'     => array(
				'label'       => __( 'Customer Email', 'easy-digital-downloads' ),
				'description' => __( 'Customer\'s email address.', 'easy-digital-downloads' ),
				'function'    => array( $this, 'customer_email' ),
			),
			'cart_total'         => array(
				'label'       => __( 'Cart Total', 'easy-digital-downloads' ),
				'description' => __( 'Cart total with currency.', 'easy-digital-downloads' ),
				'function'    => array( $this, 'cart_total' ),
			),
			'cart_count'         => array(
				'label'       => __( 'Cart Count', 'easy-digital-downloads' ),
				'description' => __( 'Number of items in cart.', 'easy-digital-downloads' ),
				'function'    => array( $this, 'cart_count' ),
			),
			'unsubscribe_link'   => array(
				'label'       => __( 'Unsubscribe Link', 'easy-digital-downloads' ),
				'description' => __( 'The link to unsubscribe from the cart recovery email. This is required for all cart recovery emails.', 'easy-digital-downloads' ),
				'function'    => array( $this, 'unsubscribe_link' ),
			),
		);

		foreach ( $tags as $tag => $data ) {
			edd_add_email_tag( $tag, $data['description'], $data['function'], $data['label'], array( 'abandoned_cart' ), array( 'customer' ) );
		}
	}

	/**
	 * Gets cart recovery link.
	 *
	 * @since 3.6.5
	 * @param int                                   $cart_id The ID of the cart.
	 * @param \EDD\Pro\CartRecovery\Carts\Cart|null $cart    The cart object.
	 * @param \EDD\Emails\Types\Email|null          $email   The email object.
	 * @return string The cart recovery link.
	 */
	public function cart_link( $cart_id, $cart, $email = null ): string {
		// Use the token from the email object if available (it's already been stored in the database).
		if ( $email && $email instanceof \EDD\Pro\CartRecovery\Emails\Types\Base ) {
			$token = $email->get_token();
		} else {
			// Fallback for cases where email object is not available (shouldn't happen in normal flow).
			$token_data = $this->issue_recovery_token( $cart_id, $cart );
			$token      = $token_data['token'];
		}

		$args = array(
			'edd_action' => 'cart_recovery_restore',
			'token'      => $token,
		);

		// Add discount code to the URL if configured for this email.
		$discount_code = $this->get_discount_code( $email );
		if ( ! empty( $discount_code ) ) {
			$args['acr-discount'] = $discount_code;
		}

		return add_query_arg( $args, edd_get_checkout_uri() );
	}

	/**
	 * Formats cart items for email display.
	 *
	 * @since 3.6.5
	 * @param int                                   $cart_id The ID of the cart.
	 * @param \EDD\Pro\CartRecovery\Carts\Cart|null $cart    The cart object.
	 * @param \EDD\Emails\Types\Email|null          $email   The email object.
	 * @return string Formatted cart items HTML.
	 */
	public function cart_items( $cart_id, $cart, $email = null ): string {
		if ( empty( $cart ) && $cart_id > 0 ) {
			$cart_query = new RecoveryCart();
			$cart       = $cart_query->get_item( $cart_id );
		}

		if ( ! $cart instanceof \EDD\Pro\CartRecovery\Carts\Cart ) {
			return '';
		}

		// Use the Cart object's get_items() method for consistent decoding.
		$items = $cart->get_items();

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

		// Collect all download IDs from the cart.
		$download_ids = array();
		foreach ( $items as $item ) {
			if ( ! empty( $item['id'] ) ) {
				$download_ids[] = (int) $item['id'];
			}
		}

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

		// Fetch all download data in a single database query using the EDD query class when available.
		$downloads_by_id = array();
		foreach ( $download_ids as $download_id ) {
			$download = edd_get_download( $download_id );
			if ( $download ) {
				$downloads_by_id[ $download_id ] = $download;
			}
		}

		$items_html = '';
		// Loop over the original cart items and use our pre-fetched data.
		foreach ( $items as $item ) {
			if ( empty( $item['id'] ) ) {
				continue;
			}

			$download  = $downloads_by_id[ $item['id'] ] ?? null; // Get the full object from our map when available.
			$item_name = $download ? $download->get_name() : ( ! empty( $item['name'] ) ? $item['name'] : __( 'Unknown Item', 'easy-digital-downloads' ) );

			$items_html .= '<div class="edd-acr-cart-item" style="border: 1px solid #ddd; padding: 15px; margin: 10px 0; background-color: #f9f9f9; border-radius: 4px;">';
			$items_html .= '<h4>' . esc_html( $item_name ) . '</h4>';

			if ( ! empty( $item['options'] ) && is_array( $item['options'] ) ) {
				$items_html .= '<p>' . self::format_cart_item_options( $item['options'] ) . '</p>';
			}

			$items_html .= '<p><strong>' . __( 'Price:', 'easy-digital-downloads' ) . '</strong> ';
			$items_html .= edd_currency_filter( edd_format_amount( $item['price'] ?? 0 ), $cart->currency );
			$items_html .= '</p>';
			$items_html .= '</div>';
		}

		return $items_html;
	}

	/**
	 * Gets cart expiration date.
	 *
	 * @since 3.6.5
	 * @param int                                   $cart_id The ID of the cart.
	 * @param \EDD\Pro\CartRecovery\Carts\Cart|null $cart    The cart object.
	 * @param \EDD\Emails\Types\Email|null          $email   The email object.
	 * @return string Formatted expiration date.
	 */
	public function recovery_expires( $cart_id, $cart, $email = null ): string {
		$retention_days  = edd_get_option( 'acr_retention_days', 30 );
		$created_time    = strtotime( $cart->date_created ?? 'now' );
		$expiration_time = $created_time + ( $retention_days * DAY_IN_SECONDS );

		return date_i18n( get_option( 'date_format' ), $expiration_time );
	}

	/**
	 * Gets customer first name.
	 *
	 * @since 3.6.5
	 * @param int                                   $cart_id The ID of the cart.
	 * @param \EDD\Pro\CartRecovery\Carts\Cart|null $cart    The cart object.
	 * @param \EDD\Emails\Types\Email|null          $email   The email object.
	 * @return string Customer first name.
	 */
	public function customer_firstname( $cart_id, $cart, $email = null ): string {
		if ( empty( $cart ) && $cart_id > 0 ) {
			$cart_query = new RecoveryCart();
			$cart       = $cart_query->get_item( $cart_id );
		}

		if ( ! $cart ) {
			return __( 'Customer', 'easy-digital-downloads' );
		}

		if ( $cart instanceof \EDD\Pro\CartRecovery\Carts\Cart ) {
			$name_data = $cart->get_customer_name();

			return ! empty( $name_data['first_name'] ) ? $name_data['first_name'] : __( 'Customer', 'easy-digital-downloads' );
		}

		// Fallback for non-Cart objects (e.g. preview mock data).
		if ( ! empty( $cart->email ) ) {
			$parts = explode( '@', $cart->email );

			return ucfirst( sanitize_text_field( $parts[0] ) );
		}

		return __( 'Customer', 'easy-digital-downloads' );
	}

	/**
	 * Gets customer last name.
	 *
	 * @since 3.6.5
	 * @param int                                   $cart_id The ID of the cart.
	 * @param \EDD\Pro\CartRecovery\Carts\Cart|null $cart    The cart object.
	 * @param \EDD\Emails\Types\Email|null          $email   The email object.
	 * @return string Customer last name.
	 */
	public function customer_lastname( $cart_id, $cart, $email = null ): string {
		if ( empty( $cart ) && $cart_id > 0 ) {
			$cart_query = new RecoveryCart();
			$cart       = $cart_query->get_item( $cart_id );
		}

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

		if ( $cart instanceof \EDD\Pro\CartRecovery\Carts\Cart ) {
			$name_data = $cart->get_customer_name();

			return ! empty( $name_data['last_name'] ) ? $name_data['last_name'] : '';
		}

		return '';
	}

	/**
	 * Gets customer email.
	 *
	 * @since 3.6.5
	 * @param int                                   $cart_id The ID of the cart.
	 * @param \EDD\Pro\CartRecovery\Carts\Cart|null $cart    The cart object.
	 * @param \EDD\Emails\Types\Email|null          $email   The email object.
	 * @return string Customer email address.
	 */
	public function customer_email( $cart_id, $cart, $email = null ): string {
		if ( empty( $cart ) && $cart_id > 0 ) {
			$cart_query = new RecoveryCart();
			$cart       = $cart_query->get_item( $cart_id );
		}

		return ! empty( $cart->email ) ? esc_html( $cart->email ) : '';
	}

	/**
	 * Gets cart total.
	 *
	 * @since 3.6.5
	 * @param int                                   $cart_id The ID of the cart.
	 * @param \EDD\Pro\CartRecovery\Carts\Cart|null $cart    The cart object.
	 * @param \EDD\Emails\Types\Email|null          $email   The email object.
	 * @return string Cart total with currency.
	 */
	public function cart_total( $cart_id, $cart, $email = null ): string {
		if ( empty( $cart ) && $cart_id > 0 ) {
			$cart_query = new RecoveryCart();
			$cart       = $cart_query->get_item( $cart_id );
		}

		$total = ! empty( $cart->cart_total ) ? $cart->cart_total : 0;

		return edd_currency_filter( edd_format_amount( $total ), $cart->currency );
	}

	/**
	 * Gets cart item count.
	 *
	 * @since 3.6.5
	 * @param int                                   $cart_id The ID of the cart.
	 * @param \EDD\Pro\CartRecovery\Carts\Cart|null $cart    The cart object.
	 * @param \EDD\Emails\Types\Email|null          $email   The email object.
	 * @return string Number of items in cart.
	 */
	public function cart_count( $cart_id, $cart, $email = null ): string {
		if ( empty( $cart ) && $cart_id > 0 ) {
			$cart_query = new RecoveryCart();
			$cart       = $cart_query->get_item( $cart_id );
		}

		if ( empty( $cart ) ) {
			return '0';
		}

		if ( $cart instanceof \EDD\Pro\CartRecovery\Carts\Cart ) {
			return (string) count( $cart->get_items() );
		}

		// Fallback for non-Cart objects (e.g. preview mock data).
		if ( ! empty( $cart->cart_contents ) ) {
			return (string) count( \EDD\Pro\CartRecovery\Carts\Cart::decode_items( $cart->cart_contents ) );
		}

		return '0';
	}

	/**
	 * Gets unsubscribe link.
	 *
	 * @since 3.6.5
	 * @param int                                   $cart_id The ID of the cart.
	 * @param \EDD\Pro\CartRecovery\Carts\Cart|null $cart    The cart object.
	 * @param \EDD\Emails\Types\Email|null          $email   The email object.
	 * @return string Unsubscribe link.
	 */
	public function unsubscribe_link( $cart_id, $cart, $email = null ): string {
		// Use the token from the email object if available (it's already been stored in the database).
		if ( $email && $email instanceof \EDD\Pro\CartRecovery\Emails\Types\Base ) {
			$token = $email->get_token();
		} else {
			// Fallback for cases where email object is not available (shouldn't happen in normal flow).
			$token_data = $this->issue_recovery_token( $cart_id, $cart );
			$token      = $token_data['token'];
		}

		if ( empty( $token ) ) {
			// Use inert link so templates render even when we cannot identify the subscriber.
			return '#';
		}

		return add_query_arg(
			array(
				'edd_action' => 'cart_recovery_unsubscribe',
				'token'      => $token,
			),
			home_url()
		);
	}

	/**
	 * Formats cart item options for display.
	 *
	 * @since 3.6.5
	 * @param array|mixed $options Item options.
	 * @return string Formatted options.
	 */
	private static function format_cart_item_options( $options ): string {
		if ( ! is_array( $options ) ) {
			return '';
		}

		$formatted = array();

		foreach ( $options as $key => $value ) {
			// Escape both key and value to prevent XSS in email content.
			$escaped_key = esc_html( (string) $key );

			if ( is_array( $value ) ) {
				// Escape all array values before imploding.
				$escaped_values = array_map( 'esc_html', $value );
				$formatted[]    = sprintf( '%s: %s', $escaped_key, implode( ', ', $escaped_values ) );
			} else {
				$escaped_value = esc_html( (string) $value );
				$formatted[]   = sprintf( '%s: %s', $escaped_key, $escaped_value );
			}
		}

		return implode( '<br>', $formatted );
	}

	/**
	 * Generates a recovery token.
	 *
	 * @param int                                   $cart_id The cart ID.
	 * @param \EDD\Pro\CartRecovery\Carts\Cart|null $cart    Optional. The cart object, if already available.
	 * @return array Array containing token and expires_at.
	 */
	private function issue_recovery_token( int $cart_id, $cart = null ): array {
		if ( empty( $cart ) ) {
			$cart_query = new RecoveryCart();
			$cart       = $cart_query->get_item( $cart_id );
		}

		if ( ! $cart || empty( $cart->email ) ) {
			return array(
				'token'      => '',
				'expires_at' => '',
			);
		}

		// Mix cart id, canonicalised email, timer tick, and randomness to avoid token reuse.
		$payload = $cart_id . '|' . strtolower( $cart->email ) . '|' . microtime( true ) . '|' . wp_rand();
		$salt    = wp_salt( 'edd_acr_recovery' );

		$token = hash_hmac( 'sha256', $payload, $salt );

		$default_ttl  = Emails::get_token_ttl();
		$filtered_ttl = (int) apply_filters( 'edd_acr_recovery_token_ttl', $default_ttl );

		if ( $filtered_ttl <= 0 ) {
			edd_debug_log( sprintf( '[EDD ACR] Invalid recovery token TTL override (%d); reverting to default.', $filtered_ttl ) );
			$filtered_ttl = $default_ttl;
		}

		// Enforce a minimum lifetime so downstream flows have enough time to run.
		$ttl        = max( $filtered_ttl, Emails::get_token_min_ttl() );
		$expires_at = gmdate( 'Y-m-d H:i:s', time() + $ttl );

		return array(
			'token'      => $token ? $token : '',
			'expires_at' => $expires_at,
		);
	}

	/**
	 * Gets the discount code for the email.
	 *
	 * @since 3.6.5
	 * @param \EDD\Emails\Emails\Types\Email $email The email object.
	 * @return string Discount code.
	 */
	private function get_discount_code( $email ): string {

		$email_template = $email->email->get_template();
		if ( ! $email_template ) {
			return '';
		}
		$discount_id = $email_template->get_metadata( 'discount_code' );
		if ( empty( $discount_id ) ) {
			return '';
		}

		// Validate the discount is currently usable.
		if ( ! edd_validate_discount( $discount_id ) ) {
			return '';
		}

		$discount = edd_get_discount( $discount_id );

		return $discount ? $discount->code : '';
	}
}
