<?php
/**
 * Abandoned Cart Recovery email list table for analytics.
 *
 * @package EDD\Pro\CartRecovery\Admin\Tables
 * @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\Admin\Tables;

use EDD\Admin\List_Table;
use EDD\Pro\CartRecovery\Emails\Repository as Emails;
use EDD\Pro\CartRecovery\Carts\Repository;
use EDD\Admin\Utils\NoItems;

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

/**
 * List table for recovery emails analytics.
 */
final class Logs extends List_Table {
	use \EDD\Pro\CartRecovery\Traits\Status;

	/**
	 * Items per page.
	 *
	 * @var int
	 */
	public $per_page = 20;

	/**
	 * Email repository.
	 *
	 * @var Emails|null
	 */
	private $email_repo;

	/**
	 * Cart repository (to resolve cart emails for display in the email list).
	 *
	 * @var Repository|null
	 */
	private $cart_repo;

	/**
	 * Error message to display if repositories fail to initialize.
	 *
	 * @var string|null
	 */
	private $initialization_error;

	/**
	 * Persistent cache group for cart data.
	 *
	 * @var string
	 */
	private const CART_CACHE_GROUP = 'edd_acr_carts';

	/**
	 * Cache TTL for cart data in seconds.
	 *
	 * @var int
	 */
	private const CART_CACHE_TTL = 300;

	/**
	 * Persistent cache group for engagement data.
	 *
	 * @var string
	 */
	private const ENGAGEMENT_CACHE_GROUP = 'edd_acr_engagement';

	/**
	 * Constructor.
	 */
	public function __construct() {
		parent::__construct(
			array(
				'singular' => 'edd_acr_email',
				'plural'   => 'edd_acr_emails',
				'ajax'     => false,
			)
		);

		// Initialize repositories - they now handle database failures gracefully.
		$this->email_repo = new Emails();

		try {
			$this->cart_repo = new Repository();
		} catch ( \Exception $e ) {
			$this->cart_repo = null;
			if ( ! $this->initialization_error ) {
				$this->initialization_error = 'Cart repository initialization failed: ' . $e->getMessage();
			}
		}

		// Get email counts for views.
		$this->get_email_counts();
	}

	/**
	 * Get the base URL for the email logs list table.
	 *
	 * @since 3.6.5
	 * @return string Base URL for the email logs list.
	 */
	public function get_base_url() {
		$base = remove_query_arg( edd_admin_removable_query_args(), admin_url( 'edit.php' ) );

		return add_query_arg(
			array(
				'post_type' => 'download',
				'page'      => 'edd-cart-recovery',
				'tab'       => 'activity',
			),
			$base
		);
	}

	/**
	 * Retrieve the email counts for each status.
	 *
	 * @since 3.6.5
	 * @return void
	 */
	private function get_email_counts() {
		if ( ! $this->email_repo ) {
			$this->counts = array(
				'total'   => 0,
				'pending' => 0,
				'sent'    => 0,
				'failed'  => 0,
				'bounced' => 0,
			);
			return;
		}

		// Get status counts from repository.
		$status_counts = $this->email_repo->get_email_status_counts();

		$this->counts = array(
			'total' => 0,
		);

		// Fill in actual counts from repository.
		foreach ( $status_counts as $status => $count ) {
			$this->counts[ $status ] = (int) $count;
			$this->counts['total']  += (int) $count;
		}
	}

	/**
	 * Retrieves the list of columns.
	 */
	public function get_columns(): array {
		return array(
			'cb'         => '<input type="checkbox" />',
			'email'      => __( 'Email', 'easy-digital-downloads' ),
			'recipient'  => __( 'Recipient', 'easy-digital-downloads' ),
			'subject'    => __( 'Subject', 'easy-digital-downloads' ),
			'status'     => __( 'Status', 'easy-digital-downloads' ),
			'engagement' => __( 'Engagement', 'easy-digital-downloads' ),
			'sent'       => __( 'Sent', 'easy-digital-downloads' ),
		);
	}

