<?php
/**
 * Admin: Payout Batches List Table
 *
 * @package     AffiliateWP\Admin\Payouts
 * @copyright   Copyright (c) 2026, Sandhills Development, LLC
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       2.33.0
 */

use AffWP\Admin\List_Table;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

#[\AllowDynamicProperties]

/**
 * Renders the Batches tab on the Payouts page.
 *
 * Each row is a container grouping N per-affiliate payouts generated in one
 * run. Clicking a batch opens a detail view filtered to its child payouts.
 *
 * @since 2.33.0
 *
 * @see \AffWP\Admin\List_Table
 */
class AffWP_Payout_Batches_Table extends List_Table {

	public $per_page = 30;

	public $total_count;
	public $processing_count;
	public $partial_count;
	public $paid_count;
	public $failed_count;

	public function __construct( $args = [] ) {
		$args = wp_parse_args(
			$args,
			[
				'singular' => 'batch',
				'plural'   => 'batches',
			]
		);

		parent::__construct( $args );

		$this->get_batch_counts();
	}

	/**
	 * Status view tabs (All / Processing / Partial / Paid / Failed).
	 *
	 * Matches the semantics documented in the batch-container decision:
	 *   processing — at least one child payout still in-flight
	 *   partial    — some children succeeded, some failed
	 *   paid       — every child succeeded
	 *   failed     — every child failed (usually batch-level rejection)
	 *
	 * @since 2.33.0
	 */
	public function get_views() {

		$base    = affwp_admin_url( 'payouts', [ 'tab' => 'batches' ] );
		$current = isset( $_GET['status'] ) ? sanitize_key( $_GET['status'] ) : '';

		$total_count      = '&nbsp;<span class="count">(' . absint( $this->total_count ) . ')</span>';
		$processing_count = '&nbsp;<span class="count">(' . absint( $this->processing_count ) . ')</span>';
		$partial_count    = '&nbsp;<span class="count">(' . absint( $this->partial_count ) . ')</span>';
		$paid_count       = '&nbsp;<span class="count">(' . absint( $this->paid_count ) . ')</span>';
		$failed_count     = '&nbsp;<span class="count">(' . absint( $this->failed_count ) . ')</span>';

		return [
			'all'        => sprintf(
				'<a href="%s"%s>%s</a>',
				esc_url( remove_query_arg( 'status', $base ) ),
				'' === $current || 'all' === $current ? ' class="current"' : '',
				_x( 'All', 'batches', 'affiliate-wp' ) . $total_count
			),
			'processing' => sprintf(
				'<a href="%s"%s>%s</a>',
				esc_url( add_query_arg( 'status', 'processing', $base ) ),
				'processing' === $current ? ' class="current"' : '',
				__( 'Processing', 'affiliate-wp' ) . $processing_count
			),
			'partial'    => sprintf(
				'<a href="%s"%s>%s</a>',
				esc_url( add_query_arg( 'status', 'partial', $base ) ),
				'partial' === $current ? ' class="current"' : '',
				__( 'Partial', 'affiliate-wp' ) . $partial_count
			),
			'completed'  => sprintf(
				'<a href="%s"%s>%s</a>',
				esc_url( add_query_arg( 'status', 'completed', $base ) ),
				'completed' === $current ? ' class="current"' : '',
				__( 'Paid', 'affiliate-wp' ) . $paid_count
			),
			'failed'     => sprintf(
				'<a href="%s"%s>%s</a>',
				esc_url( add_query_arg( 'status', 'failed', $base ) ),
				'failed' === $current ? ' class="current"' : '',
				__( 'Failed', 'affiliate-wp' ) . $failed_count
			),
		];
	}

