<?php

namespace BSD_Split_Pay_Stripe_Connect_Woo\Inc\Admin;

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

if ( ! class_exists( 'BSD_SCSP_WP_List_Table' ) ) {
	include_once 'class-bsd-split-pay-stripe-connect-woo-wp-list-table.php';
}

if ( ! class_exists( 'BSD_SCSP_List_Table_Transfers' ) ) :

	class BSD_SCSP_List_Table_Transfers extends BSD_SCSP_WP_List_Table {

		public $cookie_name = 'transfer_log_per_page';

		public function __construct() {

			parent::__construct( array(
				'singular' => __( 'Transfer', 'bsd-split-pay-stripe-connect-woo' ),
				'plural'   => __( 'Transfers', 'bsd-split-pay-stripe-connect-woo' ),
				'ajax'     => false,
			) );

			add_action( 'admin_head', array( $this, 'admin_header' ) );
		}

		/**
		 *  Associative array of columns
		 *
		 * @version   1.0.0
		 */
		public function get_columns() {
			$columns = array(
				'wc_order_id'            => __( 'Order ID', 'bsd-split-pay-stripe-connect-woo' ),
				'charge_date'            => __( 'Date', 'bsd-split-pay-stripe-connect-woo' ),
				'integration_source'     => __( 'Source', 'bsd-split-pay-stripe-connect-woo' ),
				'transfer_type'          => __( 'Item', 'bsd-split-pay-stripe-connect-woo' ),
				'level'                  => __( 'Level', 'bsd-split-pay-stripe-connect-woo' ),
				'transfer_entered_value' => __( 'Value', 'bsd-split-pay-stripe-connect-woo' ),
				'entered_variable'       => __( 'Variable', 'bsd-split-pay-stripe-connect-woo' ),
				// 'charge_amount'          => __( 'Item Total', 'bsd-split-pay-stripe-connect-woo' ),
				'item_total'        => __( 'Item Total', 'bsd-split-pay-stripe-connect-woo' ),
				'item_transfer_amount'        => __( 'Item Transfer Amount', 'bsd-split-pay-stripe-connect-woo' ),
				'item_tax_total'        => __( 'Item Tax', 'bsd-split-pay-stripe-connect-woo' ),
				'tax_transfer_type'        => __( 'Tax Transfer Type', 'bsd-split-pay-stripe-connect-woo' ),
				'transfer_tax_value'        => __( 'Tax Transfer Amount', 'bsd-split-pay-stripe-connect-woo' ),
				'transfer_amount'        => __( 'Total Transfer Amount', 'bsd-split-pay-stripe-connect-woo' ),
				'transfer_destination'   => __( 'Connected Account', 'bsd-split-pay-stripe-connect-woo' ),
				'transfer_id'            => __( 'Stripe Transfer ID', 'bsd-split-pay-stripe-connect-woo' ),
				'charge_id'              => __( 'Stripe Charge ID', 'bsd-split-pay-stripe-connect-woo' ),
			);

			return $columns;
		}

		/**
		 * Style the columns
		 * TO DO: Not currently working
		 *
		 * @version   0.0.1
		 */
		function admin_header() {
			$page = ( isset( $_GET['page'] ) ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : false;
			// error_log("The page is {$page}.");
	if ( 'my_list_test' != $page ) {
				return;
	}

			echo '<style type="text/css">';
			echo '.wp-list-table .column-id';
			echo '.wp-list-table .column-wc_order_id';
			echo '.wp-list-table .column-charge_date';
			echo '.wp-list-table .column-charge_amount';
			echo '.wp-list-table .column-transfer_amount';
			echo '.wp-list-table .column-transfer_destination';
			echo '.wp-list-table .column-charge_id';
			echo '.wp-list-table .column-transfer_id';

			echo '</style>';
		}

		/**
		 * Render a column when no column specific method exists.
		 *
		 * 3.7.5 (M4a per stability plan, response to Michael McHugh's 2026-05-21
		 * report on shopkarynmillet.com order #10025): rows backfilled by
		 * migration-373/374/375 can have empty values in the rich audit columns
		 * (charge_date, transfer_entered_value, item_total, item_tax_total,
		 * tax_transfer_type, transfer_tax_value) because the original
		 * pre-3.7.3 insert path discarded the $event_info / $transfer_info
		 * payload before writing — so the historical data is genuinely gone
		 * and the migration can only stamp what it can derive (stripe_mode,
		 * date_created/date_modified). Render those empties as an em-dash
		 * instead of a blank cell so the table doesn't look broken, and
		 * surface a "recovered row" tooltip on the row's Date cell so the
		 * merchant understands WHY a column is empty.
		 *
		 * @version   1.0.0
		 */
		public function column_default( $item, $column_name ) {
			switch ( $column_name ) {
				case 'id':
				case 'wc_order_id':
				case 'charge_amount':
				case 'transfer_amount':
				case 'transfer_tax_value':
				case 'charge_id':
				case 'transfer_id':
				case 'transfer_destination':
				case 'transfer_type':
				case 'transfer_entered_value':
				case 'entered_variable':
				case 'level':
				case 'item_transfer_amount':
				case 'item_tax_total':
				case 'tax_transfer_type':
					return $this->render_value( $item[ $column_name ] ?? null );
				case 'charge_date':
					return $this->render_charge_date( $item );
				case 'item_total':
					if ( $this->is_value_empty( $item[ $column_name ] ?? null ) ) {
						return $this->render_value( null, $item );
					}
					return $item[ $column_name ];
				case 'integration_source':
					return $this->render_integration_source( $item[ $column_name ] ?? 'woocommerce' );
				default:
					return print_r( $item, true );
			}
		}

		/**
		 * True when a column value is "absent" for display purposes — null,
		 * empty string, '0' (numeric-string zero from un-stamped numeric cols),
		 * '0000-00-00 00:00:00' (un-stamped datetime), etc.
		 */
		private function is_value_empty( $v ): bool {
			if ( $v === null || $v === '' ) {
				return true;
			}
			if ( is_string( $v ) ) {
				$t = trim( $v );
				if ( $t === '' || $t === '0000-00-00 00:00:00' || $t === '0000-00-00' ) {
					return true;
				}
			}
			return false;
		}

		/**
		 * True when the row was backfilled by a migration (vs natively inserted
		 * by the normal save chain). Heuristic: a normally-inserted row has a
		 * non-empty charge_date AND a non-empty transfer_entered_value. A row
		 * with a charge_id (so it represents a real Stripe charge) but empty
		 * charge_date is a recovered/migration-backfilled row.
		 */
		private function is_recovered_row( array $item ): bool {
			return ! $this->is_value_empty( $item['charge_id'] ?? null )
				&& $this->is_value_empty( $item['charge_date'] ?? null );
		}

		/**
		 * Render a single value cell: the value itself if present, em-dash
		 * with a "recovered" tooltip otherwise. When the optional $item arg
		 * is supplied, the tooltip explicitly references the recovered-row
		 * status so the merchant knows where the gap came from.
		 *
		 * @param mixed      $value Column value.
		 * @param array|null $item  Optional full row, used to decide whether
		 *                          to show the "recovered" tooltip text.
		 */
		private function render_value( $value, ?array $item = null ): string {
			if ( ! $this->is_value_empty( $value ) ) {
				return (string) $value;
			}
			$tooltip = ( $item !== null && $this->is_recovered_row( $item ) )
				? esc_attr__( 'Recovered from an earlier plugin version — historical details for this column were not stored on the original transfer.', 'bsd-split-pay-stripe-connect-woo' )
				: esc_attr__( 'No value recorded for this column.', 'bsd-split-pay-stripe-connect-woo' );
			return '<span class="spp-empty-cell" title="' . $tooltip . '" style="color:#a7aaad;">&mdash;</span>';
		}

		/**
		 * Render the "Date" column. When the row was natively recorded, the
		 * cell shows charge_date. When it was migration-backfilled and
		 * charge_date is empty, fall back to date_created (which migration-373
		 * fills with the migration run time) and add a small "recovered" pill
		 * so the merchant can tell at a glance.
		 */
		private function render_charge_date( array $item ): string {
			if ( ! $this->is_value_empty( $item['charge_date'] ?? null ) ) {
				return (string) $item['charge_date'];
			}
			// Recovered row — show date_created (the backfill timestamp) and a pill.
			$fallback = $item['date_created'] ?? '';
			if ( $this->is_value_empty( $fallback ) ) {
				return $this->render_value( null, $item );
			}
			$tooltip = esc_attr__( 'Recovered from an earlier plugin version. The Stripe charge timestamp was not stored on the original transfer; this is the date the row was reconstructed by the recovery migration.', 'bsd-split-pay-stripe-connect-woo' );
			return '<span style="color:#646970;">' . esc_html( (string) $fallback ) . '</span>'
				. '<br><span class="spp-recovered-pill" title="' . $tooltip . '" style="display:inline-block;font-size:10px;line-height:1.2;padding:1px 6px;border-radius:9px;background:#f0f0f1;color:#646970;margin-top:2px;cursor:help;">'
				. esc_html__( 'recovered', 'bsd-split-pay-stripe-connect-woo' )
				. '</span>';
		}

		/**
		 * Columns to make sortable.
		 *
		 * @version   1.0.0
		 */
		public function get_sortable_columns() {
			$sortable_columns = array(
				'id'                   => array( 'id', true, '', '', 'desc' ),
				'wc_order_id'          => array( 'wc_order_id', true ),
				'charge_date'          => array( 'charge_date', true ),
				'charge_amount'        => array( 'charge_amount', true ),
				'transfer_amount'      => array( 'transfer_amount', true ),
				'transfer_destination' => array( 'transfer_destination', true ),
				'charge_id'            => array( 'charge_id', true ),
				'transfer_id'          => array( 'transfer_id', true ),
			);

			return $sortable_columns;
		}

		/**
		 * Check if Stripe is in test mode or live mode
		 *
		 * @version   1.0.0
		 */
		public function get_stripe_mode() {
			if ( class_exists( '\SplitPay\Core\TransferLogger' ) ) {
				return \SplitPay\Core\TransferLogger::resolveStripeMode();
			}

			$stripe_mode = 'live';

			$woo_stripe_settings = get_option( 'woocommerce_stripe_settings' );

			$stripe_mode = isset( $woo_stripe_settings['testmode'] ) && $woo_stripe_settings['testmode'] === 'yes' ? 'test' : 'live';

			return $stripe_mode;
		}

		/**
		 * Get count of items
		 *
		 * @version   1.0.0
		 */
		private function get_total_items( $stripe_mode ) {
			global $wpdb;

			$table_name = $wpdb->prefix . 'bsd_scsp_transfer_log';
			// PT-fix-3.7.5 (security #3A): `$_REQUEST['s']` was concatenated
			// raw into $sql before prepare(). prepare() only handles its own
			// placeholders — it does NOT escape % or single-quotes already
			// embedded in the query string. Admin POSTing ?s=`'%20OR%201=1--`
			// injected directly. Gate is manage_woocommerce but admin SQLi
			// is still a privilege-escalation vector. Fix: build the LIKE
			// clause via prepare() so `$search` becomes a bound parameter
			// with $wpdb->esc_like() applied to its content.
			$search       = isset( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : '';
			$args         = array( $stripe_mode );
			$where_search = '';
			if ( $search !== '' ) {
				$where_search = ' AND transfer_destination LIKE %s';
				$args[]       = '%' . $wpdb->esc_like( $search ) . '%';
			}

			$source_filter = isset( $_REQUEST['integration_source'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['integration_source'] ) ) : '';
			$where_source  = '';
			if ( $source_filter !== '' ) {
				$where_source = ' AND integration_source = %s';
				$args[]       = $source_filter;
			}

			$sql    = "SELECT COUNT(id) FROM `$table_name` WHERE stripe_mode = %s" . $where_search . $where_source;
			// $table_name is the constant log table ($wpdb->prefix . SPP_TRANSFER_LOG_TABLE); every user value is a %s/%d placeholder bound through prepare() via $args.
			$result = $wpdb->get_var( $wpdb->prepare( $sql, $args ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- constant table identifier; all user values bound via prepare() placeholders.

			return $result;
		}

		/**
		 * Retrieve transfer data from the database
		 *
		 * @version   1.0.0
		 */
		public function get_transfers( $stripe_mode, $per_page, $paged ) {

			global $wpdb;

	if ( ! empty( $this->current_action() ) ) {
				setcookie( $this->cookie_name, $this->current_action(), time() + 86400 );
				$per_page = $this->current_action();
	} elseif ( isset( $_COOKIE[ $this->cookie_name ] ) && ! empty( $this->cookie_name ) ) {
					$per_page = wp_unslash( $_COOKIE[ $this->cookie_name ] );
}

			$table_name = $wpdb->prefix . SPP_TRANSFER_LOG_TABLE;

			$offset = $paged * $per_page;

			$orderby = ( isset( $_REQUEST['orderby'] ) && in_array( $_REQUEST['orderby'], array_keys( $this->get_sortable_columns() ) ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['orderby'] ) ) : 'id';
			$order   = ( isset( $_REQUEST['order'] ) && in_array( $_REQUEST['order'], array(
				'asc',
				'desc',
			) ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ) : 'desc';

			// PT-fix-3.7.5 (security #3A): same SQLi fix as get_total_items().
			// Plus: ORDER BY clause is built from sanitize_sql_orderby which
			// only validates against a column whitelist if the input passes
			// through get_sortable_columns() — the $orderby above already
			// filters via in_array, but the keys come from method return so
			// safe by construction.
			$search       = isset( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : '';
			$args         = array( $stripe_mode );
			$where_search = '';
			if ( $search !== '' ) {
				$where_search = ' AND transfer_destination LIKE %s';
				$args[]       = '%' . $wpdb->esc_like( $search ) . '%';
			}

			$source_filter = isset( $_REQUEST['integration_source'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['integration_source'] ) ) : '';
			$where_source  = '';
			if ( $source_filter !== '' ) {
				$where_source = ' AND integration_source = %s';
				$args[]       = $source_filter;
			}

			$orderby_clause = sanitize_sql_orderby( $orderby . ' ' . $order );
			$sql            = "SELECT
                  id
                  , wc_order_id
                  , charge_amount
                  , item_total
                  , transfer_amount
                  , transfer_tax_value
                  , item_tax_total
                  , tax_transfer_type
                  , charge_id
                  , charge_date
                  , date_created
                  , transfer_id
                  , transfer_destination
                  , transfer_type
                  , transfer_entered_value
                  , transfer_type as entered_variable
                  , transfer_type as level
                  , (transfer_amount - transfer_tax_value) as item_transfer_amount
                  , integration_source
              FROM `$table_name`
              WHERE stripe_mode = %s" . $where_search . $where_source . "
              ORDER BY {$orderby_clause}
              LIMIT %d OFFSET %d";
			$args[] = $per_page;
			$args[] = $offset;

			// Query output_type will be an associative array with ARRAY_A.
			// $table_name is the constant log table; ORDER BY runs through sanitize_sql_orderby(); every user value is a %s/%d placeholder bound through prepare() via $args.
			$query_results = $wpdb->get_results( $wpdb->prepare( $sql, $args ), ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- constant table identifier + sanitize_sql_orderby(); user values bound via prepare() placeholders.

			return $query_results;
		}

		public function get_bulk_actions() {

			return array(
				'10'  => 10,
				'25'  => 25,
				'50'  => 50,
				'100' => 100,
				'250' => 250,
			);
		}

		protected function bulk_actions( $which = '' ) {

			if ( $which == 'top' ) {
				return;
			}
			$current_action = '';
			if ( isset( $_COOKIE[ $this->cookie_name ] ) && ! empty( $this->cookie_name ) ) {
				$current_action = wp_unslash( $_COOKIE[ $this->cookie_name ] );
			}
			if ( ! empty( $this->current_action() ) ) {
				$current_action = $this->current_action();
			}
			if ( is_null( $this->_actions ) ) {
				$this->_actions = $this->get_bulk_actions();

				/**
				 * Filters the items in the bulk actions menu of the list table.
				 *
				 * The dynamic portion of the hook name, `$this->screen->id`, refers
				 * to the ID of the current screen.
				 *
				 * @since 3.1.0
				 * @since 5.6.0 A bulk action can now contain an array of options in order to create an optgroup.
				 *
				 * @param array $actions An array of the available bulk actions.
				 */
				$this->_actions = apply_filters( "bulk_actions-{$this->screen->id}", $this->_actions ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores

				$two = '';
			} else {
				$two = '2';
			}

			if ( empty( $this->_actions ) ) {
				return;
			}

			echo '<label for="bulk-action-selector-' . esc_attr( $which ) . '" class="screen-reader-text">' .
				/* translators: Hidden accessibility text. */
				esc_html__( 'Select bulk action', 'bsd-split-pay-stripe-connect-woo' ) .
				'</label>';
			echo '<select name="action' . esc_attr( $two ) . '" id="bulk-action-selector-' . esc_attr( $which ) . "\">\n";

			foreach ( $this->_actions as $key => $value ) {
				if ( is_array( $value ) ) {
					echo "\t" . '<optgroup label="' . esc_attr( $key ) . '">' . "\n";

					foreach ( $value as $name => $title ) {
						$class = ( 'edit' === $name ) ? ' class="hide-if-no-js"' : '';
						echo "\t\t" . '<option value="' . esc_attr( $name ) . '"' . wp_kses_post( $class ) . ' ' . selected( $name, $current_action, false ) . '>' . esc_html( $title ) . "</option>\n";
					}
					echo "\t" . "</optgroup>\n";
				} else {
					$class = ( 'edit' === $key ) ? ' class="hide-if-no-js"' : '';
					echo "\t" . '<option value="' . esc_attr( $key ) . '"' . wp_kses_post( $class ) . ' ' . selected( $key, $current_action, false ) . '>' . esc_html( $value ) . "</option>\n";
				}
			}

			echo "</select>\n";

			submit_button( __( 'Apply', 'bsd-split-pay-stripe-connect-woo' ), 'action', '', false, array( 'id' => "doaction$two" ) );
			echo "\n";
		}

		/**
		 * Text displayed when no customer data is available
		 *
		 * @version   1.0.0
		 */
		private function render_integration_source( $value ) {
			$labels = array(
				'woocommerce' => 'WooCommerce',
				'fluentcart'  => 'FluentCart',
			);
			return isset( $labels[ $value ] ) ? $labels[ $value ] : esc_html( $value );
		}

		protected function extra_tablenav( $which ) {
			if ( 'top' !== $which ) {
				return;
			}
			$current = isset( $_REQUEST['integration_source'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['integration_source'] ) ) : '';
			?>
			<div class="alignleft actions">
				<select name="integration_source" aria-label="<?php esc_attr_e( 'Filter by integration source', 'bsd-split-pay-stripe-connect-woo' ); ?>">
					<option value=""><?php esc_html_e( 'All Sources', 'bsd-split-pay-stripe-connect-woo' ); ?></option>
					<option value="woocommerce" <?php selected( $current, 'woocommerce' ); ?>>WooCommerce</option>
					<option value="fluentcart" <?php selected( $current, 'fluentcart' ); ?>>FluentCart</option>
				</select>
				<?php submit_button( __( 'Filter', 'bsd-split-pay-stripe-connect-woo' ), '', 'filter_action', false ); ?>
			</div>
			<?php
		}

		public function no_items() {
			esc_html_e( 'No transfer records available.', 'bsd-split-pay-stripe-connect-woo' );
		}

		/**
		 * Prints column headers, accounting for hidden and sortable columns.
		 */
		public function print_column_headers( $with_id = true ) {
			[ $columns, $hidden, $sortable, $primary ] = $this->get_column_info();

			$bsd_host    = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '';
			$bsd_req_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
			$current_url = set_url_scheme( 'http://' . $bsd_host . $bsd_req_uri );
			$current_url = remove_query_arg( 'paged', $current_url );

			// When users click on a column header to sort by other columns.
			if ( isset( $_GET['orderby'] ) ) {
				$current_orderby = wp_unslash( $_GET['orderby'] );
				// In the initial view there's no orderby parameter.
			} else {
				$current_orderby = '';
			}

			// Not in the initial view and descending order.
			if ( isset( $_GET['order'] ) && 'desc' === $_GET['order'] ) {
				$current_order = 'desc';
			} else {
				// The initial view is not always 'asc', we'll take care of this below.
				$current_order = 'asc';
			}

			if ( ! empty( $columns['cb'] ) ) {
				static $cb_counter = 1;
				$columns['cb']     = '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />
			<label for="cb-select-all-' . $cb_counter . '">' .
								'<span class="screen-reader-text">' .
								/* translators: Hidden accessibility text. */
								__( 'Select All', 'bsd-split-pay-stripe-connect-woo' ) .
								'</span>' .
								'</label>';
				++$cb_counter;
			}

			foreach ( $columns as $column_key => $column_display_name ) {
				$class          = array( 'manage-column', "column-$column_key" );
				$aria_sort_attr = '';
				$abbr_attr      = '';
				$order_text     = '';

				if ( in_array( $column_key, $hidden, true ) ) {
					$class[] = 'hidden';
				}

				if ( 'cb' === $column_key ) {
					$class[] = 'check-column';
				} elseif ( in_array( $column_key, array( 'posts', 'comments', 'links' ), true ) ) {
					$class[] = 'num';
				}

				if ( $column_key === $primary ) {
					$class[] = 'column-primary';
				}
				$class[] = 'center-column';
				if ( isset( $sortable[ $column_key ] ) ) {
					$orderby       = isset( $sortable[ $column_key ][0] ) ? $sortable[ $column_key ][0] : '';
					$desc_first    = isset( $sortable[ $column_key ][1] ) ? $sortable[ $column_key ][1] : false;
					$abbr          = isset( $sortable[ $column_key ][2] ) ? $sortable[ $column_key ][2] : '';
					$orderby_text  = isset( $sortable[ $column_key ][3] ) ? $sortable[ $column_key ][3] : '';
					$initial_order = isset( $sortable[ $column_key ][4] ) ? $sortable[ $column_key ][4] : '';

					/*
					* We're in the initial view and there's no $_GET['orderby'] then check if the
					* initial sorting information is set in the sortable columns and use that.
					*/
					if ( '' === $current_orderby && $initial_order ) {
						// Use the initially sorted column $orderby as current orderby.
						$current_orderby = $orderby;
						// Use the initially sorted column asc/desc order as initial order.
						$current_order = $initial_order;
					}

					/*
					* True in the initial view when an initial orderby is set via get_sortable_columns()
					* and true in the sorted views when the actual $_GET['orderby'] is equal to $orderby.
					*/
					if ( $current_orderby === $orderby ) {
						// The sorted column. The `aria-sort` attribute must be set only on the sorted column.
						if ( 'asc' === $current_order ) {
							$order          = 'desc';
							$aria_sort_attr = ' aria-sort="ascending"';
						} else {
							$order          = 'asc';
							$aria_sort_attr = ' aria-sort="descending"';
						}

						$class[] = 'sorted';
						$class[] = $current_order;
					} else {
						// The other sortable columns.
						$order = strtolower( $desc_first );

						if ( ! in_array( $order, array( 'desc', 'asc' ), true ) ) {
							$order = $desc_first ? 'desc' : 'asc';
						}

						$class[] = 'sortable';
						$class[] = 'desc' === $order ? 'asc' : 'desc';

						/* translators: Hidden accessibility text. */
						$asc_text = __( 'Sort ascending.', 'bsd-split-pay-stripe-connect-woo' );
						/* translators: Hidden accessibility text. */
						$desc_text  = __( 'Sort descending.', 'bsd-split-pay-stripe-connect-woo' );
						$order_text = 'asc' === $order ? $asc_text : $desc_text;
					}

					if ( '' !== $order_text ) {
						$order_text = ' <span class="screen-reader-text">' . $order_text . '</span>';
					}

					// Print an 'abbr' attribute if a value is provided via get_sortable_columns().
					$abbr_attr = $abbr ? ' abbr="' . esc_attr( $abbr ) . '"' : '';

					$column_display_name = sprintf(
						'<a href="%1$s">' .
						'<span>%2$s</span>' .
						'<span class="sorting-indicators">' .
						'<span class="sorting-indicator asc" aria-hidden="true"></span>' .
						'<span class="sorting-indicator desc" aria-hidden="true"></span>' .
						'</span>' .
						'%3$s' .
						'</a>',
						esc_url( add_query_arg( compact( 'orderby', 'order' ), $current_url ) ),
						$column_display_name,
						$order_text
					);
				}

				$tag   = ( 'cb' === $column_key ) ? 'td' : 'th';
				$scope = ( 'th' === $tag ) ? 'scope="col"' : '';
				$id    = $with_id ? "id='$column_key'" : '';

				// 3.7.5 static-analysis: $class is initialised with two literal
				// entries at line ~489 ('manage-column', "column-$column_key"),
				// so the empty() check is always false.
				$class = "class='" . implode( ' ', $class ) . "'";

				echo wp_kses_post( "<$tag $scope $id $class $aria_sort_attr $abbr_attr>$column_display_name</$tag>" );
			}
		}


		/**
		 * Handles data query and filter, sorting, and pagination.
		 *
		 * @version   1.0.0
		 */
		public function prepare_items() {
			$stripe_mode = $this->get_stripe_mode();

			$per_page = 10;
			if ( isset( $_COOKIE[ $this->cookie_name ] ) && ! empty( $this->cookie_name ) ) {
				$per_page = wp_unslash( $_COOKIE[ $this->cookie_name ] );
			}
			if ( ! empty( $this->current_action() ) ) {
				$per_page = $this->current_action();
			}
			$columns  = $this->get_columns();
			$hidden   = array();
			$sortable = $this->get_sortable_columns();

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

			$total_items = $this->get_total_items( $stripe_mode );

			$paged = isset( $_REQUEST['paged'] ) ? max( 0, intval( $_REQUEST['paged'] ) - 1 ) : 0;

			$this->items = $this->get_transfers( $stripe_mode, $per_page, $paged );

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

		protected function pagination( $which ) {
			/*
			if ($which == "top") {
				return;
			} */
			if ( empty( $this->_pagination_args ) ) {
				return;
			}

			$total_items     = $this->_pagination_args['total_items'];
			$total_pages     = $this->_pagination_args['total_pages'];
			$infinite_scroll = false;
			if ( isset( $this->_pagination_args['infinite_scroll'] ) ) {
				$infinite_scroll = $this->_pagination_args['infinite_scroll'];
			}

			if ( 'top' === $which && $total_pages > 1 ) {
				$this->screen->render_screen_reader_content( 'heading_pagination' );
			}

			$output = '<span class="displaying-num">' . sprintf(
				/* translators: %s: Number of items. */
				_n( '%s item', '%s items', $total_items, 'bsd-split-pay-stripe-connect-woo' ),
				number_format_i18n( $total_items )
			) . '</span>';

			$current              = $this->get_pagenum();
			$removable_query_args = wp_removable_query_args();

			$bsd_host    = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '';
			$bsd_req_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
			$current_url = set_url_scheme( 'http://' . $bsd_host . $bsd_req_uri );

			$current_url = remove_query_arg( $removable_query_args, $current_url );

			$page_links = array();

			$total_pages_before = '<span class="paging-input">';
			$total_pages_after  = '</span></span>';

			$disable_first = false;
			$disable_last  = false;
			$disable_prev  = false;
			$disable_next  = false;

			if ( 1 == $current ) {
				$disable_first = true;
				$disable_prev  = true;
			}
			if ( $total_pages == $current ) {
				$disable_last = true;
				$disable_next = true;
			}

			if ( $disable_first ) {
				$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&laquo;</span>';
			} else {
				$page_links[] = sprintf(
					"<a class='first-page button' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
					esc_url( remove_query_arg( 'paged', $current_url ) ),
					__( 'First page', 'bsd-split-pay-stripe-connect-woo' ),
					'&laquo;'
				);
			}

			if ( $disable_prev ) {
				$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&lsaquo;</span>';
			} else {
				$page_links[] = sprintf(
					"<a class='prev-page button' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
					esc_url( add_query_arg( 'paged', max( 1, $current - 1 ), $current_url ) ),
					__( 'Previous page', 'bsd-split-pay-stripe-connect-woo' ),
					'&lsaquo;'
				);
			}

			if ( 'bottom' === $which ) {
				$html_current_page  = $current;
				$total_pages_before = '<span class="screen-reader-text">' . __( 'Current Page', 'bsd-split-pay-stripe-connect-woo' ) . '</span><span id="table-paging" class="paging-input"><span class="tablenav-paging-text">';
			} else {
				$html_current_page = sprintf(
					"%s<input class='current-page' id='current-page-selector' type='text' name='paged' value='%s' size='%d' aria-describedby='table-paging' /><span class='tablenav-paging-text'>",
					'<label for="current-page-selector" class="screen-reader-text">' . __( 'Current Page', 'bsd-split-pay-stripe-connect-woo' ) . '</label>',
					$current,
					strlen( $total_pages )
				);
			}
			$html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
			$page_links[]     = $total_pages_before . sprintf(
				/* translators: 1: Current page, 2: Total pages. */
				_x( '%1$s of %2$s', 'paging', 'bsd-split-pay-stripe-connect-woo' ),
				$html_current_page,
				$html_total_pages
			) . $total_pages_after;

			if ( $disable_next ) {
				$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&rsaquo;</span>';
			} else {
				$page_links[] = sprintf(
					"<a class='next-page button' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
					esc_url( add_query_arg( 'paged', min( $total_pages, $current + 1 ), $current_url ) ),
					__( 'Next page', 'bsd-split-pay-stripe-connect-woo' ),
					'&rsaquo;'
				);
			}

			if ( $disable_last ) {
				$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&raquo;</span>';
			} else {
				$page_links[] = sprintf(
					"<a class='last-page button' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
					esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
					__( 'Last page', 'bsd-split-pay-stripe-connect-woo' ),
					'&raquo;'
				);
			}

			$pagination_links_class = 'pagination-links';
			if ( ! empty( $infinite_scroll ) ) {
				$pagination_links_class .= ' hide-if-js';
			}
			$output .= "\n<span class='$pagination_links_class'>" . implode( "\n", $page_links ) . '</span>';

			if ( $total_pages ) {
				$page_class = $total_pages < 2 ? ' one-page' : '';
			} else {
				$page_class = ' no-pages';
			}
			$this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>";

			echo wp_kses_post( $this->_pagination );
		}

		public function single_row_columns( $item ) {
			[ $columns, $hidden ] = $this->get_column_info();

			$integration_source = $item['integration_source'] ?? 'woocommerce';
			$order              = null;
			$order_edit_url     = '';
			$charge_url         = '';
			$stripe_mode        = $this->get_stripe_mode();
			$account_url        = 'https://dashboard.stripe.com/' . esc_html( $stripe_mode ) . '/connect/accounts/' . esc_attr( $item['transfer_destination'] );
			$transfer_url       = 'https://dashboard.stripe.com/' . esc_html( $stripe_mode ) . '/connect/transfers/' . esc_attr( $item['transfer_id'] );

			if ( $integration_source === 'fluentcart' ) {
				$order_edit_url = admin_url( 'admin.php?page=fluent-cart#/orders/' . intval( $item['wc_order_id'] ) . '/view' );
				if ( ! empty( $item['charge_id'] ) ) {
					$charge_url = 'https://dashboard.stripe.com/' . esc_html( $stripe_mode ) . '/payments/' . esc_attr( $item['charge_id'] );
				}
			} else {
				if ( function_exists( 'wc_get_order' ) ) {
					$order = wc_get_order( $item['wc_order_id'] );
				}
				$payment_gateways = array();
				// @phpstan-ignore-next-line — defensive: WC()->payment_gateways() may be null on partial WC bootstrap.
				if ( function_exists( 'WC' ) && WC()->payment_gateways() ) {
					$payment_gateways = WC()->payment_gateways->payment_gateways();
				}
				if ( $order && is_a( $order, 'WC_Order' ) ) {
					$order_edit_url = $order->get_edit_order_url();
					$payment_method = $order->get_payment_method();
					/*
					 * 3.7.4 (Vanessa Provs / sc-7212): harden against fatal
					 * "Call to a member function get_transaction_url() on null"
					 * reported when the Transfers tab renders rows whose
					 * payment method is registered but the gateway entry in
					 * WC()->payment_gateways->payment_gateways() is not a
					 * usable WC_Payment_Gateway instance (third-party gateways
					 * that publish via the filter but lazy-init the object,
					 * or rows whose stored payment_method no longer maps to
					 * an active gateway after a plugin deactivation).
					 *
					 * The pre-fix isset() check passes for any present array
					 * key — including bool/null values that some gateway
					 * filter pipelines emit — and the subsequent method call
					 * blew up the entire Transfers tab. Now we also require
					 * an object that exposes get_transaction_url() and
					 * silently fall back to the Stripe Dashboard transfer URL
					 * if the gateway cannot produce a charge URL.
					 */
					if ( ! empty( $payment_method ) && isset( $payment_gateways[ $payment_method ] )
						&& is_object( $payment_gateways[ $payment_method ] )
						&& method_exists( $payment_gateways[ $payment_method ], 'get_transaction_url' )
					) {
						$charge_url = (string) $payment_gateways[ $payment_method ]->get_transaction_url( $order );
					}
				}
			}

			{
				foreach ( $columns as $column_name => $column_display_name ) {
					switch ( $column_name ) {
						case 'id':
							?>
							<td class='bsd-scsp column-id center-column'>
								<?php echo esc_html( $item['id'] ); ?>
							</td>
							<?php
							break;
						case 'wc_order_id':
							?>
							<td class='bsd-scsp column-wc_order_id center-column id_<?php echo esc_html( $item['id'] ); ?>'>
								<?php if ( ! empty( $order_edit_url ) ) : ?>
									<a href="<?php echo esc_url( $order_edit_url ); ?>" target="_blank" rel="noopener noreferrer">
										<?php echo esc_html( $item['wc_order_id'] ); ?>
									</a>
								<?php else : ?>
									<?php echo esc_html( $item['wc_order_id'] ); ?>
								<?php endif; ?>
							</td>
							<?php
							break;
						case 'charge_date':
							// 3.7.5: case key was wrongly 'column-charge_date' (the CSS
							// class name, not the column key) — the switch never
							// matched so the date column fell through unrendered.
							// The M4a recovered-pill + em-dash render lives in
							// column_default(), but single_row_columns() overrides
							// that path, so we route through render_charge_date()
							// here too. Discovered Path B Phase 4 triple-check.
							?>
							<td class='bsd-scsp column-charge_date center-column'>
								<?php echo wp_kses_post( $this->render_charge_date( $item ) ); ?>
							</td>
							<?php
							break;
						case 'charge_amount':
							?>
							<td class='bsd-scsp column-charge_amount center-column'>
								<?php echo wp_kses_post( wc_price( $item['charge_amount'] ) ); ?>
							</td>
							<?php
							break;
						case 'transfer_amount':
							?>
							<td class='bsd-scsp column-transfer_amount center-column'
								data-transfer_type="<?php echo esc_attr( ( isset( $item['transfer_type'] ) ) ? $item['transfer_type'] : '' ); ?>">
								<?php echo wp_kses_post( wc_price( $item['transfer_amount'] ) ); ?>
							</td>
							<?php
							break;
						case 'transfer_tax_value':
							?>
							<td class='bsd-scsp column-transfer_tax_value center-column'
								data-transfer_type="<?php echo esc_attr( ( isset( $item['transfer_tax_value'] ) ) ? $item['transfer_tax_value'] : '' ); ?>">
								<?php echo wp_kses_post( wc_price( $item['transfer_tax_value'] ) ); ?>
							</td>
							<?php
							break;

						case 'transfer_destination':
							?>
							<td class='bsd-scsp column-transfer_destination center-column'>
								<a href="<?php echo esc_url( $account_url ); ?>" target="_blank" rel="noopener noreferrer"
									title="<?php echo esc_attr( $item['transfer_destination'] ); ?>"><?php echo esc_attr( substr( $item['transfer_destination'], 0, 8 ) ) . '...'; ?><?php echo esc_attr( substr( $item['transfer_destination'], - 5 ) ); ?></a>

							</td>
							<?php
							break;
						case 'charge_id':
							?>
							<td class='bsd-scsp column-charge_id center-column'>
								<a href="<?php echo esc_url( $charge_url ); ?>" target="_blank" rel="noopener noreferrer"
									title="<?php echo esc_html( $item['charge_id'] ); ?>"><?php echo esc_html( substr( $item['charge_id'], 0, 5 ) ) . '...'; ?><?php echo esc_html( substr( $item['charge_id'], - 5 ) ); ?></a>
							</td>
							<?php
							break;
						case 'transfer_id':
							?>
							<td class='bsd-scsp column-transfer_id center-column'>
								<a href="<?php echo esc_url( $transfer_url ); ?>" target="_blank" rel="noopener noreferrer"
									title="<?php echo esc_html( $item['transfer_id'] ); ?>"><?php echo esc_html( substr( $item['transfer_id'], 0, 5 ) . '...' ); ?><?php echo esc_html( substr( $item['transfer_id'], - 5 ) ); ?></a>
							</td>
							<?php
							break;
					case 'integration_source':
						?>
						<td class='bsd-scsp column-integration_source center-column'>
							<?php echo esc_html( $this->render_integration_source( $item['integration_source'] ?? 'woocommerce' ) ); ?>
						</td>
						<?php
						break;
					case 'transfer_type':
						?>
						<td class='bsd-scsp column-transfer_type center-column'>
							<?php
							if ( isset( $item['transfer_type'] ) ) {
									switch ( $item['transfer_type'] ) {
										case 1:
											// echo "Global percentage transfer";
											echo 'Subtotal';
											break;
										case 2:
											// echo "Global fixed transfer";
											echo 'Subtotal';
											break;
										case 3:
											// echo "Global shipping percentage transfer";
											echo 'Shipping';
											break;
										case 4:
											// echo "Global shipping fixed transfer";
											echo 'Shipping';
											break;
										case 5:
											// echo "Product-level transfer";
											echo 'Subtotal';
											break;
										case 6:
											// echo "Product-level shipping percentage transfer";
											echo 'Shipping';
											break;
										case 7:
											// echo "Product-level shipping fixed transfer";
											echo 'Shipping';
											break;
										case 8:
										case 9:
										case 10:
										case 11:
											// echo "Product-level shipping fixed transfer";
											echo 'Subtotal';
											break;
										case 12:
											// echo "Product extras";
											echo 'Product extras';
											break;
									}
								}
								?>
							</td>
							<?php
							break;

							case 'tax_transfer_type':
							?>
								<td class='bsd-scsp column-tax_transfer_type center-column'>
							<?php
							if ( isset( $item['tax_transfer_type'] ) ) {

								if ( isset( $item['transfer_type'] ) ) {
									if ( $item['transfer_type'] == 6 || $item['transfer_type'] == 7 ) {
										$item['tax_transfer_type'] = 'product_shipping';
									}
								}

								switch ( $item['tax_transfer_type'] ) {
									case 'all':
										echo esc_html__( 'All (100%)', 'bsd-split-pay-stripe-connect-woo' );
										break;
									case 'partial':
										echo esc_html__( 'Partial', 'bsd-split-pay-stripe-connect-woo' );
										break;
									case 'product_shipping':
										echo esc_html__( 'N/A', 'bsd-split-pay-stripe-connect-woo' );
										echo '<span class="tooltip woocommerce-help-tip">';
											echo wp_kses_post( __( '<span class="tooltiptext">Shipping tax transfers do not apply when product-level shipping transfer settings are configured. Read our <a href="https://docs.splitpayplugin.com/features/tax-handling" target="_blank" rel="noopener noreferrer">documentation</a> for details.</span>', 'bsd-split-pay-stripe-connect-woo' ) );
										echo '</span>';
										break;

									}
								}
							?>
								</td>
							<?php
								break;

						case 'transfer_entered_value':
							?>
							<td class='bsd-scsp column-entered_value center-column'>
								<?php
								if ( isset( $item['transfer_entered_value'] ) && ! empty( $item['transfer_entered_value'] ) ) {
									echo esc_html( $item['transfer_entered_value'] );
								}
								?>
							</td>
							<?php
							break;
						case 'entered_variable':
							?>
							<td class='bsd-scsp column-entered_variable center-column'>
								<?php
								if ( isset( $item['entered_variable'] ) ) {
									switch ( $item['entered_variable'] ) {
										case 1:
											// echo "Global percentage transfer";
											echo 'Percentage';
											break;
										case 2:
											// echo "Global fixed transfer";
											echo 'Fixed';
											break;
										case 3:
											// echo "Global shipping percentage transfer";
											echo 'Percentage';
											break;
										case 4:
											// echo "Global shipping fixed transfer";
											echo 'Fixed';
											break;
										case 5:
											// echo "Product-level transfer";
											echo 'Percentage';
											break;
										case 6:
											// echo "Product-level shipping percentage transfer";
											echo 'Percentage';
											break;
										case 7:
											// echo "Product-level shipping fixed transfer";
											echo 'Fixed';
											break;
										case 8:
											// echo "Product-level shipping fixed transfer";
											echo 'Percentage';
											break;
										case 9:
											// echo "Product-level shipping fixed transfer";
											echo 'Fixed';
											break;
										case 10:
											// echo "Product-level shipping fixed transfer";
											echo 'Percentage';
											break;
										case 11:
											// echo "Product-level shipping fixed transfer";
											echo 'Fixed';
											break;
									}
								}
								?>
							</td>
							<?php
							break;
						case 'level':
							?>
							<td class='bsd-scsp column-level center-column'>
								<?php
								if ( isset( $item['level'] ) ) {
									switch ( $item['level'] ) {
										case 1:
											// echo "Global percentage transfer";
											echo 'Global';
											break;
										case 2:
											// echo "Global fixed transfer";
											echo 'Global';
											break;
										case 3:
											// echo "Global shipping percentage transfer";
											echo 'Global';
											break;
										case 4:
											// echo "Global shipping fixed transfer";
											echo 'Global';
											break;
										case 5:
											// echo "Product-level transfer";
											echo 'Product';
											break;
										case 6:
											// echo "Product-level shipping percentage transfer";
											echo 'Product';
											break;
										case 7:
											// echo "Product-level shipping fixed transfer";
											echo 'Product';
											break;
										case 8:
										case 9:
											// echo "Product-level shipping fixed transfer";
											echo 'Product';
											break;
										case 10:
										case 11:
											// echo "Product-level shipping fixed transfer";
											echo 'Variable';
											break;
									}
								}
								?>
							</td>
							<?php
							break;

						default:
							echo '<td>';
							echo wp_kses_post( $this->column_default( $item, $column_name ) );
							echo '</td>';
					}
				}
			}
		}
	}

endif; // class_exists check