	/**
	 * Sortable columns.
	 */
	protected function get_sortable_columns(): array {
		return array(
			'sent'       => array( 'date_sent', true ),
			'engagement' => array( 'engagement', false ),
		);
	}

	/**
	 * Primary column name.
	 *
	 * @since 3.6.5
	 * @return string The primary column name.
	 */
	protected function get_primary_column_name(): string {
		return 'email';
	}

	/**
	 * Checkbox column.
	 *
	 * @since 3.6.5
	 * @param object $item The email item.
	 * @return string The checkbox column.
	 */
	protected function column_cb( $item ): string {
		return sprintf(
			'<input type="checkbox" name="email_ids[]" value="%s" />',
			$item->id
		);
	}

	/**
	 * Renders the email column with row actions.
	 *
	 * @since 3.6.5
	 * @param object $item The email item.
	 * @return string The email column.
	 */
	protected function column_email( $item ): string {
		$email = edd_get_email( $item->template_id );
		if ( $email ) {
			$name = $email->get_template()->get_name();
		} else {
			$name = ! empty( $item->template_id ) ? $item->template_id : __( 'Immediate Recovery', 'easy-digital-downloads' );
		}

		$actions = array();

		// Add "View Cart" action if we have a valid cart_id.
		if ( ! empty( $item->cart_id ) ) {
			$actions['view_cart'] = sprintf(
				'<button type="button" class="button-link edd-acr-view-cart" data-cart-id="%s">%s</button>',
				(int) $item->cart_id,
				esc_html__( 'View Cart', 'easy-digital-downloads' )
			);
		}

		return esc_html( $name ) . $this->row_actions( $actions );
	}

	/**
	 * Renders the Recipient column using cached cart data.
	 *
	 * @since 3.6.5
	 * @param object $item The email item.
	 * @return string The recipient column.
	 */
	protected function column_recipient( $item ): string {
		$cart_id = isset( $item->cart_id ) ? (int) $item->cart_id : 0;
		$cart    = $cart_id > 0 ? $this->get_cached_cart( $cart_id ) : null;

		$email = '';
		if ( is_object( $cart ) && isset( $cart->email ) && is_string( $cart->email ) ) {
			$email = trim( $cart->email );
		}

		if ( '' === $email ) {
			return '&mdash;';
		}

		return '<a href="mailto:' . esc_attr( $email ) . '">' . esc_html( $email ) . '</a>';
	}

	/**
	 * Renders the Subject column.
	 *
	 * @since 3.6.5
	 * @param object $item The email item.
	 * @return string The subject column.
	 */
	protected function column_subject( $item ): string {
		return ! empty( $item->email_subject ) ? esc_html( $item->email_subject ) : '&mdash;';
	}

	/**
	 * Renders the Status column with just the status badge.
	 *
	 * @since 3.6.5
	 * @param object $item The email item.
	 * @return string The status column.
	 */
	protected function column_status( $item ): string {
		if ( ! empty( $item->delivery_status ) ) {
			$label = $this->get_status_label( $item->delivery_status, 'email' );
		}

		switch ( $item->delivery_status ) {
			case 'sent':
				$status = 'info';
				break;

			case 'converted':
				$status = 'success';
				break;

			case 'failed':
				$status = 'error';
				break;

			default:
				$status = $item->delivery_status;
				break;
		}

		$badge = new \EDD\Utils\StatusBadge(
			array(
				'status' => $status,
				'label'  => $label,
			)
		);

		return $badge->get();
	}

	/**
	 * Renders the Sent column.
	 *
	 * @since 3.6.5
	 * @param object $item The email item.
	 * @return string The sent column.
	 */
	protected function column_sent( $item ): string {
		return $this->render_date_column( $item->date_sent );
	}