	/**
	 * Wraps only the <table> element in a horizontally-scrollable container
	 * so narrow viewports can pan instead of forcing column headers to wrap.
	 *
	 * The tablenavs (bulk actions, pagination, filters) deliberately sit
	 * OUTSIDE the scroll container so they stay anchored to the viewport
	 * when the user scrolls the table horizontally. Inlines the structure
	 * from \WP_List_Table::display() rather than wrapping parent::display()
	 * for that reason.
	 *
	 * @since 2.33.0
	 */
	public function display() {
		$singular = $this->_args['singular'];

		$this->display_tablenav( 'top' );

		$this->screen->render_screen_reader_content( 'heading_list' );

		/*
		 * Keep sortable headers on one line and right-align the Total header so
		 * it lines up with the right-aligned amounts below. See referrals
		 * display() for the WP header-stacking rationale.
		 */
		static $sortable_style_printed = false;
		if ( ! $sortable_style_printed ) {
			echo '<style>.affwp-batches-scroller th.sortable a,.affwp-batches-scroller th.sorted a{display:flex;align-items:center;gap:4px;}.affwp-batches-scroller th.column-total_amount a{justify-content:flex-end;}</style>';
			$sortable_style_printed = true;
		}

		?>
		<div x-data="affwpScrollShadow" class="relative clear-both">
			<div x-ref="scroller" class="affwp-batches-scroller <?php echo esc_attr( $this->get_scroller_classes( '', true ) ); ?>">
				<table class="wp-list-table <?php echo esc_attr( implode( ' ', $this->get_table_classes() ) ); ?>">
					<thead>
						<tr>
							<?php $this->print_column_headers(); ?>
						</tr>
					</thead>

					<tbody id="the-list"<?php echo $singular ? " data-wp-lists='list:" . esc_attr( $singular ) . "'" : ''; ?>>
						<?php $this->display_rows_or_placeholder(); ?>
					</tbody>

					<tfoot>
						<tr>
							<?php $this->print_column_headers( false ); ?>
						</tr>
					</tfoot>
				</table>
			</div>
			<?php $this->render_scroll_shadow_overlays(); ?>
		</div>
		<?php

		$this->display_tablenav( 'bottom' );
	}

	public function get_columns() {
		/*
		 * Columns follow the scan order shared with the Payouts table:
		 * identity, subject, money, state, method, detail, when. "Did this
		 * run succeed?" is the primary question on a batches screen, so
		 * Status sits up front with Progress as its granular breakdown,
		 * rather than stranded at the far right.
		 */
		$columns = [
			'cb'            => '<input type="checkbox" />',
			'batch_id'      => __( 'Batch', 'affiliate-wp' ),
			'total_count'   => __( 'Affiliates', 'affiliate-wp' ),
			'total_amount'  => _x( 'Total', 'batches', 'affiliate-wp' ),
			'status'        => _x( 'Status', 'batches', 'affiliate-wp' ),
			'progress'      => __( 'Progress', 'affiliate-wp' ),
			'payout_method' => __( 'Method', 'affiliate-wp' ),
			'date_created'  => _x( 'Date', 'batches', 'affiliate-wp' ),
			'actions'       => '<span class="screen-reader-text">' . esc_html__( 'Actions', 'affiliate-wp' ) . '</span>',
		];

		/**
		 * Filters the batches list table columns.
		 *
		 * @since 2.33.0
		 *
		 * @param array $columns List table columns.
		 */
		return apply_filters( 'affwp_payout_batches_table_columns', $this->prepare_columns( $columns ) );
	}

	public function get_sortable_columns() {
		return [
			'batch_id'     => [ 'batch_id', false ],
			'date_created' => [ 'date_created', false ],
			'total_amount' => [ 'total_amount', false ],
			'status'       => [ 'status', false ],
		];
	}

	public function column_cb( $batch ) {
		return '<input type="checkbox" name="batch_id[]" value="' . absint( $batch->batch_id ) . '" />';
	}

	/**
	 * Tags each row with `data-batch-id` so the global row-click handler in
	 * affwp-batch-drawer.js can open the drawer when the user clicks
	 * anywhere on the row outside the sticky Actions column or other
	 * interactive controls. The `group` class drives the hover reveal of
	 * the per-row Retry icon (column_actions).
	 *
	 * @since 2.33.0
	 *
	 * @param \AffWP\Affiliate\Payout\Batch $batch Batch object.
	 */
	protected function get_table_classes() {
		return $this->get_unstriped_table_classes();
	}

	public function single_row( $batch ) {
		$batch_id = 0;
		if ( is_object( $batch ) && isset( $batch->batch_id ) ) {
			$batch_id = (int) $batch->batch_id;
		} elseif ( is_array( $batch ) && isset( $batch['batch_id'] ) ) {
			$batch_id = (int) $batch['batch_id'];
		}

		printf( '<tr class="group" data-batch-id="%d">', $batch_id );
		$this->single_row_columns( $batch );
		echo '</tr>';
	}

