<?php
/**
 * Email Log admin workspace.
 *
 * @package SubmissionInbox
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Renders an explainable, privacy-first history of WordPress mail attempts.
 */
class SUBIN_EmailLogPage {
	const ACTION_NONCE = 'subin_email_log_action';

	/** @var \SUBIN\MailLog\MailEventRepository */
	private $events;

	/** @var \SUBIN\Mail\EmailErrorClassifier */
	private $error_classifier;

	/** @var \SUBIN\MailLog\MailEventPresenter */
	private $presenter;

	/** @var \SUBIN\MailLog\EmailLogPreferences */
	private $preferences;

	/** @var \SUBIN_SettingsRepository */
	private $settings;

	/** @var \SUBIN\Notifications\NotificationJobRepository */
	private $jobs;

	/** @var \SUBIN\MailLog\EmailLogExportService */
	private $exports;

	/** @var array<string,mixed> */
	private $active_filters = array();

	/** @var string */
	private $filter_error = '';

	/** Constructor. */
	public function __construct() {
		$this->events           = new \SUBIN\MailLog\MailEventRepository();
		$this->error_classifier = new \SUBIN\Mail\EmailErrorClassifier();
		$this->presenter        = new \SUBIN\MailLog\MailEventPresenter();
		$this->preferences      = new \SUBIN\MailLog\EmailLogPreferences();
		$this->settings         = new \SUBIN_SettingsRepository();
		$this->jobs             = new \SUBIN\Notifications\NotificationJobRepository();
		$this->exports          = new \SUBIN\MailLog\EmailLogExportService( $this->events, $this->presenter );
	}

	/**
	 * Handle personal display settings, saved views, and exact immutable retries.
	 *
	 * This must run on load-{$hook} so redirects happen before admin HTML.
	 *
	 * @return void
	 */
	public function handle_request() {
		if ( ! \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::VIEW_EMAIL_LOG ) ) {
			wp_die( esc_html__( 'You do not have permission to view email events.', 'submission-inbox' ) );
		}

		$request_method = isset( $_SERVER['REQUEST_METHOD'] ) && is_scalar( $_SERVER['REQUEST_METHOD'] )
			? sanitize_text_field( wp_unslash( (string) $_SERVER['REQUEST_METHOD'] ) )
			: '';
		if ( 'POST' !== strtoupper( $request_method ) ) {
			return;
		}

		$action = sanitize_key( $this->post_scalar( 'subin_email_log_action' ) );
		if ( '' === $action ) {
			return;
		}
		if ( 'start_export' === $action ) {
			$this->start_background_export();
			return;
		}
		if ( 'delete_export' === $action ) {
			$this->delete_background_export();
			return;
		}
		if ( ! wp_verify_nonce( sanitize_text_field( $this->post_scalar( 'subin_email_log_nonce' ) ), self::ACTION_NONCE ) ) {
			wp_die( esc_html__( 'Security check failed. Refresh the page and try again.', 'submission-inbox' ) );
		}

		$filters  = $this->get_filters_from_post();
		$event_id = absint( $this->post_scalar( 'event_id' ) );
		$notice   = 'action_failed';

		if ( 'save_preferences' === $action ) {
			$raw_columns = isset( $_POST['columns'] ) && is_array( $_POST['columns'] ) ? wp_unslash( $_POST['columns'] ) : array();
			$columns     = array();
			foreach ( $raw_columns as $column ) {
				if ( is_scalar( $column ) ) {
					$columns[] = sanitize_key( (string) $column );
				}
			}
			$saved   = $this->preferences->save_preferences(
				array(
					'density'  => sanitize_key( $this->post_scalar( 'density' ) ),
					'columns'  => $columns,
					'per_page' => absint( $this->post_scalar( 'per_page', '20' ) ),
				)
			);
			$notice = $saved ? 'preferences_saved' : 'action_failed';
		} elseif ( 'save_view' === $action ) {
			$result = $this->preferences->save_view( sanitize_text_field( $this->post_scalar( 'view_name' ) ), $filters );
			$notice = is_wp_error( $result ) ? sanitize_key( $result->get_error_code() ) : 'view_saved';
		} elseif ( 'delete_view' === $action ) {
			$notice = $this->preferences->delete_view( sanitize_key( $this->post_scalar( 'view_id' ) ) ) ? 'view_deleted' : 'action_failed';
		} elseif ( 'retry_event' === $action ) {
			$notice = $this->retry_exact_event( $event_id );
		}