	/**
	 * Renders the Engagement column showing open and click counts.
	 *
	 * @since 3.6.5
	 * @param object $item The email item.
	 * @return string The engagement column.
	 */
	protected function column_engagement( $item ): string {
		$engagement = $this->get_cached_engagement( (int) $item->id );
		$opens      = (int) $engagement['opens'];
		$clicks     = (int) $engagement['clicks'];

		// If no engagement at all, show dash.
		if ( 0 === $opens && 0 === $clicks ) {
			return '&mdash;';
		}

		$parts = array();

		if ( $opens > 0 ) {
			$parts[] = sprintf(
				/* translators: %d: number of opens */
				_n( '%d open', '%d opens', $opens, 'easy-digital-downloads' ),
				$opens
			);
		}

		if ( $clicks > 0 ) {
			$parts[] = sprintf(
				/* translators: %d: number of clicks */
				_n( '%d click', '%d clicks', $clicks, 'easy-digital-downloads' ),
				$clicks
			);
		}

		return esc_html( implode( ', ', $parts ) );
	}

	/**
	 * Renders a date column with a human-readable time difference.
	 *
	 * @since 3.6.5
	 * @param string|null $datetime MySQL datetime string.
	 * @return string Formatted date string.
	 */
	private function render_date_column( ?string $datetime ): string {
		if ( empty( $datetime ) || '0000-00-00 00:00:00' === $datetime ) {
			return '&mdash;';
		}

		$time = strtotime( $datetime );

		return sprintf(
			'<abbr title="%s">%s</abbr>',
			esc_attr( date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $time ) ),
			esc_html( sprintf( '%s ago', human_time_diff( $time ) ) )
		);
	}

	/**
	 * Message when no items.
	 *
	 * @since 3.6.5
	 */
	public function no_items() {
		$actions = array(
			array(
				'type'   => 'primary',
				'label'  => __( 'Learn More', 'easy-digital-downloads' ),
				'url'    => edd_link_helper(
					'https://easydigitaldownloads.com/docs/abandoned-cart-recovery/',
					array(
						'utm_medium'  => 'cart-recovery',
						'utm_content' => 'documentation',
					),
					false
				),
				'target' => '_blank',
				'rel'    => 'noopener noreferrer',
			),
		);
		if ( $this->has_filters() ) {
			$actions[] = array(
				'type'  => 'secondary',
				'label' => __( 'Clear Filters', 'easy-digital-downloads' ),
				'url'   => edd_get_admin_url(
					array(
						'page' => 'edd-cart-recovery',
						'tab'  => 'activity',
					)
				),
			);
		} else {
			$actions[] = array(
				'type'  => 'secondary',
				'label' => __( 'Configure Recovery Settings', 'easy-digital-downloads' ),
				'url'   => edd_get_admin_url(
					array(
						'page' => 'edd-cart-recovery',
						'tab'  => 'settings',
					)
				),
			);
		}
		$no_items = new NoItems(
			__( 'No recovery emails found.', 'easy-digital-downloads' ),
			__( 'Email analytics will appear here once recovery emails are sent to customers with abandoned carts.', 'easy-digital-downloads' ),
			$actions
		);
		$no_items->render();
	}

	/**
	 * Retrieves all the data for email logs.
	 *
	 * @since 3.6.5
	 * @return array Email log items.
	 */
	public function get_data() {
		// Check if repositories are available.
		if ( ! $this->email_repo ) {
			return array();
		}

		// Get pagination arguments.
		$args = $this->parse_pagination_args();

		// Parse common filters (search, status, dates, cart_id).
		$args = $this->parse_common_filters( $args );

		$result = $this->email_repo->get_emails_for_admin( $args );

		$items = ! empty( $result['items'] ) ? $result['items'] : array();

		// Prime cart cache to prevent N+1 queries during rendering.
		if ( ! empty( $items ) ) {
			$this->prime_cart_cache( $items );
			$this->prime_engagement_cache( $items );
		}

		return $items;
	}

	/**
	 * Parses common GET parameters used by table display and CSV export.
	 *
	 * @since 3.6.5
	 * @param array $base_args Base arguments to merge with parsed parameters.
	 * @return array Parsed arguments.
	 */
	private function parse_common_filters( array $base_args = array() ): array {
		// Search filter.
		$search = $this->get_search();
		if ( $search ) {
			$base_args['search'] = $search;
		}

		// Status filter - use delivery_status for email logs.
		$status           = $this->get_status();
		$allowed_statuses = array_keys( $this->get_email_statuses() );
		if ( $status && in_array( $status, $allowed_statuses, true ) ) {
			$base_args['delivery_status'] = $status;
		}

		// Date range filters.
		$date_from = $this->get_request_var( 'date_from' );
		if ( $date_from && $this->validate_date( $date_from ) ) {
			$base_args['date_from'] = sanitize_text_field( $date_from );
		}

		$date_to = $this->get_request_var( 'date_to' );
		if ( $date_to && $this->validate_date( $date_to ) ) {
			$base_args['date_to'] = sanitize_text_field( $date_to );
		}

		// Cart ID filter.
		$cart_id = $this->get_request_var( 'cart_id' );
		if ( ! empty( $cart_id ) ) {
			if ( is_numeric( $cart_id ) ) {
				$base_args['cart_id'] = (int) $cart_id;
			} else {
				$query = new \EDD\Pro\CartRecovery\Database\Queries\RecoveryCart();
				$cart  = $query->get_item_by( 'session_id', $cart_id );
				if ( $cart ) {
					$base_args['cart_id'] = $cart->id;
				}
			}
		}

		return $base_args;
	}

	/**
	 * Validates a date string in Y-m-d format.
	 *
	 * @since 3.6.5
	 * @param string $date Date string to validate.
	 * @return bool True if valid, false otherwise.
	 */
	private function validate_date( string $date ): bool {
		$d = \DateTime::createFromFormat( 'Y-m-d', $date );
		return $d && $d->format( 'Y-m-d' ) === $date;
	}

	/**
	 * Prepares the items for the table.
	 *
	 * @since 3.6.5
	 */
	public function prepare_items() {
		$columns  = $this->get_columns();
		$hidden   = array();
		$sortable = $this->get_sortable_columns();
		$status   = $this->get_status( 'total' );

		$this->_column_headers = array( $columns, $hidden, $sortable );
		$this->items           = $this->get_data();

		if ( empty( $this->counts[ $status ] ) ) {
			return;
		}

		$this->set_pagination_args(
			array(
				'total_pages' => ceil( $this->counts[ $status ] / $this->per_page ),
				'total_items' => $this->counts[ $status ],
				'per_page'    => $this->per_page,
			)
		);
	}

	/**
	 * Displays the advanced filters bar matching EDD core pattern.
	 *
	 * @since 3.6.5
	 */
	public function advanced_filters() {
		// Use parent helper methods for getting request vars.
		$date_from = sanitize_text_field( $this->get_request_var( 'date_from', '' ) );
		$date_to   = sanitize_text_field( $this->get_request_var( 'date_to', '' ) );
		$cart_id   = sanitize_text_field( $this->get_request_var( 'cart_id', '' ) );
		?>
		<div id="edd-filters" class="wp-filter">
			<div class="filter-items">
				<span id="edd-email-status-filter">
					<?php $this->status_dropdown(); ?>
				</span>
				<span id="edd-date-filters" class="edd-from-to-wrapper">
					<?php

					echo EDD()->html->date_field(
						array(
							'id'          => 'start-date',
							'name'        => 'start-date',
							'placeholder' => _x( 'From', 'date filter', 'easy-digital-downloads' ),
							'value'       => $date_from,
						)
					);

					echo EDD()->html->date_field(
						array(
							'id'          => 'end-date',
							'name'        => 'end-date',
							'placeholder' => _x( 'To', 'date filter', 'easy-digital-downloads' ),
							'value'       => $date_to,
						)
					);

					?>
				</span>
				<span id="edd-cart-id-filter">
					<label for="cart-id" class="screen-reader-text"><?php esc_html_e( 'Cart ID', 'easy-digital-downloads' ); ?></label>
					<input type="text" id="cart-id" name="cart_id" value="<?php echo esc_attr( $cart_id ); ?>"
							placeholder="<?php esc_attr_e( 'Cart ID', 'easy-digital-downloads' ); ?>" class="regular-text" />
				</span>
				<span id="edd-email-after-core-filters">
					<input type="submit" class="button-secondary" value="<?php esc_attr_e( 'Apply', 'easy-digital-downloads' ); ?>"/>
				</span>
				<?php if ( ! empty( $date_from ) || ! empty( $date_to ) || ! empty( $cart_id ) ) : ?>
					<a href="
					<?php
					echo esc_url(
						edd_get_admin_url(
							array(
								'page' => 'edd-cart-recovery',
								'tab'  => 'activity',
							)
						)
					);
					?>
								" class="button-secondary">
						<?php esc_html_e( 'Clear Filter', 'easy-digital-downloads' ); ?>
					</a>
				<?php endif; ?>
				<?php
				/**
				 * Fires at the end of the advanced filters row.
				 *
				 * @since 3.6.5
				 */
				do_action( 'edd_cart_recovery_email_logs_advanced_filters_row' );
				?>
			</div>
			<?php $this->search_box( __( 'Search', 'easy-digital-downloads' ), 'edd-email-logs' ); ?>
		</div>
		<?php
	}

	/**
	 * Renders a status filter dropdown.
	 *
	 * @since 3.6.5
	 */
	private function status_dropdown(): void {
		$current = filter_input( INPUT_GET, 'status', FILTER_SANITIZE_SPECIAL_CHARS );
		$options = $this->get_status_options();

		if ( empty( $options ) ) {
			return;
		}
		?>
		<select name="status">
			<?php foreach ( $options as $value => $label ) : ?>
				<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $current, $value ); ?>>
					<?php echo esc_html( $label ); ?>
				</option>
			<?php endforeach; ?>
		</select>
		<?php
	}

	/**
	 * Renders the search box for the email logs list table.
	 *
	 * @since 3.6.5
	 * @param string $text The text for the search box.
	 * @param string $input_id The ID for the search box.
	 * @return void
	 */
	public function search_box( $text, $input_id ): void {
		if ( ! $this->get_search() && ! $this->has_items() ) {
			return;
		}

		$input_id = $input_id . '-search-input';
		?>
		<p class="search-form">
			<label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo esc_html( $text ); ?>:</label>
			<input type="search" id="<?php echo esc_attr( $input_id ); ?>" name="s" placeholder="<?php esc_html_e( 'Search emails...', 'easy-digital-downloads' ); ?>" value="<?php _admin_search_query(); ?>" />
		</p>
		<?php
	}

	/**
	 * Display the list table with form wrapper.
	 *
	 * @since 3.6.5
	 */
	public function display() {
		?>
		<form method="get">
			<?php
			foreach ( $this->get_hidden_fields() as $name => $value ) {
				printf( '<input type="hidden" name="%s" value="%s" />', esc_attr( $name ), esc_attr( $value ) );
			}

			$this->advanced_filters();
			parent::display();
			?>
		</form>
		<?php
	}

	/**
	 * Generate the table navigation above or below the table
	 *
	 * Removes the referrer nonce from parent class.
	 *
	 * @since 3.6.5
	 * @param string $which Position of the tablenav: 'top' or 'bottom'.
	 */
	protected function display_tablenav( $which ) {
		if ( 'top' === $which ) {
			wp_nonce_field( 'bulk-' . $this->_args['plural'], '_wpnonce', false );
		}
		?>
		<div class="tablenav <?php echo esc_attr( $which ); ?>">
			<?php if ( $this->has_items() ) : ?>
				<div class="alignleft actions bulkactions">
					<?php $this->bulk_actions( $which ); ?>
				</div>
				<?php
			endif;

			$this->extra_tablenav( $which );
			$this->pagination( $which );
			?>

			<br class="clear" />
		</div>
		<?php
	}

	/**
	 * Returns an array of additional hidden fields for the list table form.
	 *
	 * @since 3.6.5
	 * @return array Additional hidden fields.
	 */
	private function get_hidden_fields(): array {
		return array(
			'post_type' => 'download',
			'page'      => 'edd-cart-recovery',
			'tab'       => 'activity',
		);
	}

	/**
	 * Bulk actions (placeholder).
	 *
	 * @since 3.6.5
	 * @return array Bulk actions.
	 */
	public function get_bulk_actions(): array {
		return array(
			'delete' => __( 'Delete', 'easy-digital-downloads' ),
		);
	}

	/**
	 * Prime the persistent cart cache for the given email items.
	 * Uses batch queries to eliminate N+1 query patterns.
	 *
	 * @since 3.6.5
	 * @param array $items The email items.
	 * @return void
	 */
	private function prime_cart_cache( array $items ): void {
		// Skip cart caching if cart repository is not available.
		if ( ! $this->cart_repo ) {
			return;
		}

		$cart_ids = array();
		foreach ( $items as $item ) {
			if ( ! empty( $item->cart_id ) ) {
				$cart_ids[] = (int) $item->cart_id;
			}
		}

		// Skip if no cart IDs to process.
		if ( empty( $cart_ids ) ) {
			return;
		}

		// Check persistent cache first, then batch load missing carts.
		$this->batch_load_carts_from_cache( $cart_ids );
	}

	/**
	 * Batch load carts from persistent cache with database fallback.
	 *
	 * @since 3.6.5
	 * @param array $cart_ids Array of cart IDs to load.
	 */
	private function batch_load_carts_from_cache( array $cart_ids ): void {
		$uncached_ids = array();
		$cached_data  = array();

		// Check persistent cache for each cart ID.
		foreach ( $cart_ids as $cart_id ) {
			$cached_cart = wp_cache_get( $cart_id, self::CART_CACHE_GROUP );
			if ( false !== $cached_cart ) {
				$cached_data[] = $cart_id;
			} else {
				$uncached_ids[] = $cart_id;
			}
		}

		// Batch load only the uncached carts from database.
		if ( ! empty( $uncached_ids ) ) {
			$this->batch_load_carts_from_database( $uncached_ids );
		}

		// Log cache performance metrics.
		if ( count( $cart_ids ) > 10 ) {
			$cache_hit_rate = count( $cached_data ) / count( $cart_ids ) * 100;
			edd_debug_log(
				sprintf(
					'[EDD ACR] Cart cache hit rate: %.1f%% (%d/%d carts from cache)',
					$cache_hit_rate,
					count( $cached_data ),
					count( $cart_ids )
				)
			);
		}
	}

	/**
	 * Batch load carts from database and store in persistent cache.
	 *
	 * @since 3.6.5
	 * @param array $cart_ids Array of cart IDs to load.
	 */
	private function batch_load_carts_from_database( array $cart_ids ): void {
		global $wpdb;

		if ( ! ( $wpdb instanceof \wpdb ) ) {
			edd_debug_log( '[EDD ACR] Database not available for cart batch loading.' );
			return;
		}

		$table        = $wpdb->prefix . 'edd_acr_carts';
		$placeholders = implode( ',', array_fill( 0, count( $cart_ids ), '%d' ) );

		// Use splat operator to unpack array arguments for PHP 7.4 compatibility.
		$query = $wpdb->prepare(
			"SELECT id, email FROM {$table} WHERE id IN ({$placeholders}) LIMIT " . count( $cart_ids ), // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			...$cart_ids
		);

		if ( false === $query ) {
			edd_debug_log( '[EDD ACR] Cart batch query prepare failed.' );
			return;
		}

		$start_time = microtime( true );
		$results    = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
		$query_time = microtime( true ) - $start_time;

		// Log performance metrics for monitoring.
		if ( $query_time > 0.1 ) {
			edd_debug_log(
				sprintf(
					'[EDD ACR] Cart batch query took %.3fs for %d carts',
					$query_time,
					count( $cart_ids )
				)
			);
		}

		// Store in persistent cache for future requests.
		foreach ( (array) $results as $cart ) {
			wp_cache_set( $cart->id, $cart, self::CART_CACHE_GROUP, self::CART_CACHE_TTL );
		}

		// Log any missing carts for debugging.
		$loaded_ids  = array_map(
			function ( $cart ) {
				return $cart->id;
			},
			$results
		);
		$missing_ids = array_diff( $cart_ids, $loaded_ids );
		if ( ! empty( $missing_ids ) ) {
			edd_debug_log(
				sprintf(
					'[EDD ACR] Missing carts in batch load: %s',
					implode( ', ', $missing_ids )
				)
			);
		}
	}

	/**
	 * Get cart from persistent cache.
	 * This eliminates N+1 query patterns by relying entirely on WordPress Object Cache.
	 *
	 * @since 3.6.5
	 * @param int $cart_id Cart ID.
	 * @return object|null
	 */
	private function get_cached_cart( int $cart_id ): ?object {
		// WordPress Object Cache provides in-memory caching for the current request.
		$cached_cart = wp_cache_get( $cart_id, self::CART_CACHE_GROUP );
		return false !== $cached_cart ? $cached_cart : null;
	}

	/**
	 * Prime the engagement cache for the given email items.
	 * Uses batch queries to eliminate N+1 query patterns.
	 *
	 * @since 3.6.5
	 * @param array $items The email items.
	 * @return void
	 */
	private function prime_engagement_cache( array $items ): void {
		$email_ids = array();
		foreach ( $items as $item ) {
			if ( ! empty( $item->id ) ) {
				$email_ids[] = (int) $item->id;
			}
		}

		// Skip if no email IDs to process.
		if ( empty( $email_ids ) ) {
			return;
		}

		// Use Journey Repository to batch fetch engagement counts.
		$journey_repo = new \EDD\Pro\CartRecovery\Journey\Repository();
		$engagement   = $journey_repo->get_email_engagement_counts( $email_ids );

		// Store in WordPress Object Cache for current request.
		foreach ( $engagement as $email_id => $counts ) {
			wp_cache_set( $email_id, $counts, self::ENGAGEMENT_CACHE_GROUP );
		}
	}

	/**
	 * Get engagement counts from cache.
	 *
	 * @since 3.6.5
	 * @param int $email_id Email ID.
	 * @return array Array with 'opens' and 'clicks' keys.
	 */
	private function get_cached_engagement( int $email_id ): array {
		$cached = wp_cache_get( $email_id, self::ENGAGEMENT_CACHE_GROUP );

		if ( false !== $cached && is_array( $cached ) ) {
			return $cached;
		}

		// Return empty engagement if not cached.
		return array(
			'opens'  => 0,
			'clicks' => 0,
		);
	}

	/**
	 * Checks if any filters are applied to the current request.
	 *
	 * @since 3.6.5
	 * @return bool True if any filters are applied, false otherwise.
	 */
	private function has_filters(): bool {
		if ( ! empty( filter_input( INPUT_GET, 'status', FILTER_SANITIZE_SPECIAL_CHARS ) ) ) {
			return true;
		}

		if ( ! empty( filter_input( INPUT_GET, 'date_from', FILTER_SANITIZE_SPECIAL_CHARS ) ) ) {
			return true;
		}

		if ( ! empty( filter_input( INPUT_GET, 'date_to', FILTER_SANITIZE_SPECIAL_CHARS ) ) ) {
			return true;
		}

		if ( ! empty( filter_input( INPUT_GET, 'cart_id', FILTER_SANITIZE_SPECIAL_CHARS ) ) ) {
			return true;
		}

		if ( ! empty( filter_input( INPUT_GET, 's', FILTER_SANITIZE_SPECIAL_CHARS ) ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Returns an array of statuses to be used in the status dropdown.
	 *
	 * @since 3.6.5
	 * @return array
	 */
	private function get_status_options(): array {
		return array_merge(
			array(
				'' => __( 'All Statuses', 'easy-digital-downloads' ),
			),
			$this->get_email_statuses()
		);
	}
}