	/**
	 * Batch ID column — links to the detail view.
	 */
	public function column_batch_id( $batch ) {
		$url = affwp_admin_url( 'payouts', [
			'tab'      => 'batches',
			'batch_id' => (int) $batch->batch_id,
		] );

		/*
		 * `retry_of_batch_id > 0` means this batch was created by retrying
		 * another batch's failed items. Earlier we rendered this as an
		 * inline parenthetical on the same line as the ID ("#56 (retry of
		 * #42)"), which crowded the column and made the ID harder to scan.
		 * Moved to its own line below the ID as a small muted caption with
		 * a downward-back-arrow glyph: the ID stays scannable, the lineage
		 * is still discoverable, and the click-through is unchanged.
		 */
		$suffix = '';
		if ( ! empty( $batch->retry_of_batch_id ) ) {
			$parent_id     = (int) $batch->retry_of_batch_id;
			$parent_exists = (bool) affwp_get_payout_batch( $parent_id );

			if ( $parent_exists ) {
				$parent_url = affwp_admin_url( 'payouts', [
					'tab'      => 'batches',
					'batch_id' => $parent_id,
				] );
				$suffix = '<div class="text-xs text-gray-500 mt-0.5">' . sprintf(
					/* translators: %s: parent batch link */
					esc_html__( '↳ retry of %s', 'affiliate-wp' ),
					'<a href="' . esc_url( $parent_url ) . '" class="text-gray-500 hover:text-affwp-link">#' . $parent_id . '</a>'
				) . '</div>';
			} else {
				/*
				 * Parent batch was deleted. Render as plain muted text rather
				 * than a broken link — clicking would land on "Batch not found."
				 */
				$suffix = '<div class="text-xs text-gray-400 mt-0.5">' . sprintf(
					/* translators: %d: deleted parent batch ID */
					esc_html__( '↳ retry of #%d (deleted)', 'affiliate-wp' ),
					$parent_id
				) . '</div>';
			}
		}

		return '<a href="' . esc_url( $url ) . '"><strong>#' . (int) $batch->batch_id . '</strong></a>' . $suffix;
	}