		$args = $this->url_args_from_filters( $filters );
		if ( $event_id > 0 ) {
			$args['event_id'] = $event_id;
		}
		$args['subin_notice'] = $notice;
		wp_safe_redirect( add_query_arg( $args, admin_url( 'admin.php' ) ) );
		exit;
	}

	/** Render page. */
	public function render() {
		if ( ! \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::VIEW_EMAIL_LOG ) ) {
			wp_die( esc_html__( 'You do not have permission to view email events.', 'submission-inbox' ) );
		}

		$filters               = $this->get_filters_from_request();
		$this->active_filters  = $filters;
		$display               = $this->preferences->get_preferences();
		$per_page              = absint( $display['per_page'] ?? 20 );
		$paged                 = max( 1, SUBIN_Request::get_absint( 'paged', 1 ) );
		$read_error            = null;
		$total                 = $this->events->count_events_checked( $filters );
		if ( is_wp_error( $total ) ) {
			$read_error = $total;
			$total      = 0;
		}
		$total_pages           = max( 1, (int) ceil( $total / $per_page ) );
		$paged                 = min( $paged, $total_pages );
		$events                = $read_error ? array() : $this->events->get_paginated_events_checked(
			array_merge(
				$filters,
				array(
					'per_page' => $per_page,
					'offset'   => ( $paged - 1 ) * $per_page,
				)
			)
			);
		if ( is_wp_error( $events ) ) {
			$read_error = $events;
			$events     = array();
		}
		$summary               = $read_error ? array() : $this->events->get_summary_checked( $filters );
		if ( is_wp_error( $summary ) ) {
			$read_error = $summary;
			$summary    = array();
		}
		$event_id              = SUBIN_Request::get_absint( 'event_id', 0 );
		$selected_event        = ! $read_error && $event_id > 0 ? $this->events->get_event_with_context_checked( $event_id ) : null;
		if ( is_wp_error( $selected_event ) ) {
			$read_error     = $selected_event;
			$selected_event = null;
		}
		$secondary_actions     = array();
		$header_args           = array( 'wrapper_class' => 'subin-email-log-page' );

		if ( \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::VIEW_EMAIL_HEALTH ) ) {
			$header_args['primary_action'] = array(
				'label' => __( 'Email Health', 'submission-inbox' ),
				'url'   => $this->email_health_url( $filters ),
			);
		}
		if ( \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::EXPORT_EMAIL_LOG ) ) {
			$secondary_actions[] = array(
				'label' => __( 'Export CSV', 'submission-inbox' ),
				'url'   => $this->list_url( $filters, $paged ) . '#subin-email-log-exports',
				'icon'  => 'dashicons-download',
			);
		}
		$header_args['secondary_actions'] = $secondary_actions;

		SUBIN_View::page_header(
			__( 'Email Log', 'submission-inbox' ),
			__( 'Inspect each WordPress email attempt, its exact context, and what happened next.', 'submission-inbox' ),
			$header_args
		);

		$this->render_request_notice();
		SUBIN_AdminNotices::render_settings_errors( 'subin_messages' );
		$this->render_truth_notice();
		if ( $read_error ) {
			$this->render_data_unavailable();
			SUBIN_View::page_footer();
			return;
		}
		$this->render_summary_cards( $summary, $filters );
		$this->render_quick_views( $filters );
		$this->render_filters( $filters );
		$this->render_filter_chips( $filters );
		$this->render_results_toolbar( $total, $filters, $display );
		?>
		<div class="subin-email-log-workspace">
			<main class="subin-email-log-results" id="subin-email-log-results">
				<?php $detail_rendered = $this->render_events_table( $events, $total, $filters, $display, $selected_event ); ?>
				<?php if ( $selected_event && ! $detail_rendered ) : ?>
					<div class="subin-event-detail-inline subin-event-detail-inline--standalone">
						<?php $this->render_event_detail( $selected_event, $filters ); ?>
					</div>
				<?php elseif ( $event_id > 0 && ! $selected_event ) : ?>
					<aside class="subin-event-detail subin-event-detail--missing" aria-labelledby="subin-event-detail-title">
						<a class="subin-event-detail__close" href="<?php echo esc_url( $this->list_url( $filters, $paged ) ); ?>" aria-label="<?php esc_attr_e( 'Close event details', 'submission-inbox' ); ?>"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></a>
						<h2 id="subin-event-detail-title"><?php esc_html_e( 'Event not found', 'submission-inbox' ); ?></h2>
						<p><?php esc_html_e( 'This event may have expired under the retention policy, or the link is invalid.', 'submission-inbox' ); ?></p>
					</aside>
				<?php endif; ?>
				<?php $this->render_pagination( $total, $paged, $per_page, $filters ); ?>
			</main>
		</div>
		<?php
		SUBIN_View::page_footer();
	}

	/** Handle CSV export before the page sends HTML. */
	public function handle_export_request() {
		$raw_action = isset( $_GET['subin_export_action'] ) ? wp_unslash( $_GET['subin_export_action'] ) : '';
		$raw_legacy = isset( $_GET['subin_export'] ) ? wp_unslash( $_GET['subin_export'] ) : '';
		$action     = is_scalar( $raw_action ) ? sanitize_key( (string) $raw_action ) : '';
		$is_legacy  = is_scalar( $raw_legacy ) && 'email_log' === sanitize_key( (string) $raw_legacy );
		if ( '' === $action && $is_legacy ) {
			$action = 'legacy';
		}
		if ( ! in_array( $action, array( 'download', 'legacy' ), true ) ) {
			return;
		}
		if ( ! \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::EXPORT_EMAIL_LOG ) ) {
			wp_die( esc_html__( 'You do not have permission to export email events.', 'submission-inbox' ) );
		}

		$filters = $this->get_filters_from_request();
		if ( 'legacy' === $action ) {
			wp_safe_redirect( $this->list_url( $filters, 1 ) . '#subin-email-log-exports' );
			exit;
		}

		$this->maybe_export( $filters, $action );
	}

	/** Render the handoff/delivery and retention contract. */
	private function render_truth_notice() {
		$retention_days = $this->settings->mail_log_retention_days();
		$retention      = 0 === $retention_days
			? __( 'kept until you change this policy', 'submission-inbox' )
			: sprintf(
				/* translators: %d: retention days. */
				_n( 'kept for %d day', 'kept for %d days', $retention_days, 'submission-inbox' ),
				$retention_days
			);
		?>
		<section class="subin-log-truth" aria-labelledby="subin-log-truth-title">
			<span class="subin-log-truth__icon dashicons dashicons-shield-alt" aria-hidden="true"></span>
			<div>
				<h2 id="subin-log-truth-title"><?php esc_html_e( 'Accepted is not the same as delivered', 'submission-inbox' ); ?></h2>
				<p>
					<?php esc_html_e( '“Accepted by WordPress” means wp_mail() accepted the attempt. Delivered, bounced, or opened appears only when an authenticated email provider supplies evidence.', 'submission-inbox' ); ?>
					<?php /* translators: %s: human-readable Email Log retention period. */ ?>
					<?php echo esc_html( sprintf( __( 'Email bodies and attachments are not stored in Email Log; event metadata is %s.', 'submission-inbox' ), $retention ) ); ?>
					<?php esc_html_e( 'For scheduled delivery and safe retry, an immutable notification-job body is kept outside Email Log only until the bounded operational payload retention expires.', 'submission-inbox' ); ?>
				</p>
			</div>
			<?php if ( \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::MANAGE_SETTINGS ) ) : ?>
				<a href="<?php echo esc_url( admin_url( 'admin.php?page=subin-settings&subin_settings_tab=privacy' ) ); ?>"><?php esc_html_e( 'Review retention', 'submission-inbox' ); ?></a>
			<?php endif; ?>
		</section>
		<?php
	}

	/** Render five clickable outcome cards in the current non-outcome scope. */
	private function render_summary_cards( array $summary, array $filters ) {
		$cards = array(
			array( 'key' => '', 'count_key' => 'total', 'label' => __( 'All events', 'submission-inbox' ), 'icon' => 'dashicons-email-alt2', 'tone' => 'neutral' ),
			array( 'key' => 'accepted', 'count_key' => 'accepted', 'label' => __( 'Accepted', 'submission-inbox' ), 'icon' => 'dashicons-yes-alt', 'tone' => 'success' ),
			array( 'key' => 'failed', 'count_key' => 'failed', 'label' => __( 'Failed', 'submission-inbox' ), 'icon' => 'dashicons-warning', 'tone' => 'danger' ),
			array( 'key' => 'not_generated', 'count_key' => 'no_email_generated', 'label' => __( 'Not generated', 'submission-inbox' ), 'icon' => 'dashicons-dismiss', 'tone' => 'warning' ),
			array( 'key' => 'awaiting', 'count_key' => 'awaiting', 'label' => __( 'Awaiting outcome', 'submission-inbox' ), 'icon' => 'dashicons-clock', 'tone' => 'info' ),
		);
		$current = sanitize_key( (string) ( $filters['outcome'] ?? '' ) );
		?>
		<nav class="subin-log-scope" aria-label="<?php esc_attr_e( 'Filter by outcome', 'submission-inbox' ); ?>">
			<?php foreach ( $cards as $card ) : ?>
				<?php
				$next = $filters;
				unset( $next['status'], $next['outcome'] );
				if ( '' !== $card['key'] ) {
					$next['outcome'] = $card['key'];
				}
				$active = $card['key'] === $current;
				?>
				<a class="subin-log-scope__card subin-log-scope__card--<?php echo esc_attr( $card['tone'] ); ?><?php echo $active ? ' is-active' : ''; ?>" href="<?php echo esc_url( $this->list_url( $next, 1 ) ); ?>"<?php echo $active ? ' aria-current="page"' : ''; ?>>
					<span class="dashicons <?php echo esc_attr( $card['icon'] ); ?>" aria-hidden="true"></span>
					<strong><?php echo esc_html( (string) absint( $summary[ $card['count_key'] ] ?? 0 ) ); ?></strong>
					<span><?php echo esc_html( $card['label'] ); ?></span>
				</a>
			<?php endforeach; ?>
		</nav>
		<div class="subin-log-awaiting-breakdown" aria-label="<?php esc_attr_e( 'Awaiting outcome breakdown', 'submission-inbox' ); ?>">
			<?php
			$processing = array_merge( $filters, array( 'outcome' => 'processing' ) );
			$stalled    = array_merge( $filters, array( 'outcome' => 'stalled' ) );
			?>
			<span><?php esc_html_e( 'Awaiting outcome includes:', 'submission-inbox' ); ?></span>
			<a href="<?php echo esc_url( $this->list_url( $processing, 1 ) ); ?>"><strong><?php echo esc_html( (string) absint( $summary['processing'] ?? 0 ) ); ?></strong> <?php esc_html_e( 'processing', 'submission-inbox' ); ?></a>
			<a class="subin-log-awaiting-breakdown__stalled" href="<?php echo esc_url( $this->list_url( $stalled, 1 ) ); ?>"><strong><?php echo esc_html( (string) absint( $summary['stalled'] ?? 0 ) ); ?></strong> <?php esc_html_e( 'stalled', 'submission-inbox' ); ?></a>
			<?php /* translators: %d: number of email events completed without a reliable terminal signal. */ ?>
			<span><?php echo esc_html( sprintf( __( '%d completed without a reliable terminal signal', 'submission-inbox' ), absint( $summary['unknown'] ?? 0 ) ) ); ?></span>
			<?php /* translators: %d: number of minutes after which a pending event is considered stalled. */ ?>
			<span><?php echo esc_html( sprintf( __( 'Stalled means pending for more than %d minutes.', 'submission-inbox' ), max( 1, (int) ceil( \SUBIN\MailLog\MailEventRepository::STALLED_AFTER_SECONDS / 60 ) ) ) ); ?></span>
		</div>
		<?php
	}

	/** Render task-oriented quick views. */
	private function render_quick_views( array $filters ) {
		$views = array(
			array( 'label' => __( 'All activity', 'submission-inbox' ), 'filters' => array() ),
			array( 'label' => __( 'Needs attention', 'submission-inbox' ), 'filters' => array( 'outcome' => 'needs_attention' ) ),
			array( 'label' => __( 'Auto-replies', 'submission-inbox' ), 'filters' => array( 'email_type' => 'visitor_auto_reply' ) ),
			array( 'label' => __( 'Admin notifications', 'submission-inbox' ), 'filters' => array( 'email_type' => 'admin_notification' ) ),
			array( 'label' => __( 'Verification tests', 'submission-inbox' ), 'filters' => array( 'event_kind' => 'health_verification' ) ),
		);
		?>
		<nav class="subin-log-quick-views" aria-label="<?php esc_attr_e( 'Quick views', 'submission-inbox' ); ?>">
			<span class="subin-log-quick-views__label"><?php esc_html_e( 'Quick views', 'submission-inbox' ); ?></span>
			<?php foreach ( $views as $view ) : ?>
				<?php
				$view_filters = array_merge( array( 'sort' => $filters['sort'] ?? 'newest' ), $view['filters'] );
				$active       = $this->is_quick_view_active( $filters, $view['filters'] );
				?>
				<a class="subin-log-quick-view<?php echo $active ? ' is-active' : ''; ?>" href="<?php echo esc_url( $this->list_url( $view_filters, 1 ) ); ?>"<?php echo $active ? ' aria-current="page"' : ''; ?>><?php echo esc_html( $view['label'] ); ?></a>
			<?php endforeach; ?>
		</nav>
		<?php
	}

	/** Render search and progressive advanced filters. */
	private function render_filters( array $filters ) {
		$form_contexts = $this->events->get_distinct_form_contexts();
		$email_types   = $this->events->get_distinct_email_types();
		$origins       = $this->origin_options( $form_contexts );
		$advanced      = $this->has_advanced_filters( $filters );
		?>
		<section class="subin-log-filter-panel" aria-labelledby="subin-log-filter-title">
			<h2 id="subin-log-filter-title" class="screen-reader-text"><?php esc_html_e( 'Search and filter email events', 'submission-inbox' ); ?></h2>
			<form method="get" action="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>" class="subin-log-filter-form">
				<input type="hidden" name="page" value="subin-email-log" />
				<div class="subin-log-filter-main">
					<label class="subin-log-search" for="subin-email-log-search">
						<span><?php esc_html_e( 'Search the log', 'submission-inbox' ); ?></span>
						<span class="subin-log-search__control"><span class="dashicons dashicons-search" aria-hidden="true"></span><input type="search" id="subin-email-log-search" name="s" value="<?php echo esc_attr( (string) ( $filters['search'] ?? '' ) ); ?>" placeholder="<?php esc_attr_e( 'Recipient, sender, subject, Event ID, Lead ID…', 'submission-inbox' ); ?>" /></span>
					</label>
					<label for="subin-email-log-outcome">
						<span><?php esc_html_e( 'Outcome', 'submission-inbox' ); ?></span>
						<select id="subin-email-log-outcome" name="subin_outcome">
							<?php $this->render_select_options( $this->outcome_options(), (string) ( $filters['outcome'] ?? '' ) ); ?>
						</select>
					</label>
					<label for="subin-email-log-period">
						<span><?php esc_html_e( 'Period', 'submission-inbox' ); ?></span>
						<select id="subin-email-log-period" name="subin_date_range">
							<?php $this->render_select_options( $this->period_options(), (string) ( $filters['date_range'] ?? '' ) ); ?>
						</select>
					</label>
					<button type="submit" class="button button-primary subin-btn subin-btn-primary"><?php esc_html_e( 'Apply', 'submission-inbox' ); ?></button>
				</div>
				<details class="subin-log-advanced"<?php echo $advanced ? ' open' : ''; ?>>
					<summary><?php esc_html_e( 'Advanced filters', 'submission-inbox' ); ?><span class="subin-log-advanced__count"><?php echo esc_html( (string) $this->advanced_filter_count( $filters ) ); ?></span></summary>
					<div class="subin-log-advanced__grid">
						<label for="subin-email-log-type"><span><?php esc_html_e( 'Email type', 'submission-inbox' ); ?></span><select id="subin-email-log-type" name="subin_email_type"><?php $this->render_email_type_options( $email_types, (string) ( $filters['email_type'] ?? '' ) ); ?></select></label>
						<label for="subin-email-log-origin"><span><?php esc_html_e( 'Origin', 'submission-inbox' ); ?></span><select id="subin-email-log-origin" name="subin_origin"><?php $this->render_select_options( $origins, $this->origin_filter_value( $filters ) ); ?></select></label>
						<label for="subin-email-log-form"><span><?php esc_html_e( 'Form', 'submission-inbox' ); ?></span><select id="subin-email-log-form" name="subin_form_context"><?php $this->render_form_options( $form_contexts, $filters ); ?></select></label>
						<label for="subin-email-log-rule"><span><?php esc_html_e( 'Rule ID', 'submission-inbox' ); ?></span><input id="subin-email-log-rule" type="number" min="1" step="1" name="subin_rule_id" value="<?php echo esc_attr( absint( $filters['rule_id'] ?? 0 ) ?: '' ); ?>" placeholder="<?php esc_attr_e( 'Any rule', 'submission-inbox' ); ?>" /></label>
						<label for="subin-email-log-from"><span><?php esc_html_e( 'From date', 'submission-inbox' ); ?></span><input id="subin-email-log-from" type="date" name="subin_date_from" value="<?php echo esc_attr( (string) ( $filters['date_from'] ?? '' ) ); ?>" /></label>
						<label for="subin-email-log-to"><span><?php esc_html_e( 'To date', 'submission-inbox' ); ?></span><input id="subin-email-log-to" type="date" name="subin_date_to" value="<?php echo esc_attr( (string) ( $filters['date_to'] ?? '' ) ); ?>" /></label>
						<label for="subin-email-log-sort"><span><?php esc_html_e( 'Sort', 'submission-inbox' ); ?></span><select id="subin-email-log-sort" name="subin_sort"><?php $this->render_select_options( array( 'newest' => __( 'Newest first', 'submission-inbox' ), 'oldest' => __( 'Oldest first', 'submission-inbox' ) ), (string) ( $filters['sort'] ?? 'newest' ) ); ?></select></label>
					</div>
				</details>
			</form>
			<?php if ( '' !== $this->filter_error ) : ?><p class="subin-log-filter-error" role="alert"><?php echo esc_html( $this->filter_error ); ?></p><?php endif; ?>
		</section>
		<?php
	}

	/** Render active filter chips with one-click removal. */
	private function render_filter_chips( array $filters ) {
		$chips = $this->active_filter_labels( $filters );
		if ( empty( $chips ) ) {
			return;
		}
		?>
		<div class="subin-log-filter-chips" aria-label="<?php esc_attr_e( 'Active filters', 'submission-inbox' ); ?>">
			<span><?php esc_html_e( 'Filtered by', 'submission-inbox' ); ?></span>
			<?php foreach ( $chips as $key => $label ) : ?>
				<?php $next = $this->without_filter( $filters, $key ); ?>
				<?php /* translators: %s: label of the active filter to remove. */ ?>
				<a href="<?php echo esc_url( $this->list_url( $next, 1 ) ); ?>" class="subin-log-filter-chip" aria-label="<?php echo esc_attr( sprintf( __( 'Remove filter: %s', 'submission-inbox' ), $label ) ); ?>"><?php echo esc_html( $label ); ?><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></a>
			<?php endforeach; ?>
			<a class="subin-log-filter-clear" href="<?php echo esc_url( $this->list_url( array(), 1 ) ); ?>"><?php esc_html_e( 'Clear all', 'submission-inbox' ); ?></a>
		</div>
		<?php
	}

	/** Render result count, saved views, and personal display controls. */
	private function render_results_toolbar( $total, array $filters, array $display ) {
		$views = $this->preferences->get_saved_views();
		?>
		<div class="subin-log-results-toolbar">
			<div>
				<h2><?php esc_html_e( 'Events', 'submission-inbox' ); ?></h2>
				<?php /* translators: %d: number of email events in the current view. */ ?>
				<p><?php echo esc_html( sprintf( _n( '%d event in this view', '%d events in this view', $total, 'submission-inbox' ), $total ) ); ?></p>
			</div>
			<div class="subin-log-results-toolbar__actions">
				<details class="subin-log-popover">
					<summary class="button"><span class="dashicons dashicons-visibility" aria-hidden="true"></span><?php esc_html_e( 'Display', 'submission-inbox' ); ?></summary>
					<form method="post" class="subin-log-popover__panel">
						<input type="hidden" name="subin_email_log_action" value="save_preferences" />
						<?php wp_nonce_field( self::ACTION_NONCE, 'subin_email_log_nonce' ); ?>
						<?php $this->render_hidden_filters( $filters ); ?>
						<fieldset><legend><?php esc_html_e( 'Visible columns', 'submission-inbox' ); ?></legend>
							<?php foreach ( array( 'type_origin' => __( 'Type and origin', 'submission-inbox' ), 'outcome' => __( 'Outcome', 'submission-inbox' ), 'related' => __( 'Related records', 'submission-inbox' ) ) as $key => $label ) : ?>
								<label><input type="checkbox" name="columns[]" value="<?php echo esc_attr( $key ); ?>" <?php checked( in_array( $key, $display['columns'] ?? array(), true ) ); ?> /><?php echo esc_html( $label ); ?></label>
							<?php endforeach; ?>
						</fieldset>
						<label><?php esc_html_e( 'Density', 'submission-inbox' ); ?><select name="density"><option value="comfortable" <?php selected( $display['density'] ?? '', 'comfortable' ); ?>><?php esc_html_e( 'Comfortable', 'submission-inbox' ); ?></option><option value="compact" <?php selected( $display['density'] ?? '', 'compact' ); ?>><?php esc_html_e( 'Compact', 'submission-inbox' ); ?></option></select></label>
						<label><?php esc_html_e( 'Rows per page', 'submission-inbox' ); ?><select name="per_page"><?php foreach ( array( 20, 50, 100 ) as $size ) : ?><option value="<?php echo esc_attr( $size ); ?>" <?php selected( absint( $display['per_page'] ?? 20 ), $size ); ?>><?php echo esc_html( (string) $size ); ?></option><?php endforeach; ?></select></label>
						<button class="button button-primary" type="submit"><?php esc_html_e( 'Save display', 'submission-inbox' ); ?></button>
					</form>
				</details>
				<details class="subin-log-popover">
					<summary class="button"><span class="dashicons dashicons-star-filled" aria-hidden="true"></span><?php esc_html_e( 'Saved views', 'submission-inbox' ); ?><?php if ( ! empty( $views ) ) : ?><span class="subin-log-count"><?php echo esc_html( (string) count( $views ) ); ?></span><?php endif; ?></summary>
					<div class="subin-log-popover__panel subin-log-saved-views">
						<?php if ( empty( $views ) ) : ?><p><?php esc_html_e( 'Save the current filters for one-click access later.', 'submission-inbox' ); ?></p><?php endif; ?>
						<?php foreach ( $views as $view ) : ?>
							<?php /* translators: %s: saved view name. */ ?>
							<div class="subin-log-saved-view"><a href="<?php echo esc_url( $this->list_url( $view['filters'], 1 ) ); ?>"><?php echo esc_html( $view['name'] ); ?></a><form method="post"><input type="hidden" name="subin_email_log_action" value="delete_view" /><input type="hidden" name="view_id" value="<?php echo esc_attr( $view['id'] ); ?>" /><?php wp_nonce_field( self::ACTION_NONCE, 'subin_email_log_nonce' ); ?><button type="submit" class="subin-icon-button" aria-label="<?php echo esc_attr( sprintf( __( 'Delete saved view %s', 'submission-inbox' ), $view['name'] ) ); ?>"><span class="dashicons dashicons-trash" aria-hidden="true"></span></button></form></div>
						<?php endforeach; ?>
						<?php if ( count( $views ) < \SUBIN\MailLog\EmailLogPreferences::MAX_SAVED_VIEWS ) : ?>
							<form method="post" class="subin-log-save-view-form"><input type="hidden" name="subin_email_log_action" value="save_view" /><?php wp_nonce_field( self::ACTION_NONCE, 'subin_email_log_nonce' ); ?><?php $this->render_hidden_filters( $filters ); ?><label for="subin-log-view-name"><?php esc_html_e( 'Name this view', 'submission-inbox' ); ?></label><div><input id="subin-log-view-name" name="view_name" type="text" maxlength="80" required /><button class="button" type="submit"><?php esc_html_e( 'Save', 'submission-inbox' ); ?></button></div></form>
						<?php endif; ?>
						</div>
					</details>
					<?php if ( \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::EXPORT_EMAIL_LOG ) ) : ?>
						<?php $this->render_export_manager( $filters, $total ); ?>
					<?php endif; ?>
				</div>
			</div>
			<?php
		}

	/** Render private background export jobs and their scoped actions. */
	private function render_export_manager( array $filters, $total ) {
		$jobs        = $this->exports->list_exports();
		$start_nonce = $this->exports->create_nonce( 'start' );
		$has_error   = is_wp_error( $jobs ) || is_wp_error( $start_nonce );
		$jobs        = is_array( $jobs ) ? $jobs : array();
		$active      = array_filter(
			$jobs,
			static function ( $job ) {
				return in_array( $job['status'] ?? '', array( 'queued', 'processing' ), true );
			}
		);
		?>
		<details id="subin-email-log-exports" class="subin-log-popover subin-log-export-popover" data-subin-export-popover>
			<summary class="button"><span class="dashicons dashicons-download" aria-hidden="true"></span><?php esc_html_e( 'Exports', 'submission-inbox' ); ?><?php if ( ! empty( $active ) ) : ?><span class="subin-log-count"><?php echo esc_html( (string) count( $active ) ); ?></span><?php endif; ?></summary>
			<div class="subin-log-popover__panel subin-log-export-panel">
				<div class="subin-log-export-panel__intro"><strong><?php esc_html_e( 'Private background exports', 'submission-inbox' ); ?></strong><p><?php esc_html_e( 'CSV files are built in small batches, stored outside public uploads, and expire after 24 hours.', 'submission-inbox' ); ?></p></div>
				<?php if ( $has_error ) : ?>
					<p class="subin-log-export-error" role="alert"><?php esc_html_e( 'Export storage is temporarily unavailable. Check WP-Cron and the server temporary directory, then refresh.', 'submission-inbox' ); ?></p>
				<?php else : ?>
					<?php /* translators: %d: number of email events matching the export filters. */ ?>
					<form method="post" class="subin-log-export-start"><input type="hidden" name="subin_email_log_action" value="start_export" /><input type="hidden" name="subin_export_nonce" value="<?php echo esc_attr( $start_nonce ); ?>" /><?php $this->render_hidden_filters( $filters ); ?><span><?php echo esc_html( sprintf( _n( '%d matching event', '%d matching events', $total, 'submission-inbox' ), $total ) ); ?></span><button class="button button-primary" type="submit"><?php esc_html_e( 'Prepare CSV', 'submission-inbox' ); ?></button></form>
				<?php endif; ?>

				<?php if ( ! empty( $jobs ) ) : ?>
					<div class="subin-log-export-jobs" aria-live="polite">
						<?php foreach ( $jobs as $job ) : ?><?php $this->render_export_job( $job, $filters ); ?><?php endforeach; ?>
					</div>
					<?php if ( ! empty( $active ) ) : ?><a class="subin-log-export-refresh" href="<?php echo esc_url( $this->list_url( $filters, 1 ) . '#subin-email-log-exports' ); ?>"><span class="dashicons dashicons-update" aria-hidden="true"></span><?php esc_html_e( 'Refresh progress', 'submission-inbox' ); ?></a><?php endif; ?>
				<?php else : ?>
					<p class="subin-log-export-empty"><?php esc_html_e( 'No prepared exports yet.', 'submission-inbox' ); ?></p>
				<?php endif; ?>
			</div>
		</details>
		<?php
	}

	/** Render one user-owned background export job. */
	private function render_export_job( array $job, array $filters ) {
		$job_id    = sanitize_key( (string) ( $job['id'] ?? '' ) );
		$status    = sanitize_key( (string) ( $job['status'] ?? 'failed' ) );
		$progress  = min( 100, absint( $job['progress'] ?? 0 ) );
		$status_map = array(
			'queued'     => __( 'Queued', 'submission-inbox' ),
			'processing' => __( 'Processing', 'submission-inbox' ),
			'completed'  => __( 'Ready', 'submission-inbox' ),
			'failed'     => __( 'Failed', 'submission-inbox' ),
		);
		$download_nonce = 'completed' === $status ? $this->exports->create_nonce( 'download', $job_id ) : null;
		$delete_nonce   = $this->exports->create_nonce( 'delete', $job_id );
		?>
		<article class="subin-log-export-job subin-log-export-job--<?php echo esc_attr( $status ); ?>">
			<?php /* translators: %1$d: processed email events, %2$d: total email events in the export. */ ?>
			<div class="subin-log-export-job__head"><div><strong><?php echo esc_html( $status_map[ $status ] ?? __( 'Unknown', 'submission-inbox' ) ); ?></strong><small><?php echo esc_html( sprintf( __( '%1$d of %2$d events', 'submission-inbox' ), absint( $job['processed'] ?? 0 ), absint( $job['total'] ?? 0 ) ) ); ?></small></div><time datetime="<?php echo esc_attr( gmdate( DATE_ATOM, absint( $job['created_at'] ?? 0 ) ) ); ?>"><?php echo esc_html( $this->format_export_timestamp( $job['created_at'] ?? 0 ) ); ?></time></div>
			<?php /* translators: %d: export completion percentage. */ ?>
			<?php if ( in_array( $status, array( 'queued', 'processing' ), true ) ) : ?><div class="subin-log-export-progress" role="progressbar" aria-label="<?php esc_attr_e( 'Export progress', 'submission-inbox' ); ?>" aria-valuemin="0" aria-valuemax="100" aria-valuenow="<?php echo esc_attr( $progress ); ?>"><span style="width: <?php echo esc_attr( $progress ); ?>%"></span></div><span class="screen-reader-text"><?php echo esc_html( sprintf( __( '%d percent complete', 'submission-inbox' ), $progress ) ); ?></span><?php endif; ?>
			<?php if ( 'failed' === $status && ! empty( $job['error'] ) ) : ?><p class="subin-log-export-error"><?php echo esc_html( $job['error'] ); ?></p><?php endif; ?>
			<?php if ( ! empty( $job['truncated'] ) ) : ?><p class="subin-log-export-warning"><?php esc_html_e( 'This file reached the protected row or size limit. Narrow the filters for a complete export.', 'submission-inbox' ); ?></p><?php endif; ?>
			<div class="subin-log-export-job__actions">
				<?php if ( 'completed' === $status && ! is_wp_error( $download_nonce ) ) : ?><a class="button button-primary" href="<?php echo esc_url( $this->background_export_download_url( $job_id, $download_nonce, $filters ) ); ?>"><?php esc_html_e( 'Download', 'submission-inbox' ); ?><?php if ( ! empty( $job['file_size'] ) ) : ?> <small>(<?php echo esc_html( size_format( absint( $job['file_size'] ) ) ); ?>)</small><?php endif; ?></a><?php endif; ?>
				<?php if ( ! is_wp_error( $delete_nonce ) ) : ?><form method="post"><input type="hidden" name="subin_email_log_action" value="delete_export" /><input type="hidden" name="export_id" value="<?php echo esc_attr( $job_id ); ?>" /><input type="hidden" name="subin_export_nonce" value="<?php echo esc_attr( $delete_nonce ); ?>" /><?php $this->render_hidden_filters( $filters ); ?><button class="button" type="submit"><?php esc_html_e( 'Remove', 'submission-inbox' ); ?></button></form><?php endif; ?>
			</div>
			<?php /* translators: %s: time remaining before the export expires. */ ?>
			<?php if ( ! empty( $job['expires_at'] ) ) : ?><small class="subin-log-export-expiry"><?php echo esc_html( sprintf( __( 'Expires in %s', 'submission-inbox' ), human_time_diff( time(), absint( $job['expires_at'] ) ) ) ); ?></small><?php endif; ?>
		</article>
		<?php
	}

	/**
	 * Render compact, scannable event list with the selected detail inline.
	 *
	 * @return bool Whether the selected event was rendered below its list row.
	 */
	private function render_events_table( array $events, $total, array $filters, array $display, $selected_event ) {
		if ( empty( $events ) ) {
			if ( $this->has_active_filters( $filters ) || $total > 0 ) {
				SUBIN_View::empty_state( __( 'No events match this view', 'submission-inbox' ), __( 'Remove a filter or broaden the search period.', 'submission-inbox' ) );
			} else {
				SUBIN_View::empty_state( __( 'No email events yet', 'submission-inbox' ), __( 'New WordPress email attempts will appear here. Message bodies and attachments are never copied into the log.', 'submission-inbox' ) );
			}
			return false;
		}

		$columns = is_array( $display['columns'] ?? null ) ? $display['columns'] : array( 'type_origin', 'outcome', 'related' );
		$density = 'compact' === ( $display['density'] ?? '' ) ? 'compact' : 'comfortable';
		$column_count = 3;
		foreach ( array( 'type_origin', 'outcome', 'related' ) as $optional_column ) {
			if ( in_array( $optional_column, $columns, true ) ) {
				++$column_count;
			}
		}
		$detail_rendered = false;
		?>
		<div class="subin-log-table-card subin-log-table-card--<?php echo esc_attr( $density ); ?>" tabindex="0" role="region" aria-label="<?php esc_attr_e( 'Email events', 'submission-inbox' ); ?>">
			<table class="widefat subin-log-table">
				<caption class="screen-reader-text"><?php esc_html_e( 'Email event history. Open an event to inspect its full audit context.', 'submission-inbox' ); ?></caption>
				<thead><tr>
					<th scope="col"><?php esc_html_e( 'Time', 'submission-inbox' ); ?></th>
					<th scope="col"><?php esc_html_e( 'Message', 'submission-inbox' ); ?></th>
					<?php if ( in_array( 'type_origin', $columns, true ) ) : ?><th scope="col"><?php esc_html_e( 'Type / Origin', 'submission-inbox' ); ?></th><?php endif; ?>
					<?php if ( in_array( 'outcome', $columns, true ) ) : ?><th scope="col"><?php esc_html_e( 'Outcome', 'submission-inbox' ); ?></th><?php endif; ?>
					<?php if ( in_array( 'related', $columns, true ) ) : ?><th scope="col"><?php esc_html_e( 'Related', 'submission-inbox' ); ?></th><?php endif; ?>
					<th scope="col"><span class="screen-reader-text"><?php esc_html_e( 'Event details', 'submission-inbox' ); ?></span></th>
				</tr></thead>
				<tbody>
					<?php foreach ( $events as $event ) : ?>
						<?php $this->render_event_row( $event, $filters, $columns, $selected_event ); ?>
						<?php if ( $selected_event && absint( $selected_event->id ?? 0 ) === absint( $event->id ?? 0 ) ) : ?>
							<?php $this->render_event_detail_row( $selected_event, $filters, $column_count ); ?>
							<?php $detail_rendered = true; ?>
						<?php endif; ?>
					<?php endforeach; ?>
				</tbody>
			</table>
		</div>
		<?php

		return $detail_rendered;
	}

	/** Render Event Detail immediately below its selected table row. */
	private function render_event_detail_row( $event, array $filters, $column_count ) {
		?>
		<tr class="subin-log-detail-row">
			<td colspan="<?php echo esc_attr( max( 1, absint( $column_count ) ) ); ?>">
				<?php $this->render_event_detail( $event, $filters ); ?>
			</td>
		</tr>
		<?php
	}

	/** Render one compact event row. */
	private function render_event_row( $event, array $filters, array $columns, $selected_event ) {
		$presentation = $this->presenter->present( $event );
		$outcome      = $presentation['outcome'];
		$type         = $presentation['email_type'];
		$origin       = $presentation['origin'];
		$event_id     = absint( $event->id ?? 0 );
		$selected     = $selected_event && absint( $selected_event->id ?? 0 ) === $event_id;
		$details_url  = $selected
			? $this->list_url( $filters, max( 1, SUBIN_Request::get_absint( 'paged', 1 ) ) ) . '#subin-email-event-' . $event_id
			: $this->event_url( $event_id, $filters );
		if ( $selected ) {
			/* translators: %d: email event ID. */
			$details_label = sprintf( __( 'Hide details for event #%d', 'submission-inbox' ), $event_id );
		} else {
			/* translators: %d: email event ID. */
			$details_label = sprintf( __( 'View details for event #%d', 'submission-inbox' ), $event_id );
		}
		$issue        = 'failed' === $outcome['key']
			? $this->error_classifier->classify( (string) ( $event->error_message ?? '' ) )
			: ( 'not_generated' === $outcome['key'] ? $this->no_email_reason( $event ) : array() );
		?>
		<tr id="subin-email-event-<?php echo esc_attr( $event_id ); ?>" class="subin-log-row subin-log-row--<?php echo esc_attr( $outcome['tone'] ); ?><?php echo $selected ? ' is-selected' : ''; ?>"<?php echo $selected ? ' aria-current="true"' : ''; ?>>
			<td data-label="<?php esc_attr_e( 'Time', 'submission-inbox' ); ?>"><time datetime="<?php echo esc_attr( $this->datetime_attribute( $event->created_at ?? '' ) ); ?>" title="<?php echo esc_attr( $this->format_date( $event->created_at ?? '' ) ); ?>"><?php echo esc_html( $this->format_compact_date( $event->created_at ?? '' ) ); ?></time></td>
			<td data-label="<?php esc_attr_e( 'Message', 'submission-inbox' ); ?>">
				<a class="subin-log-message" href="<?php echo esc_url( $details_url ); ?>" aria-expanded="<?php echo $selected ? 'true' : 'false'; ?>"<?php echo $selected ? ' aria-controls="subin-email-log-event-detail"' : ''; ?>><?php echo esc_html( '' !== trim( (string) ( $event->subject ?? '' ) ) ? $event->subject : __( 'No subject', 'submission-inbox' ) ); ?></a>
				<?php /* translators: %1$s: masked recipient address, %2$d: email event ID. */ ?>
				<span class="subin-log-message__meta"><?php echo esc_html( sprintf( __( 'To %1$s · Event #%2$d', 'submission-inbox' ), $this->mask_recipient( (string) ( $event->to_email ?? '' ) ), $event_id ) ); ?></span>
			</td>
			<?php if ( in_array( 'type_origin', $columns, true ) ) : ?><td data-label="<?php esc_attr_e( 'Type / Origin', 'submission-inbox' ); ?>"><span class="subin-log-type"><?php echo esc_html( $type['label'] ); ?></span><span class="subin-log-origin"><?php echo esc_html( $origin['label'] ); ?><?php echo '' !== $origin['form_name'] ? ' · ' . esc_html( $origin['form_name'] ) : ''; ?></span></td><?php endif; ?>
			<?php if ( in_array( 'outcome', $columns, true ) ) : ?><td data-label="<?php esc_attr_e( 'Outcome', 'submission-inbox' ); ?>"><?php SUBIN_View::badge( $outcome['label'], $outcome['tone'] ); ?><?php if ( ! empty( $issue['title'] ) ) : ?><span class="subin-log-outcome-reason"><?php echo esc_html( $issue['title'] ); ?></span><?php elseif ( 'stalled' === $outcome['key'] ) : ?><span class="subin-log-outcome-reason"><?php esc_html_e( 'Pending longer than expected', 'submission-inbox' ); ?></span><?php endif; ?></td><?php endif; ?>
			<?php if ( in_array( 'related', $columns, true ) ) : ?><td data-label="<?php esc_attr_e( 'Related', 'submission-inbox' ); ?>"><?php $this->render_related_summary( $event, $origin ); ?></td><?php endif; ?>
			<td class="subin-log-row__open"><a href="<?php echo esc_url( $details_url ); ?>" aria-label="<?php echo esc_attr( $details_label ); ?>" aria-expanded="<?php echo $selected ? 'true' : 'false'; ?>"<?php echo $selected ? ' aria-controls="subin-email-log-event-detail"' : ''; ?>><span class="dashicons <?php echo $selected ? 'dashicons-arrow-up-alt2' : 'dashicons-arrow-down-alt2'; ?>" aria-hidden="true"></span></a></td>
		</tr>
		<?php
	}

	/** Render the permanent event detail panel. */
	private function render_event_detail( $event, array $filters ) {
		$presentation = $this->presenter->present( $event );
		$outcome      = $presentation['outcome'];
		$type         = $presentation['email_type'];
		$origin       = $presentation['origin'];
		$fingerprint  = $presentation['fingerprint'];
		$evidence     = $presentation['evidence'];
		$event_id     = absint( $event->id ?? 0 );
		$attempts     = $this->events->get_related_attempts_checked( $event );
		$attempts_error = is_wp_error( $attempts );
		$attempts       = $attempts_error ? array() : $attempts;
		$error        = $this->error_classifier->sanitize_technical_error( (string) ( $event->error_message ?? '' ) );
		$issue        = 'failed' === $outcome['key']
			? $this->error_classifier->classify( $error )
			: ( 'not_generated' === $outcome['key'] ? $this->no_email_reason( $event ) : array() );
		?>
		<aside id="subin-email-log-event-detail" class="subin-event-detail subin-event-detail--<?php echo esc_attr( $outcome['tone'] ); ?>" aria-labelledby="subin-event-detail-title" tabindex="-1" data-subin-event-detail>
			<header class="subin-event-detail__header">
				<?php /* translators: %d: email event ID. */ ?>
				<div><span class="subin-event-detail__eyebrow"><?php echo esc_html( sprintf( __( 'Event #%d', 'submission-inbox' ), $event_id ) ); ?></span><h2 id="subin-event-detail-title"><?php echo esc_html( '' !== trim( (string) ( $event->subject ?? '' ) ) ? $event->subject : __( 'No subject', 'submission-inbox' ) ); ?></h2></div>
				<a class="subin-event-detail__close" data-subin-event-detail-close href="<?php echo esc_url( $this->list_url( $filters, max( 1, SUBIN_Request::get_absint( 'paged', 1 ) ) ) . '#subin-email-event-' . $event_id ); ?>" aria-label="<?php esc_attr_e( 'Close event details', 'submission-inbox' ); ?>"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></a>
			</header>

			<section class="subin-event-outcome" aria-labelledby="subin-event-outcome-title">
				<div class="subin-event-outcome__heading"><?php SUBIN_View::badge( $outcome['label'], $outcome['tone'] ); ?><?php if ( ! empty( $evidence ) ) : ?><span class="subin-event-outcome__verified"><span class="dashicons dashicons-lock" aria-hidden="true"></span><?php esc_html_e( 'Provider evidence verified', 'submission-inbox' ); ?></span><?php endif; ?></div>
				<h3 id="subin-event-outcome-title" class="screen-reader-text"><?php esc_html_e( 'Outcome', 'submission-inbox' ); ?></h3>
				<p><?php echo esc_html( 'not_generated' === $outcome['key'] ? __( 'No email message reached WordPress transport.', 'submission-inbox' ) : $outcome['description'] ); ?></p>
			</section>

			<?php if ( ! empty( $issue ) ) : ?>
				<section class="subin-event-next-step<?php echo ! empty( $issue['expected'] ) ? ' subin-event-next-step--expected' : ''; ?>" aria-labelledby="subin-event-next-step-title"><span class="dashicons <?php echo ! empty( $issue['expected'] ) ? 'dashicons-info-outline' : 'dashicons-sos'; ?>" aria-hidden="true"></span><div><h3 id="subin-event-next-step-title"><?php echo esc_html( $issue['title'] ); ?></h3><p><?php echo esc_html( $issue['explanation'] ); ?></p><strong><?php echo esc_html( $issue['recommended_action'] ); ?></strong></div></section>
			<?php elseif ( 'stalled' === $outcome['key'] || 'awaiting' === $outcome['key'] ) : ?>
				<section class="subin-event-next-step" aria-labelledby="subin-event-next-step-title"><span class="dashicons dashicons-clock" aria-hidden="true"></span><div><h3 id="subin-event-next-step-title"><?php esc_html_e( 'Review the sending path', 'submission-inbox' ); ?></h3><p><?php esc_html_e( 'Open Email Health to check whether the site has a current transport or configuration incident.', 'submission-inbox' ); ?></p></div></section>
			<?php endif; ?>

			<?php $this->render_detail_actions( $event, $origin, $filters ); ?>

			<section class="subin-event-detail__section" aria-labelledby="subin-event-message-title"><h3 id="subin-event-message-title"><?php esc_html_e( 'Message', 'submission-inbox' ); ?></h3><dl class="subin-event-facts">
				<?php $this->render_fact( __( 'To', 'submission-inbox' ), $event->to_email ?? '', true ); ?>
				<?php $this->render_fact( __( 'Requested/configured From', 'submission-inbox' ), $event->from_email ?? '', true ); ?>
				<?php $this->render_fact( __( 'Subject', 'submission-inbox' ), $event->subject ?? __( 'No subject', 'submission-inbox' ), true ); ?>
			</dl></section>

			<section class="subin-event-detail__section" aria-labelledby="subin-event-timeline-title"><h3 id="subin-event-timeline-title"><?php esc_html_e( 'Timeline', 'submission-inbox' ); ?></h3><?php $this->render_event_timeline( $event, $evidence ); ?></section>

			<section class="subin-event-detail__section" aria-labelledby="subin-event-context-title"><h3 id="subin-event-context-title"><?php esc_html_e( 'Audit context', 'submission-inbox' ); ?></h3><dl class="subin-event-facts subin-event-facts--grid">
				<?php $this->render_fact( __( 'Email type', 'submission-inbox' ), $type['label'] ); ?>
				<?php $this->render_fact( __( 'Origin', 'submission-inbox' ), $origin['label'] ); ?>
				<?php $this->render_fact( __( 'Form', 'submission-inbox' ), $this->form_context_label( $origin ) ); ?>
				<?php $this->render_fact( __( 'Lead', 'submission-inbox' ), ! empty( $event->lead_id ) ? '#' . absint( $event->lead_id ) : '' ); ?>
				<?php $this->render_fact( __( 'Rule', 'submission-inbox' ), ! empty( $event->rule_id ) ? ( (string) ( $event->rule_name ?? '' ) ?: '#' . absint( $event->rule_id ) ) : '' ); ?>
				<?php $this->render_fact( __( 'Rule revision', 'submission-inbox' ), ! empty( $event->rule_version ) ? 'v' . absint( $event->rule_version ) : '' ); ?>
				<?php $this->render_fact( __( 'Current rule state', 'submission-inbox' ), ! empty( $event->rule_status ) ? ucfirst( sanitize_key( $event->rule_status ) ) : '' ); ?>
				<?php $this->render_fact( __( 'Saved design', 'submission-inbox' ), ! empty( $event->template_id ) ? ( (string) ( $event->template_name ?? '' ) ?: '#' . absint( $event->template_id ) ) : '' ); ?>
				<?php $this->render_fact( __( 'Delivery', 'submission-inbox' ), $this->delivery_label( $event ) ); ?>
				<?php $this->render_fact( __( 'Job', 'submission-inbox' ), ! empty( $event->job_id ) ? '#' . absint( $event->job_id ) : '' ); ?>
				<?php $this->render_fact( __( 'Job state', 'submission-inbox' ), ! empty( $event->job_status ) ? ucfirst( sanitize_key( $event->job_status ) ) : '' ); ?>
				<?php $this->render_fact( __( 'Worker attempts', 'submission-inbox' ), isset( $event->job_attempts ) ? (string) absint( $event->job_attempts ) : '' ); ?>
				<?php $this->render_fact( __( 'Correlation', 'submission-inbox' ), $event->correlation_id ?? '' ); ?>
				<?php $this->render_fact( __( 'Parent event', 'submission-inbox' ), ! empty( $event->parent_event_id ) ? '#' . absint( $event->parent_event_id ) : '' ); ?>
				<?php $this->render_fact( __( 'Retry of event', 'submission-inbox' ), ! empty( $event->retry_of_event_id ) ? '#' . absint( $event->retry_of_event_id ) : '' ); ?>
			</dl></section>

			<?php if ( ! empty( $evidence ) ) : ?>
				<section class="subin-event-detail__section" aria-labelledby="subin-event-evidence-title"><h3 id="subin-event-evidence-title"><?php esc_html_e( 'Provider evidence', 'submission-inbox' ); ?></h3><p class="subin-event-detail__help"><?php esc_html_e( 'These facts came from an authenticated provider signal, not from wp_mail().', 'submission-inbox' ); ?></p><dl class="subin-event-facts subin-event-facts--grid"><?php $this->render_fact( __( 'Provider status', 'submission-inbox' ), ucfirst( $evidence['status'] ) ); ?><?php $this->render_fact( __( 'Message ID', 'submission-inbox' ), $evidence['message_id'], true ); ?><?php $this->render_fact( __( 'Evidence source', 'submission-inbox' ), $evidence['source'] ); ?><?php $this->render_fact( __( 'Occurred', 'submission-inbox' ), $this->format_date( $evidence['occurred_at'] ) ); ?></dl></section>
			<?php endif; ?>

			<?php if ( '' !== $error || ! empty( $event->reason_code ) ) : ?>
				<details class="subin-event-technical"><summary><?php esc_html_e( 'Technical details', 'submission-inbox' ); ?></summary><dl class="subin-event-facts"><?php $this->render_fact( __( 'Event source', 'submission-inbox' ), $event->source ?? '' ); ?><?php $this->render_fact( __( 'Event kind', 'submission-inbox' ), $event->effective_event_kind ?? $event->event_kind ?? '' ); ?><?php $this->render_fact( __( 'Reason code', 'submission-inbox' ), $event->reason_code ?? '' ); ?><?php $this->render_fact( __( 'Sanitized error', 'submission-inbox' ), $error, true ); ?></dl></details>
			<?php endif; ?>

				<section class="subin-event-privacy" aria-labelledby="subin-event-privacy-title"><span class="dashicons dashicons-privacy" aria-hidden="true"></span><div><h3 id="subin-event-privacy-title"><?php esc_html_e( 'Privacy-first record', 'submission-inbox' ); ?></h3><p><?php esc_html_e( 'The email body and attachments are not stored in Email Log.', 'submission-inbox' ); ?><?php if ( ! empty( $fingerprint ) ) : ?> <?php echo esc_html( $fingerprint['description'] ); ?> <code><?php echo esc_html( $fingerprint['value'] ); ?>…</code><?php endif; ?></p></div></section>
				<?php if ( $attempts_error ) : ?><div class="notice notice-error inline"><p><?php esc_html_e( 'Related attempts are temporarily unavailable. The original event has not been changed.', 'submission-inbox' ); ?></p></div><?php endif; ?>

			<?php $this->render_related_attempts( $attempts, $event, $filters ); ?>
			<?php $this->render_retry_control( $event, $presentation, $filters ); ?>
		</aside>
		<?php
	}

	/** Render a stable database-unavailable state instead of a false empty log. */
	private function render_data_unavailable() {
		?>
		<div class="notice notice-error inline subin-email-log-unavailable" role="alert">
			<p><strong><?php esc_html_e( 'Email Log is temporarily unavailable.', 'submission-inbox' ); ?></strong></p>
			<p><?php esc_html_e( 'InboxMend could not read the event ledger. No events were reported as missing or successful. Refresh the page after checking the database connection.', 'submission-inbox' ); ?></p>
		</div>
		<?php
	}

	/** Render context links without exposing unavailable admin routes. */
	private function render_detail_actions( $event, array $origin, array $filters ) {
		$actions = array();
		if ( ! empty( $event->linked_lead_id ) && \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::VIEW_LEADS ) ) {
			$actions[] = array( 'label' => __( 'Open lead', 'submission-inbox' ), 'url' => $this->lead_detail_url( absint( $event->lead_id ) ) );
		}
		if ( 'submission_inbox_form' === $origin['key'] && ctype_digit( (string) $origin['form_id'] ) && \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::VIEW_FORMS ) ) {
			$actions[] = array( 'label' => __( 'Open form', 'submission-inbox' ), 'url' => $this->form_url( absint( $origin['form_id'] ) ) );
		}
		if ( ! empty( $event->rule_id ) && \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::MANAGE_AUTO_REPLIES ) ) {
			$actions[] = array( 'label' => __( 'Open rule', 'submission-inbox' ), 'url' => $this->rule_url( absint( $event->rule_id ) ) );
		}
		if ( ! empty( $event->template_id ) && \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::MANAGE_AUTO_REPLIES ) ) {
			$actions[] = array( 'label' => __( 'Open saved design', 'submission-inbox' ), 'url' => $this->template_url( absint( $event->template_id ) ) );
		}
		if ( \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::VIEW_EMAIL_HEALTH ) ) {
			$actions[] = array( 'label' => __( 'Email Health', 'submission-inbox' ), 'url' => $this->email_health_url( $filters ) );
		}
		if ( \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::MANAGE_SENDING ) ) {
			$actions[] = array( 'label' => __( 'Sending settings', 'submission-inbox' ), 'url' => admin_url( 'admin.php?page=subin-settings&subin_settings_tab=sending' ) );
		}
		?>
		<nav class="subin-event-actions" aria-label="<?php esc_attr_e( 'Related actions', 'submission-inbox' ); ?>"><?php foreach ( $actions as $action ) : ?><a class="button" href="<?php echo esc_url( $action['url'] ); ?>"><?php echo esc_html( $action['label'] ); ?></a><?php endforeach; ?><button type="button" class="button subin-copy-shortcode" data-subin-copy="<?php echo esc_attr( $this->permanent_event_url( absint( $event->id ?? 0 ) ) ); ?>"><?php esc_html_e( 'Copy event link', 'submission-inbox' ); ?></button><span class="subin-copy-feedback" aria-live="polite"></span></nav>
		<?php
	}

	/** Render event timeline. */
	private function render_event_timeline( $event, array $evidence ) {
		$steps = array(
			array( 'label' => __( 'Created', 'submission-inbox' ), 'date' => $event->created_at ?? '', 'note' => __( 'WordPress mail attempt recorded', 'submission-inbox' ) ),
		);
		if ( ! empty( $event->scheduled_at ) ) {
			$steps[] = array( 'label' => __( 'Scheduled', 'submission-inbox' ), 'date' => $event->scheduled_at, 'note' => __( 'Earliest planned send time', 'submission-inbox' ) );
		}
		if ( ! empty( $event->job_available_at ) && (string) $event->job_available_at !== (string) ( $event->scheduled_at ?? '' ) ) {
			$steps[] = array( 'label' => __( 'Queue eligible', 'submission-inbox' ), 'date' => $event->job_available_at, 'note' => __( 'Job became available to the worker', 'submission-inbox' ) );
		}
		$completed = $event->completed_at ?? '';
		if ( '' === (string) $completed && in_array( sanitize_key( (string) ( $event->status ?? '' ) ), array( 'sent', 'failed', 'unknown', 'no_email_generated' ), true ) ) {
			$completed = $event->updated_at ?? '';
		}
		if ( ! empty( $completed ) ) {
			$steps[] = array( 'label' => __( 'Outcome recorded', 'submission-inbox' ), 'date' => $completed, 'note' => __( 'WordPress observation completed', 'submission-inbox' ) );
		}
		if ( ! empty( $event->updated_at ) && (string) $event->updated_at !== (string) $completed ) {
			$steps[] = array( 'label' => __( 'Last updated', 'submission-inbox' ), 'date' => $event->updated_at, 'note' => __( 'Audit record last updated', 'submission-inbox' ) );
		}
		if ( ! empty( $event->job_sent_at ) && (string) $event->job_sent_at !== (string) $completed ) {
			$steps[] = array( 'label' => __( 'Job completed', 'submission-inbox' ), 'date' => $event->job_sent_at, 'note' => __( 'Notification worker recorded completion', 'submission-inbox' ) );
		}
		if ( ! empty( $evidence['occurred_at'] ) ) {
			$steps[] = array( 'label' => __( 'Provider evidence', 'submission-inbox' ), 'date' => $evidence['occurred_at'], 'note' => ucfirst( $evidence['status'] ) );
		}
		?>
		<ol class="subin-event-timeline"><?php foreach ( $steps as $step ) : ?><li><span class="subin-event-timeline__marker" aria-hidden="true"></span><div><strong><?php echo esc_html( $step['label'] ); ?></strong><time datetime="<?php echo esc_attr( $this->datetime_attribute( $step['date'] ) ); ?>"><?php echo esc_html( $this->format_date( $step['date'] ) ); ?></time><small><?php echo esc_html( $step['note'] ); ?></small></div></li><?php endforeach; ?></ol>
		<?php
	}

	/**
	 * Explain a stable no-email reason without exposing arbitrary stored error text.
	 *
	 * Expected skips remain visible in the audit trail but are distinguished from
	 * configuration or submission problems that need attention.
	 *
	 * @param object $event Event row.
	 * @return array{title:string,explanation:string,recommended_action:string,expected:bool}
	 */
	private function no_email_reason( $event ) {
		$code = sanitize_key( (string) ( $event->reason_code ?? '' ) );

		if ( in_array( $code, array( 'conditions_not_met', 'rule_conditions_not_met', 'no_matching_rule', 'rule_not_matched' ), true ) ) {
			return array(
				'title'              => __( 'Rule conditions did not match', 'submission-inbox' ),
				'explanation'        => __( 'This submission did not meet the conditions required to create this email.', 'submission-inbox' ),
				'recommended_action' => __( 'No action is needed unless this submission should have matched the rule.', 'submission-inbox' ),
				'expected'           => true,
			);
		}

		if ( in_array( $code, array( 'auto_reply_disabled', 'rule_disabled', 'notification_disabled', 'not_configured', 'intentional_skip' ), true ) ) {
			return array(
				'title'              => __( 'Email was intentionally skipped', 'submission-inbox' ),
				'explanation'        => __( 'The related notification or auto-reply was not enabled when this event was created.', 'submission-inbox' ),
				'recommended_action' => __( 'No action is needed. Enable the related rule or notification only if an email should be created.', 'submission-inbox' ),
				'expected'           => true,
			);
		}

		if ( in_array( $code, array( 'missing_content', 'empty_content', 'template_missing', 'no_content' ), true ) ) {
			return array(
				'title'              => __( 'Email content is missing', 'submission-inbox' ),
				'explanation'        => __( 'The rule could not produce a usable email body from its current content or saved design.', 'submission-inbox' ),
				'recommended_action' => __( 'Open the related rule, add content or choose a saved design, then run a safe test.', 'submission-inbox' ),
				'expected'           => false,
			);
		}

		if ( 'subject_too_long' === $code ) {
			return array(
				'title'              => __( 'Rendered subject is too long', 'submission-inbox' ),
				'explanation'        => __( 'Merge-tag values expanded the subject beyond the safe sending limit.', 'submission-inbox' ),
				'recommended_action' => __( 'Shorten the rule subject or the merge-tag values it includes, then run a safe test.', 'submission-inbox' ),
				'expected'           => false,
			);
		}

		if ( in_array( $code, array( 'content_too_large', 'body_too_large' ), true ) ) {
			return array(
				'title'              => __( 'Rendered email is too large', 'submission-inbox' ),
				'explanation'        => __( 'The generated email exceeded the bounded message-size safety limit.', 'submission-inbox' ),
				'recommended_action' => __( 'Reduce the design, content, or large merge-tag values, then run a safe test.', 'submission-inbox' ),
				'expected'           => false,
			);
		}

		if ( in_array( $code, array( 'auto_reply_email_field_not_selected', 'auto_reply_email_field_not_found' ), true ) ) {
			return array(
				'title'              => __( 'Visitor email field is not available', 'submission-inbox' ),
				'explanation'        => __( 'The rule does not point to an Email field that exists on the current form.', 'submission-inbox' ),
				'recommended_action' => __( 'Open the related rule and select an existing Email field as the visitor recipient.', 'submission-inbox' ),
				'expected'           => false,
			);
		}

		if ( in_array( $code, array( 'auto_reply_email_empty', 'recipient_missing', 'missing_recipient', 'recipient_empty', 'notification_recipient_missing' ), true ) ) {
			return array(
				'title'              => __( 'Recipient email is missing', 'submission-inbox' ),
				'explanation'        => __( 'The selected recipient field did not provide an email address for this message.', 'submission-inbox' ),
				'recommended_action' => __( 'Check the submitted email and make the selected Email field required if every visitor should receive a message.', 'submission-inbox' ),
				'expected'           => false,
			);
		}

		if ( in_array( $code, array( 'auto_reply_email_invalid', 'invalid_recipient', 'recipient_invalid' ), true ) ) {
			return array(
				'title'              => __( 'Recipient email is invalid', 'submission-inbox' ),
				'explanation'        => __( 'The selected recipient value was not a valid email address, so sending was stopped safely.', 'submission-inbox' ),
				'recommended_action' => __( 'Review the submitted value and the rule’s selected Email field before trying again.', 'submission-inbox' ),
				'expected'           => false,
			);
		}

		return array(
			'title'              => __( 'No email was created', 'submission-inbox' ),
			'explanation'        => __( 'This event did not include a recognized safe explanation for why generation stopped.', 'submission-inbox' ),
			'recommended_action' => __( 'Review the related form and rule settings. The machine reason remains available under Technical details.', 'submission-inbox' ),
			'expected'           => false,
		);
	}

	/** Render immutable related attempts. */
	private function render_related_attempts( array $attempts, $event, array $filters ) {
		if ( count( $attempts ) <= 1 ) {
			return;
		}
		$event_id       = absint( $event->id ?? 0 );
		$is_attempt_set = ! empty( $event->job_id ) || ! empty( $event->parent_event_id ) || ! empty( $event->retry_of_event_id );
		$heading        = $is_attempt_set ? __( 'Attempt history', 'submission-inbox' ) : __( 'Related events', 'submission-inbox' );
		$description    = $is_attempt_set
			? __( 'Every attempt remains a separate immutable event. Retrying never rewrites the original history.', 'submission-inbox' )
			: __( 'These events share a narrowly matched request context. They may represent different messages from the same workflow.', 'submission-inbox' );
		?>
		<section class="subin-event-detail__section" aria-labelledby="subin-event-attempts-title"><h3 id="subin-event-attempts-title"><?php echo esc_html( sprintf( '%1$s (%2$d)', $heading, count( $attempts ) ) ); ?></h3><p class="subin-event-detail__help"><?php echo esc_html( $description ); ?></p><ol class="subin-event-attempts">
			<?php /* translators: %d: email event ID. */ ?>
			<?php foreach ( $attempts as $attempt ) : ?><?php $attempt_outcome = $this->presenter->outcome( $attempt ); $current = absint( $attempt->id ?? 0 ) === absint( $event_id ); ?><li class="<?php echo $current ? 'is-current' : ''; ?>"><a href="<?php echo esc_url( $this->event_url( absint( $attempt->id ?? 0 ), $filters ) ); ?>"><span><?php echo esc_html( sprintf( __( 'Event #%d', 'submission-inbox' ), absint( $attempt->id ?? 0 ) ) ); ?><?php if ( $current ) : ?> <small><?php esc_html_e( 'Current', 'submission-inbox' ); ?></small><?php endif; ?></span><strong><?php echo esc_html( $attempt_outcome['label'] ); ?></strong><time><?php echo esc_html( $this->format_compact_date( $attempt->created_at ?? '' ) ); ?></time></a></li><?php endforeach; ?>
		</ol></section>
		<?php
	}

	/** Render retry only for a repository-verified exact immutable failed job. */
	private function render_retry_control( $event, array $presentation, array $filters ) {
		if ( 'failed' !== ( $presentation['outcome']['key'] ?? '' ) || ! \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::RETRY_NOTIFICATIONS ) ) {
			return;
		}
		$job_id    = absint( $event->job_id ?? 0 );
		$lead_id   = absint( $event->lead_id ?? 0 );
		$email_type = sanitize_key( (string) ( $event->email_type ?? $event->job_email_type ?? '' ) );
		if ( $job_id <= 0 || $lead_id <= 0 || '' === $email_type ) {
			return;
		}
		$state = $this->jobs->get_event_retry_state( $job_id, $lead_id, $email_type, absint( $event->id ?? 0 ) );
		?>
		<section class="subin-event-retry" aria-labelledby="subin-event-retry-title"><h3 id="subin-event-retry-title"><?php esc_html_e( 'Safe retry', 'submission-inbox' ); ?></h3>
			<?php if ( ! empty( $state['retryable'] ) ) : ?><p><?php esc_html_e( 'This InboxMend job still has its immutable message snapshot. A retry will create a new linked attempt and preserve this failure.', 'submission-inbox' ); ?></p><form method="post"><input type="hidden" name="subin_email_log_action" value="retry_event" /><input type="hidden" name="event_id" value="<?php echo esc_attr( absint( $event->id ?? 0 ) ); ?>" /><?php wp_nonce_field( self::ACTION_NONCE, 'subin_email_log_nonce' ); ?><?php $this->render_hidden_filters( $filters ); ?><button class="button button-primary" type="submit"><?php esc_html_e( 'Queue exact retry', 'submission-inbox' ); ?></button></form>
			<?php elseif ( ! empty( $state['queued'] ) ) : ?><p class="subin-event-retry__queued"><span class="dashicons dashicons-clock" aria-hidden="true"></span><?php esc_html_e( 'This exact job is already queued or processing.', 'submission-inbox' ); ?></p>
			<?php else : ?><p><?php esc_html_e( 'A safe retry is unavailable because the immutable snapshot expired or this event did not come from an InboxMend notification job. The log never reconstructs historical emails from current settings.', 'submission-inbox' ); ?></p><?php endif; ?>
		</section>
		<?php
	}

	/** Render related context in one compact cell. */
	private function render_related_summary( $event, array $origin ) {
		$items = array();
		if ( ! empty( $event->lead_id ) ) {
			$items[] = empty( $event->linked_lead_id ) ? __( 'Deleted lead', 'submission-inbox' ) : '#' . absint( $event->lead_id );
		}
		$form = $this->form_context_label( $origin );
		if ( '' !== $form ) {
			$items[] = $form;
		}
		if ( ! empty( $event->rule_id ) ) {
			/* translators: %d: auto-reply rule ID. */
			$items[] = sprintf( __( 'Rule #%d', 'submission-inbox' ), absint( $event->rule_id ) );
		}
		if ( empty( $items ) ) {
			echo '<span class="subin-muted">' . esc_html__( 'None', 'submission-inbox' ) . '</span>';
			return;
		}
		echo '<span class="subin-log-related">' . esc_html( implode( ' · ', $items ) ) . '</span>';
	}

	/** Stream one completed, private background export before admin HTML. */
	private function maybe_export( array $filters, $action = 'download' ) {
		if ( ! \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::EXPORT_EMAIL_LOG ) ) {
			wp_die( esc_html__( 'You do not have permission to export email events.', 'submission-inbox' ) );
		}
		if ( 'download' !== sanitize_key( (string) $action ) ) {
			return;
		}

		$result = $this->exports->download_export(
			SUBIN_Request::get_key( 'export_id' ),
			SUBIN_Request::get_text( 'subin_export_nonce' )
		);
		if ( is_wp_error( $result ) ) {
			$this->redirect_after_export( $filters, $this->export_error_notice( $result ) );
		}
	}

	/** Queue the current filtered scope for private background generation. */
	private function start_background_export() {
		if ( ! \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::EXPORT_EMAIL_LOG ) ) {
			wp_die( esc_html__( 'You do not have permission to export email events.', 'submission-inbox' ) );
		}
		$filters = $this->get_filters_from_post();
		$result  = $this->exports->start_export(
			$filters,
			sanitize_text_field( $this->post_scalar( 'subin_export_nonce' ) )
		);
		$this->redirect_after_export( $filters, is_wp_error( $result ) ? $this->export_error_notice( $result ) : 'export_queued' );
	}

	/** Delete one current-user export using the service's job-scoped nonce. */
	private function delete_background_export() {
		if ( ! \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::EXPORT_EMAIL_LOG ) ) {
			wp_die( esc_html__( 'You do not have permission to export email events.', 'submission-inbox' ) );
		}
		$filters = $this->get_filters_from_post();
		$result  = $this->exports->delete_export(
			sanitize_key( $this->post_scalar( 'export_id' ) ),
			sanitize_text_field( $this->post_scalar( 'subin_export_nonce' ) )
		);
		$this->redirect_after_export( $filters, is_wp_error( $result ) ? $this->export_error_notice( $result ) : 'export_deleted' );
	}

	/** Redirect back to the same safe scope after an export operation. */
	private function redirect_after_export( array $filters, $notice ) {
		$url = add_query_arg( 'subin_notice', sanitize_key( (string) $notice ), $this->list_url( $filters, 1 ) );
		wp_safe_redirect( $url . '#subin-email-log-exports' );
		exit;
	}

	/** Map internal export errors to stable, non-sensitive UI notices. */
	private function export_error_notice( $error ) {
		$code = is_wp_error( $error ) ? sanitize_key( $error->get_error_code() ) : '';
		if ( 'subin_email_log_export_limit_reached' === $code ) {
			return 'export_limit';
		}
		if ( in_array( $code, array( 'subin_email_log_export_schedule_failed', 'subin_email_log_export_not_ready' ), true ) ) {
			return 'export_not_ready';
		}
		if ( false !== strpos( $code, '_file_' ) || false !== strpos( $code, '_temp_' ) ) {
			return 'export_storage_failed';
		}
		return 'export_failed';
	}

	/** Retry one exact failed immutable job after re-reading its event context. */
	private function retry_exact_event( $event_id ) {
		if ( ! \SUBIN\Security\Capabilities::can_current_user( \SUBIN\Security\Capabilities::RETRY_NOTIFICATIONS ) ) {
			return 'retry_denied';
		}
		$event = $this->events->get_event_with_context( $event_id );
		if ( ! $event || 'failed' !== sanitize_key( (string) ( $event->status ?? '' ) ) ) {
			return 'retry_unavailable';
		}
		$job_id     = absint( $event->job_id ?? 0 );
		$lead_id    = absint( $event->lead_id ?? 0 );
		$email_type = sanitize_key( (string) ( $event->email_type ?? $event->job_email_type ?? '' ) );
		$state      = $this->jobs->get_event_retry_state( $job_id, $lead_id, $email_type, absint( $event->id ?? 0 ) );
		if ( empty( $state['retryable'] ) && empty( $state['queued'] ) ) {
			return 'retry_unavailable';
		}
		$result = $this->jobs->retry_event_job( $job_id, $lead_id, $email_type, absint( $event->id ?? 0 ) );
		if ( is_wp_error( $result ) ) {
			return 'retry_failed';
		}
		if ( class_exists( '\SUBIN\Forms\FormSubmissionHandler' ) ) {
			\SUBIN\Forms\FormSubmissionHandler::schedule_notification_worker();
		}
		return 'retry_queued';
	}

	/** Get and validate list filters from GET. */
	private function get_filters_from_request() {
		$legacy_status = SUBIN_Request::get_key( 'subin_status' );
		$outcome       = SUBIN_Request::get_key( 'subin_outcome' );
		if ( '' === $outcome ) {
			$outcome = array(
				'sent'               => 'accepted',
				'failed'             => 'failed',
				'no_email_generated' => 'not_generated',
				'pending'            => 'awaiting',
				'unknown'            => 'awaiting',
			)[ $legacy_status ] ?? '';
		}
		return $this->sanitize_filters(
			array(
				'outcome'     => $outcome,
				'source'      => SUBIN_Request::get_key( 'subin_source' ),
				'provider'    => '',
				'origin'      => SUBIN_Request::get_text( 'subin_origin' ),
				'form_context' => SUBIN_Request::get_text( 'subin_form_context' ),
				'form_id'     => SUBIN_Request::get_text( 'subin_form_id' ),
				'email_type'  => SUBIN_Request::get_key( 'subin_email_type' ),
				'event_kind'  => SUBIN_Request::get_key( 'subin_event_kind' ),
				'rule_id'     => SUBIN_Request::get_absint( 'subin_rule_id', 0 ),
				'date_range'  => SUBIN_Request::get_key( 'subin_date_range' ),
				'date_from'   => SUBIN_Request::get_text( 'subin_date_from' ),
				'date_to'     => SUBIN_Request::get_text( 'subin_date_to' ),
				'search'      => SUBIN_Request::get_text( 's' ),
				'sort'        => SUBIN_Request::get_key( 'subin_sort' ),
			)
		);
	}

	/** Get filters posted by embedded forms without trusting arbitrary query args. */
	private function get_filters_from_post() {
		return $this->sanitize_filters(
			array(
				'outcome'    => sanitize_key( $this->post_scalar( 'filter_outcome' ) ),
				'source'     => sanitize_key( $this->post_scalar( 'filter_source' ) ),
				'provider'   => sanitize_key( $this->post_scalar( 'filter_provider' ) ),
				'form_id'    => sanitize_text_field( $this->post_scalar( 'filter_form_id' ) ),
				'email_type' => sanitize_key( $this->post_scalar( 'filter_email_type' ) ),
				'event_kind' => sanitize_key( $this->post_scalar( 'filter_event_kind' ) ),
				'rule_id'    => absint( $this->post_scalar( 'filter_rule_id' ) ),
				'date_range' => sanitize_key( $this->post_scalar( 'filter_date_range' ) ),
				'date_from'  => sanitize_text_field( $this->post_scalar( 'filter_date_from' ) ),
				'date_to'    => sanitize_text_field( $this->post_scalar( 'filter_date_to' ) ),
				'search'     => sanitize_text_field( $this->post_scalar( 'filter_search' ) ),
				'sort'       => sanitize_key( $this->post_scalar( 'filter_sort' ) ),
			)
		);
	}

	/** Read one scalar POST value without allowing arrays into string APIs. */
	private function post_scalar( $key, $fallback = '' ) {
		$value = isset( $_POST[ $key ] ) ? wp_unslash( $_POST[ $key ] ) : $fallback;
		return is_scalar( $value ) ? (string) $value : (string) $fallback;
	}

	/** Normalize the stable filter contract. */
	private function sanitize_filters( array $raw ) {
		$outcomes = array( 'accepted', 'handled', 'failed', 'not_generated', 'awaiting', 'processing', 'stalled', 'needs_attention', 'queued', 'delivered', 'bounced', 'deferred', 'dropped', 'complained', 'opened' );
		$ranges   = array( 'today', 'yesterday', '7', '30', '90' );
		$filters  = array(
			'outcome'    => in_array( sanitize_key( (string) ( $raw['outcome'] ?? '' ) ), $outcomes, true ) ? sanitize_key( (string) $raw['outcome'] ) : '',
			'source'     => substr( sanitize_key( (string) ( $raw['source'] ?? '' ) ), 0, 100 ),
			'provider'   => substr( sanitize_key( (string) ( $raw['provider'] ?? '' ) ), 0, 64 ),
			'form_id'    => substr( sanitize_text_field( (string) ( $raw['form_id'] ?? '' ) ), 0, 190 ),
			'email_type' => substr( sanitize_key( (string) ( $raw['email_type'] ?? '' ) ), 0, 50 ),
			'event_kind' => substr( sanitize_key( (string) ( $raw['event_kind'] ?? '' ) ), 0, 50 ),
			'rule_id'    => absint( $raw['rule_id'] ?? 0 ),
			'date_range' => in_array( (string) ( $raw['date_range'] ?? '' ), $ranges, true ) ? (string) $raw['date_range'] : '',
			'date_from'  => $this->sanitize_date( $raw['date_from'] ?? '' ),
			'date_to'    => $this->sanitize_date( $raw['date_to'] ?? '' ),
			'search'     => substr( sanitize_text_field( (string) ( $raw['search'] ?? '' ) ), 0, 200 ),
			'sort'       => 'oldest' === sanitize_key( (string) ( $raw['sort'] ?? '' ) ) ? 'oldest' : 'newest',
		);

		$origin = (string) ( $raw['origin'] ?? '' );
		if ( 0 === strpos( $origin, 'provider:' ) ) {
			$filters['provider'] = substr( sanitize_key( substr( $origin, 9 ) ), 0, 64 );
			$filters['source']   = '';
		} elseif ( 0 === strpos( $origin, 'source:' ) ) {
			$filters['source']   = substr( sanitize_key( substr( $origin, 7 ) ), 0, 100 );
			$filters['provider'] = '';
		}
		$form_context = (string) ( $raw['form_context'] ?? '' );
		if ( '' === $form_context && false !== strpos( (string) ( $raw['form_id'] ?? '' ), '|' ) ) {
			$form_context = (string) $raw['form_id'];
		}
		if ( false !== strpos( $form_context, '|' ) ) {
			list( $form_provider, $form_id ) = array_pad( explode( '|', $form_context, 2 ), 2, '' );
			$filters['form_id']  = substr( sanitize_text_field( $form_id ), 0, 190 );
			$filters['provider'] = substr( sanitize_key( $form_provider ), 0, 64 );
			$filters['source']   = '';
		}
		if ( '' !== $filters['date_from'] || '' !== $filters['date_to'] ) {
			$filters['date_range'] = '';
		}
		if ( '' !== $filters['date_from'] && '' !== $filters['date_to'] && $filters['date_from'] > $filters['date_to'] ) {
			$this->filter_error = __( 'The “From date” must be earlier than or equal to the “To date”.', 'submission-inbox' );
		}
		return $filters;
	}

	/** Render current action result. */
	private function render_request_notice() {
		$notice = SUBIN_Request::get_key( 'subin_notice' );
		$map    = array(
			'preferences_saved' => array( 'success', __( 'Email Log display preferences saved.', 'submission-inbox' ) ),
			'view_saved'        => array( 'success', __( 'Email Log view saved.', 'submission-inbox' ) ),
			'view_deleted'      => array( 'success', __( 'Saved view deleted.', 'submission-inbox' ) ),
			'retry_queued'      => array( 'success', __( 'Exact immutable retry queued. A new linked event will appear when it runs.', 'submission-inbox' ) ),
			'retry_unavailable' => array( 'warning', __( 'This event cannot be retried safely from its historical snapshot.', 'submission-inbox' ) ),
			'retry_denied'      => array( 'error', __( 'You do not have permission to retry notifications.', 'submission-inbox' ) ),
			'retry_failed'      => array( 'error', __( 'The retry could not be queued. Refresh the event and try again.', 'submission-inbox' ) ),
			'export_queued'     => array( 'success', __( 'Private CSV export queued. Refresh its progress in the Exports panel.', 'submission-inbox' ) ),
			'export_deleted'    => array( 'success', __( 'Temporary export deleted.', 'submission-inbox' ) ),
			'export_limit'      => array( 'warning', __( 'Remove an existing export or wait for it to finish before starting another.', 'submission-inbox' ) ),
			'export_not_ready'  => array( 'warning', __( 'The export is not ready yet. Check WP-Cron and refresh its progress.', 'submission-inbox' ) ),
			'export_storage_failed' => array( 'error', __( 'The protected temporary export file is unavailable on this server.', 'submission-inbox' ) ),
			'export_failed'     => array( 'error', __( 'The export action could not be completed safely. Refresh and try again.', 'submission-inbox' ) ),
			'action_failed'     => array( 'error', __( 'The Email Log action could not be completed.', 'submission-inbox' ) ),
			'subin_email_log_view_invalid' => array( 'warning', __( 'Enter a name for the saved view.', 'submission-inbox' ) ),
			'subin_email_log_view_limit'   => array( 'warning', __( 'Delete a saved view before adding another.', 'submission-inbox' ) ),
		);
		if ( ! isset( $map[ $notice ] ) ) {
			return;
		}
		printf( '<div class="notice notice-%1$s is-dismissible"><p>%2$s</p></div>', esc_attr( $map[ $notice ][0] ), esc_html( $map[ $notice ][1] ) );
	}

	/** Render hidden filter values for POST actions. */
	private function render_hidden_filters( array $filters ) {
		foreach ( array( 'outcome', 'source', 'provider', 'form_id', 'email_type', 'event_kind', 'rule_id', 'date_range', 'date_from', 'date_to', 'search', 'sort' ) as $key ) {
			$value = $filters[ $key ] ?? '';
			if ( '' === (string) $value || ( 'rule_id' === $key && absint( $value ) <= 0 ) ) {
				continue;
			}
			echo '<input type="hidden" name="filter_' . esc_attr( $key ) . '" value="' . esc_attr( (string) $value ) . '" />';
		}
	}

	/** Render a definition-list fact, omitting empty values. */
	private function render_fact( $label, $value, $wide = false ) {
		$value = is_scalar( $value ) ? trim( (string) $value ) : '';
		if ( '' === $value ) {
			return;
		}
		?><div class="<?php echo $wide ? 'subin-event-fact--wide' : ''; ?>"><dt><?php echo esc_html( $label ); ?></dt><dd><?php echo esc_html( $value ); ?></dd></div><?php
	}

	/** Build sanitized URL args from filters. */
	private function url_args_from_filters( array $filters ) {
		return array_filter(
			array(
				'page'               => 'subin-email-log',
				'subin_outcome'      => $filters['outcome'] ?? '',
				'subin_origin'       => $this->origin_filter_value( $filters ),
				'subin_form_id'      => ! empty( $filters['form_id'] ) && empty( $filters['provider'] ) ? (string) $filters['form_id'] : '',
				'subin_form_context' => ! empty( $filters['form_id'] ) && ! empty( $filters['provider'] ) ? ( (string) $filters['provider'] . '|' . (string) $filters['form_id'] ) : '',
				'subin_email_type'   => $filters['email_type'] ?? '',
				'subin_event_kind'   => $filters['event_kind'] ?? '',
				'subin_rule_id'      => absint( $filters['rule_id'] ?? 0 ),
				'subin_date_range'   => $filters['date_range'] ?? '',
				'subin_date_from'    => $filters['date_from'] ?? '',
				'subin_date_to'      => $filters['date_to'] ?? '',
				's'                   => $filters['search'] ?? '',
				'subin_sort'         => 'oldest' === ( $filters['sort'] ?? '' ) ? 'oldest' : '',
			)
		);
	}

	/** List URL. */
	private function list_url( array $filters, $paged = 1 ) {
		$args = $this->url_args_from_filters( $filters );
		if ( $paged > 1 ) {
			$args['paged'] = absint( $paged );
		}
		return add_query_arg( $args, admin_url( 'admin.php' ) );
	}

	/** Permanent event URL preserving the current view. */
	private function event_url( $event_id, array $filters ) {
		return add_query_arg( 'event_id', absint( $event_id ), $this->list_url( $filters, max( 1, SUBIN_Request::get_absint( 'paged', 1 ) ) ) );
	}

	/** Minimal stable permalink independent of the current list filters. */
	private function permanent_event_url( $event_id ) {
		return add_query_arg( array( 'page' => 'subin-email-log', 'event_id' => absint( $event_id ) ), admin_url( 'admin.php' ) );
	}

	/** Authenticated download URL for one private, completed export. */
	private function background_export_download_url( $job_id, $nonce, array $filters ) {
		return add_query_arg(
			array_merge(
				$this->url_args_from_filters( $filters ),
				array(
					'subin_export_action' => 'download',
					'export_id'           => sanitize_key( (string) $job_id ),
					'subin_export_nonce'  => sanitize_text_field( (string) $nonce ),
				)
			),
			admin_url( 'admin.php' )
		);
	}

	/** Format a Unix export timestamp in the site timezone. */
	private function format_export_timestamp( $timestamp ) {
		$timestamp = absint( $timestamp );
		return $timestamp > 0 ? wp_date( 'M j, H:i', $timestamp, wp_timezone() ) : '';
	}

	/** Email Health URL. */
	private function email_health_url( array $filters ) {
		$args = array( 'page' => 'subin-email-status' );
		if ( ! empty( $filters['form_id'] ) && ctype_digit( (string) $filters['form_id'] ) ) {
			$args['subin_form_id'] = absint( $filters['form_id'] );
		}
		return add_query_arg( $args, admin_url( 'admin.php' ) );
	}

	/** Context URLs. */
	private function lead_detail_url( $lead_id ) {
		return add_query_arg( array( 'page' => SUBIN_AdminMenu::MENU_SLUG, 'action' => 'view', 'lead_id' => absint( $lead_id ) ), admin_url( 'admin.php' ) );
	}

	private function form_url( $form_id ) {
		return add_query_arg( array( 'page' => 'submission-inbox-forms', 'view' => 'edit', 'form_id' => absint( $form_id ), 'tab' => 'settings' ), admin_url( 'admin.php' ) );
	}

	private function rule_url( $rule_id ) {
		return add_query_arg( array( 'page' => 'submission-inbox-auto-replies', 'subin_auto_replies_tab' => 'rules', 'rule_view' => 'edit', 'id' => absint( $rule_id ) ), admin_url( 'admin.php' ) );
	}

	private function template_url( $template_id ) {
		return add_query_arg( array( 'page' => 'submission-inbox-auto-replies', 'subin_auto_replies_tab' => 'templates', 'view' => 'edit', 'id' => absint( $template_id ) ), admin_url( 'admin.php' ) );
	}

	/** Pagination. */
	private function render_pagination( $total, $paged, $per_page, array $filters ) {
		$total_pages = max( 1, (int) ceil( $total / max( 1, $per_page ) ) );
		if ( $total_pages <= 1 ) {
			return;
		}
		echo '<nav class="subin-pagination" aria-label="' . esc_attr__( 'Email Log pagination', 'submission-inbox' ) . '">';
		/* translators: %1$d: current page number, %2$d: total number of pages. */
		echo '<span class="subin-pagination__info">' . esc_html( sprintf( __( 'Page %1$d of %2$d', 'submission-inbox' ), absint( $paged ), absint( $total_pages ) ) ) . '</span><div class="subin-pagination__links">';
		echo wp_kses_post( paginate_links( array( 'base' => add_query_arg( 'paged', '%#%', $this->list_url( $filters, 1 ) ), 'format' => '', 'current' => $paged, 'total' => $total_pages, 'prev_text' => __( 'Previous', 'submission-inbox' ), 'next_text' => __( 'Next', 'submission-inbox' ) ) ) );
		echo '</div></nav>';
	}

	/** Select helpers. */
	private function render_select_options( array $options, $current ) {
		foreach ( $options as $value => $label ) {
			printf( '<option value="%1$s"%2$s>%3$s</option>', esc_attr( (string) $value ), selected( (string) $current, (string) $value, false ), esc_html( (string) $label ) );
		}
	}

	private function render_email_type_options( array $types, $current ) {
		$options = array( '' => __( 'All email types', 'submission-inbox' ) );
		foreach ( array_unique( array_merge( array( 'admin_notification', 'visitor_auto_reply', 'auto_reply_test', 'health_verification', 'wordpress_email' ), $types ) ) as $type ) {
			$presented        = $this->presenter->email_type( (object) array( 'email_type' => $type ) );
			$options[ $type ] = $presented['label'];
		}
		$this->render_select_options( $options, $current );
	}

	private function render_form_options( array $contexts, array $filters ) {
		$current = ! empty( $filters['form_id'] ) ? ( (string) ( $filters['provider'] ?? '' ) . '|' . (string) $filters['form_id'] ) : '';
		echo '<option value="">' . esc_html__( 'All forms', 'submission-inbox' ) . '</option>';
		foreach ( $contexts as $context ) {
			$provider = sanitize_key( (string) ( $context->provider ?? '' ) );
			$form_id  = sanitize_text_field( (string) ( $context->form_id ?? '' ) );
			if ( '' === $form_id ) {
				continue;
			}
			$label = trim( (string) ( $context->form_name ?? '' ) );
			$label = '' !== $label ? $label : $form_id;
			printf( '<option value="%1$s"%2$s>%3$s</option>', esc_attr( $provider . '|' . $form_id ), selected( $current, $provider . '|' . $form_id, false ), esc_html( $label . ' · ' . $this->origin_label( $provider ) ) );
		}
	}

	private function origin_options( array $contexts ) {
		$providers = array( 'submission_inbox_form', 'contact_form_7', 'wpforms', 'fluent_forms', 'gravity_forms', 'forminator', 'ninja_forms' );
		foreach ( $contexts as $context ) {
			$providers[] = sanitize_key( (string) ( $context->provider ?? '' ) );
		}
		$options = array( '' => __( 'All origins', 'submission-inbox' ) );
		foreach ( array_unique( array_filter( $providers ) ) as $provider ) {
			$options[ 'provider:' . $provider ] = $this->origin_label( $provider );
		}
		$options['source:wp_mail'] = __( 'WordPress / other plugins', 'submission-inbox' );
		$options['source:plugin']  = __( 'Other WordPress plugins', 'submission-inbox' );
		$options['source:unknown'] = __( 'Unknown origin', 'submission-inbox' );
		return $options;
	}

	private function origin_label( $provider ) {
		$origin = $this->presenter->origin( (object) array( 'effective_provider' => $provider, 'source' => $provider ) );
		return $origin['label'];
	}

	private function outcome_options() {
		return array( '' => __( 'All outcomes', 'submission-inbox' ), 'accepted' => __( 'Accepted', 'submission-inbox' ), 'handled' => __( 'Handled before transport', 'submission-inbox' ), 'failed' => __( 'Failed', 'submission-inbox' ), 'not_generated' => __( 'Not generated', 'submission-inbox' ), 'awaiting' => __( 'Awaiting outcome', 'submission-inbox' ), 'processing' => __( 'Processing', 'submission-inbox' ), 'stalled' => __( 'Stalled', 'submission-inbox' ), 'needs_attention' => __( 'Needs attention', 'submission-inbox' ), 'queued' => __( 'Queued (provider verified)', 'submission-inbox' ), 'delivered' => __( 'Delivered (provider verified)', 'submission-inbox' ), 'bounced' => __( 'Bounced (provider verified)', 'submission-inbox' ), 'deferred' => __( 'Deferred (provider verified)', 'submission-inbox' ), 'dropped' => __( 'Dropped (provider verified)', 'submission-inbox' ), 'complained' => __( 'Spam complaint (provider verified)', 'submission-inbox' ), 'opened' => __( 'Opened (provider verified)', 'submission-inbox' ) );
	}

	private function period_options() {
		return array( '' => __( 'All time', 'submission-inbox' ), 'today' => __( 'Today', 'submission-inbox' ), 'yesterday' => __( 'Yesterday', 'submission-inbox' ), '7' => __( 'Last 7 days', 'submission-inbox' ), '30' => __( 'Last 30 days', 'submission-inbox' ), '90' => __( 'Last 90 days', 'submission-inbox' ) );
	}

	/** Filter label helpers. */
	private function active_filter_labels( array $filters ) {
		$labels = array();
		if ( ! empty( $filters['search'] ) ) {
			/* translators: %s: Email Log search query. */
			$labels['search'] = sprintf( __( 'Search: %s', 'submission-inbox' ), $filters['search'] );
		}
		if ( ! empty( $filters['outcome'] ) ) {
			$labels['outcome'] = $this->outcome_options()[ $filters['outcome'] ] ?? $filters['outcome'];
		}
		if ( ! empty( $filters['email_type'] ) ) {
			$type = $this->presenter->email_type( (object) array( 'email_type' => $filters['email_type'] ) );
			$labels['email_type'] = $type['label'];
		}
		if ( ! empty( $filters['event_kind'] ) ) {
			$kind = $this->presenter->event_kind( (object) array( 'effective_event_kind' => $filters['event_kind'] ) );
			$labels['event_kind'] = $kind['label'];
		}
		if ( empty( $filters['form_id'] ) && ( ! empty( $filters['provider'] ) || ! empty( $filters['source'] ) ) ) {
			$labels['origin'] = $this->origin_label( $filters['provider'] ?: $filters['source'] );
		}
		if ( ! empty( $filters['form_id'] ) ) {
			$form_origin       = ! empty( $filters['provider'] ) ? $this->origin_label( $filters['provider'] ) : $this->origin_label( 'submission_inbox_form' );
			/* translators: %1$s: form ID, %2$s: form provider label. */
			$labels['form_id'] = sprintf( __( 'Form: %1$s · %2$s', 'submission-inbox' ), $filters['form_id'], $form_origin );
		}
		if ( ! empty( $filters['rule_id'] ) ) {
			/* translators: %d: auto-reply rule ID. */
			$labels['rule_id'] = sprintf( __( 'Rule #%d', 'submission-inbox' ), absint( $filters['rule_id'] ) );
		}
		if ( ! empty( $filters['date_range'] ) ) {
			$labels['date_range'] = $this->period_options()[ $filters['date_range'] ] ?? $filters['date_range'];
		}
		if ( ! empty( $filters['date_from'] ) ) {
			/* translators: %s: start date for the Email Log filter. */
			$labels['date_from'] = sprintf( __( 'From %s', 'submission-inbox' ), $filters['date_from'] );
		}
		if ( ! empty( $filters['date_to'] ) ) {
			/* translators: %s: end date for the Email Log filter. */
			$labels['date_to'] = sprintf( __( 'To %s', 'submission-inbox' ), $filters['date_to'] );
		}
		if ( 'oldest' === ( $filters['sort'] ?? '' ) ) {
			$labels['sort'] = __( 'Oldest first', 'submission-inbox' );
		}
		return $labels;
	}

	private function without_filter( array $filters, $key ) {
		if ( 'origin' === $key ) {
			$filters['provider'] = '';
			$filters['source']   = '';
			$filters['form_id']  = '';
		} else {
			$filters[ $key ] = 'sort' === $key ? 'newest' : ( 'rule_id' === $key ? 0 : '' );
		}
		if ( 'form_id' === $key ) {
			$filters['form_id']  = '';
			$filters['provider'] = '';
		}
		return $filters;
	}

	private function is_quick_view_active( array $filters, array $view ) {
		$meaningful = $filters;
		unset( $meaningful['sort'] );
		$meaningful = array_filter( $meaningful, static function ( $value ) { return '' !== (string) $value && 0 !== $value; } );
		return $meaningful === $view;
	}

	private function has_advanced_filters( array $filters ) {
		return $this->advanced_filter_count( $filters ) > 0;
	}

	private function advanced_filter_count( array $filters ) {
		$count = 0;
		foreach ( array( 'email_type', 'event_kind', 'provider', 'source', 'form_id', 'rule_id', 'date_from', 'date_to' ) as $key ) {
			if ( ! empty( $filters[ $key ] ) ) {
				++$count;
			}
		}
		if ( 'oldest' === ( $filters['sort'] ?? '' ) ) {
			++$count;
		}
		return $count;
	}

	private function has_active_filters( array $filters ) {
		foreach ( $filters as $key => $value ) {
			if ( 'sort' === $key ) {
				continue;
			}
			if ( '' !== (string) $value && 0 !== $value ) {
				return true;
			}
		}
		return false;
	}

	private function origin_filter_value( array $filters ) {
		if ( ! empty( $filters['provider'] ) ) {
			return 'provider:' . sanitize_key( $filters['provider'] );
		}
		if ( ! empty( $filters['source'] ) ) {
			return 'source:' . sanitize_key( $filters['source'] );
		}
		return '';
	}

	/** Presentation helpers. */
	private function form_context_label( array $origin ) {
		if ( '' !== (string) ( $origin['form_name'] ?? '' ) ) {
			return (string) $origin['form_name'];
		}
		return '' !== (string) ( $origin['form_id'] ?? '' ) ? '#' . (string) $origin['form_id'] : '';
	}

	private function delivery_label( $event ) {
		$mode = sanitize_key( (string) ( $event->delivery_mode ?? '' ) );
		if ( 'delay' === $mode ) {
			return __( 'Delayed', 'submission-inbox' );
		}
		if ( 'immediate' === $mode ) {
			return __( 'Immediate', 'submission-inbox' );
		}
		return '';
	}

	private function mask_recipient( $recipients ) {
		$parts = preg_split( '/[,;\n]+/', (string) $recipients );
		$first = trim( is_array( $parts ) ? (string) ( $parts[0] ?? '' ) : '' );
		if ( preg_match( '/<([^>]+)>/', $first, $match ) ) {
			$first = $match[1];
		}
		$email = sanitize_email( $first );
		if ( '' === $email || false === strpos( $email, '@' ) ) {
			return __( 'address hidden', 'submission-inbox' );
		}
		list( $local, $domain ) = explode( '@', $email, 2 );
		$visible = function_exists( 'mb_substr' ) ? mb_substr( $local, 0, 1 ) : substr( $local, 0, 1 );
		return $visible . '•••@' . $domain;
	}

	private function sanitize_date( $date ) {
		$date = sanitize_text_field( (string) $date );
		if ( 1 !== preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date ) ) {
			return '';
		}
		$parts = array_map( 'absint', explode( '-', $date ) );
		return 3 === count( $parts ) && checkdate( $parts[1], $parts[2], $parts[0] ) ? $date : '';
	}

	private function format_date( $date ) {
		return empty( $date ) ? '' : mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $date );
	}

	private function format_compact_date( $date ) {
		return empty( $date ) ? '' : mysql2date( 'M j, H:i', $date );
	}

	private function datetime_attribute( $date ) {
		if ( empty( $date ) ) {
			return '';
		}
		$parsed = date_create_from_format( 'Y-m-d H:i:s', (string) $date, wp_timezone() );
		return $parsed ? $parsed->format( DATE_ATOM ) : '';
	}
}