	public function column_date_created( $batch ) {
		if ( empty( $batch->date_created ) ) {
			return '—';
		}
		return esc_html( date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), strtotime( $batch->date_created ) ) );
	}

	public function column_payout_method( $batch ) {
		if ( 'mixed' === $batch->payout_method ) {
			return '<em>' . esc_html__( 'Mixed (per-affiliate routing)', 'affiliate-wp' ) . '</em>';
		}

		return esc_html( affwp_get_payout_method_display_label( $batch->payout_method ) );
	}

	public function column_total_count( $batch ) {
		return number_format_i18n( (int) $batch->total_count );
	}

	public function column_total_amount( $batch ) {
		return sprintf(
			'<span class="block! min-[783px]:text-right! tabular-nums!">%s</span>',
			affwp_currency_filter( affwp_format_amount( $batch->total_amount ) )
		);
	}

	/**
	 * Progress column — "processed / total" counters plus failure count.
	 *
	 * Gives admins an at-a-glance read on whether a batch is still in-flight
	 * without needing to drill into the detail view. The number formatting
	 * stays muted (description class) so the main list rhythm holds.
	 */
	public function column_progress( $batch ) {

		$total     = (int) $batch->total_count;
		$processed = (int) $batch->processed_count;
		$failed    = (int) $batch->failed_count;

		if ( 0 === $total ) {
			return '—';
		}

		$out = sprintf(
			'<span>%s / %s</span>',
			number_format_i18n( $processed ),
			number_format_i18n( $total )
		);

		if ( $failed > 0 ) {
			$out .= ' <span class="description">' . sprintf(
				/* translators: %d: number of failed payouts in the batch */
				esc_html( _n( '(%d failed)', '(%d failed)', $failed, 'affiliate-wp' ) ),
				$failed
			) . '</span>';
		}

		return $out;
	}

	public function column_status( $batch ) {
		return affwp_get_payout_status_badge( $batch->status );
	}

	public function column_actions( $batch ) {

		$batch_id = (int) $batch->batch_id;
		$menu_id  = 'affwp-batch-actions-' . $batch_id;

		$view_url = affwp_admin_url(
			'payouts',
			array(
				'tab'      => 'batches',
				'batch_id' => $batch_id,
			)
		);

		/*
		 * All actions live in the kebab dropdown. Whole-row click opens
		 * the drawer (see single_row + affwp-batch-drawer.js). Retry on
		 * failed batches also surfaces as a hover-only icon for one-click
		 * access during queue triage; the kebab keeps the menu entry too.
		 */
		$row_actions = [];

		$retry_url     = '';
		$retry_confirm = '';

		if ( (int) $batch->failed_count > 0 ) {
			$retry_url = wp_nonce_url(
				affwp_admin_url(
					'payouts',
					array(
						'affwp_action' => 'retry_batch_failed',
						'batch_id'     => $batch_id,
					)
				),
				'payout-nonce'
			);

			$retry_confirm = wp_json_encode(
				sprintf(
					/* translators: %d: count of failed payouts */
					_n(
						'Retry %d failed payout in a new batch?',
						'Retry %d failed payouts in a new batch?',
						(int) $batch->failed_count,
						'affiliate-wp'
					),
					(int) $batch->failed_count
				)
			);

			$row_actions['retry'] = sprintf(
				'<a href="%1$s" onclick="return confirm(%2$s);">%3$s</a>',
				esc_url( $retry_url ),
				esc_attr( $retry_confirm ),
				esc_html(
					sprintf(
						/* translators: %d: count of failed payouts */
						_n( 'Retry %d failed payout', 'Retry %d failed payouts', (int) $batch->failed_count, 'affiliate-wp' ),
						(int) $batch->failed_count
					)
				)
			);
		}

		$row_actions['view'] = sprintf(
			'<a href="%s">%s</a>',
			esc_url( $view_url ),
			esc_html__( 'View', 'affiliate-wp' )
		);

		/**
		 * Filters the row actions for a batch in the batches list table.
		 *
		 * All entries render inside the kebab dropdown. Entries must be
		 * `<a>` markup strings.
		 *
		 * @since 2.33.0
		 *
		 * @param array                         $row_actions Key => <a> markup.
		 * @param \AffWP\Affiliate\Payout\Batch $batch       Current batch object.
		 */
		$row_actions = apply_filters( 'affwp_batch_row_actions', $row_actions, $batch );

		/*
		 * Hover-only Retry icon for failed batches, via the shared rail-action
		 * helper so the tooltip matches the Payouts/Affiliates/Referrals tables
		 * (dark, arrow-less, non-animated). The inline confirm() mirrors the
		 * kebab entry so both call sites get the dialog.
		 */
		$inline_html = '';
		if ( $retry_url ) {
			$retry_label = sprintf(
				/* translators: %d: count of failed payouts */
				_n( 'Retry %d failed payout', 'Retry %d failed payouts', (int) $batch->failed_count, 'affiliate-wp' ),
				(int) $batch->failed_count
			);

			$inline_html = $this->render_rail_action(
				$retry_url,
				'arrow-rotate-cw',
				$retry_label,
				sprintf( ' onclick="return confirm(%s);"', esc_attr( $retry_confirm ) )
			);
		}

		// Build kebab items. Standard formatting matching the Payouts table.
		$items_html = '';
		foreach ( $row_actions as $action_key => $action_markup ) {
			$classes = 'block w-full text-left px-3 py-2 text-sm focus:outline-none focus:bg-gray-50';

			if ( preg_match( '/<a([^>]+)>(.+?)<\/a>/is', $action_markup, $m ) ) {
				$attrs = $m[1];
				$label = wp_kses( $m[2], [ 'strong' => [], 'em' => [] ] );
			} else {
				$items_html .= '<div class="' . esc_attr( $classes ) . ' text-gray-700">' . $action_markup . '</div>';
				continue;
			}

			$tone = 'text-gray-700 hover:text-gray-900 hover:bg-gray-50';

			$items_html .= sprintf(
				'<a%1$s role="menuitem" class="%2$s">%3$s</a>',
				$attrs,
				esc_attr( $classes . ' ' . $tone ),
				$label
			);
		}

		/*
		 * Kebab: native HTML Popover anchored via affwp-popover-anchor.js.
		 * Horizontal dots (meatball) reads as per-row contextual actions
		 * rather than a navigation menu, matching the Payouts table.
		 */
		$kebab_html = '';
		if ( ! empty( $items_html ) ) {
			$kebab_html = sprintf(
				'<button type="button" popovertarget="%1$s" aria-haspopup="menu" class="cursor-pointer inline-flex items-center justify-center size-6 rounded-full border-0 bg-transparent p-0 text-gray-400 hover:text-gray-700 hover:bg-gray-100 aria-expanded:bg-gray-100 aria-expanded:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-affwp-brand-500 transition-colors">
					<span class="sr-only">%2$s</span>
					' . \AffiliateWP\Utils\Icons::get( 'dots-horizontal', array( 'class' => 'w-4 h-4', 'aria-hidden' => 'true' ) ) . '
				</button>
				<div popover id="%1$s" role="menu" data-affwp-popover-anchor="%1$s" class="m-0 min-w-[160px] origin-top-right rounded-lg border-0! bg-white py-1 ring-1 ring-black/8 shadow-[0_12px_28px_-8px_rgba(0,0,0,0.18),0_4px_10px_-4px_rgba(0,0,0,0.06)]">%3$s</div>',
				esc_attr( $menu_id ),
				esc_html__( 'More actions', 'affiliate-wp' ),
				$items_html
			);
		}

		return sprintf(
			'<div class="affwp-ui absolute right-2.5 top-1/2 -translate-y-1/2 inline-flex items-center gap-1 rounded-full p-0.5 transition-[background-color,box-shadow] duration-150 group-hover:bg-white group-hover:shadow-[0_4px_10px_-2px_rgba(0,0,0,0.08),0_2px_4px_-2px_rgba(0,0,0,0.06)] group-hover:ring-1 group-hover:ring-black/8">%1$s%2$s</div>',
			$inline_html,
			$kebab_html
		);
	}

	public function column_default( $batch, $column_name ) {
		$value = isset( $batch->$column_name ) ? $batch->$column_name : '';

		/**
		 * Filters the default column value for the batches list table.
		 *
		 * @since 2.33.0
		 *
		 * @param mixed                        $value  Column value.
		 * @param \AffWP\Affiliate\Payout_Batch $batch  Current batch object.
		 */
		return apply_filters( 'affwp_payout_batches_table_' . $column_name, $value, $batch );
	}

	public function no_items() {
		esc_html_e( 'No batches found. Batches are created automatically when you generate payouts.', 'affiliate-wp' );
	}

	/**
	 * Bulk actions for the Payout Batches list table.
	 *
	 * v1 ships "Retry failed payouts" only — it reuses the single-batch retry
	 * code path (one new retry batch per selected source batch, preserving
	 * each source's audit lineage via `retry_of_batch_id`). Bulk cancel /
	 * delete / export remain deferred until their single-row semantics are
	 * defined.
	 *
	 * @since 2.33.0
	 *
	 * @return array Bulk action key => label map.
	 */
	public function get_bulk_actions() {
		/**
		 * Filters the bulk actions available on the Payout Batches list table.
		 *
		 * @since 2.33.0
		 *
		 * @param array $actions Bulk action key => label map.
		 */
		return apply_filters(
			'affwp_payout_batches_bulk_actions',
			[
				'retry_failed' => __( 'Retry failed payouts', 'affiliate-wp' ),
			]
		);
	}

	/**
	 * Dispatches the selected bulk action against checked batch IDs.
	 *
	 * Each selected source batch gets its own retry batch via
	 * `affwp_retry_batch_failed_payouts()`. Sources with zero failed children
	 * are skipped silently — covers the stale-page-load race where the
	 * checkbox was visible but the failed items were already retried
	 * elsewhere. On completion, redirect to the list with the existing
	 * `payout_retried_bulk` notice, whose renderer reads `retried` / `failed`
	 * query args and prints the aggregate totals.
	 *
	 * @since 2.33.0
	 */
	public function process_bulk_action() {

		if ( empty( $_REQUEST['_wpnonce'] ) ) {
			return;
		}

		if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'bulk-batches' ) ) {
			return;
		}

		if ( ! current_user_can( 'manage_payouts' ) ) {
			return;
		}

		if ( 'retry_failed' !== $this->current_action() ) {
			return;
		}

		$ids = isset( $_REQUEST['batch_id'] ) ? array_map( 'absint', (array) $_REQUEST['batch_id'] ) : [];
		$ids = array_filter( $ids );

		if ( empty( $ids ) ) {
			return;
		}

		$retried = 0;
		$failed  = 0;

		foreach ( $ids as $bid ) {
			$result = affwp_retry_batch_failed_payouts( (int) $bid );
			if ( 'ok' === $result['status'] ) {
				$retried += (int) $result['succeeded'];
				$failed  += (int) $result['failed'];
			}
		}

		wp_safe_redirect( affwp_admin_url( 'payouts', [
			'tab'          => 'batches',
			'affwp_notice' => 'payout_retried_bulk',
			'retried'      => $retried,
			'failed'       => $failed,
		] ) );
		exit;
	}

	/**
	 * Loads paginated batch data, populates $this->items for the parent class.
	 */
	public function batches_data() {

		$page    = isset( $_GET['paged'] ) ? absint( $_GET['paged'] ) : 1;
		$status  = isset( $_GET['status'] ) ? sanitize_key( $_GET['status'] ) : '';
		$order   = isset( $_GET['order'] ) ? sanitize_key( $_GET['order'] ) : 'DESC';
		$orderby = isset( $_GET['orderby'] ) ? sanitize_key( $_GET['orderby'] ) : 'batch_id';
		$search  = isset( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : '';

		$per_page = $this->get_items_per_page( 'affwp_edit_payout_batches_per_page', $this->per_page );

		$args = [
			'number'  => $per_page,
			'offset'  => $per_page * ( $page - 1 ),
			'status'  => $status,
			'search'  => $search,
			'order'   => $order,
			'orderby' => $orderby,
		];

		return affiliate_wp()->affiliates->payout_batches->get_batches( $args );
	}

	/**
	 * Status counters powering the view tabs.
	 */
	public function get_batch_counts() {
		$this->processing_count = affiliate_wp()->affiliates->payout_batches->count( [ 'status' => 'processing' ] );
		$this->partial_count    = affiliate_wp()->affiliates->payout_batches->count( [ 'status' => 'partial' ] );
		/*
		 * Batch terminal-success status is `completed`, not `paid`. The `paid_count`
		 * property name stays for backwards compatibility; only the queried status
		 * enum needs to match the data layer.
		 */
		$this->paid_count       = affiliate_wp()->affiliates->payout_batches->count( [ 'status' => 'completed' ] );
		$this->failed_count     = affiliate_wp()->affiliates->payout_batches->count( [ 'status' => 'failed' ] );

		$this->total_count =
			$this->processing_count
			+ $this->partial_count
			+ $this->paid_count
			+ $this->failed_count;
	}

	public function prepare_items() {

		$this->process_bulk_action();

		$per_page = $this->get_items_per_page( 'affwp_edit_payout_batches_per_page', $this->per_page );

		$this->_column_headers = [
			$this->get_columns(),
			[],
			$this->get_sortable_columns(),
		];

		$status = isset( $_GET['status'] ) ? sanitize_key( $_GET['status'] ) : '';
		$search = isset( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : '';

		$this->items = $this->batches_data();

		$status_map = [
			'processing' => $this->processing_count,
			'partial'    => $this->partial_count,
			'completed'  => $this->paid_count,
			'failed'     => $this->failed_count,
		];

		if ( '' !== $search ) {
			/*
			 * Search filters the list, so pagination needs the search-aware
			 * total. View tab counts (status_map) intentionally stay unfiltered:
			 * matching WP convention, search narrows the rows, not the tabs.
			 */
			$total_items = (int) affiliate_wp()->affiliates->payout_batches->count( [
				'search' => $search,
				'status' => $status,
			] );
		} else {
			$total_items = ( '' === $status || 'all' === $status )
				? (int) $this->total_count
				: (int) ( $status_map[ $status ] ?? 0 );
		}

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