<?php
// phpcs:disable WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
// phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
// phpcs:disable WordPress.PHP.YodaConditions.NotYoda

/**
 * The admin-specific functionality of the plugin.
 *
 * @link       https://github.com/ParogDev
 * @since      1.0.0
 *
 * @package    WeeConnectPay
 * @subpackage WeeConnectPay/admin
 */

namespace WeeConnectPay\WordPress\Plugin\admin;

use Exception;
use WC_Order;
use WeeConnectPay\Integrations\IntegrationSettings;
use WeeConnectPay\Integrations\LogService;
use WeeConnectPay\ThreeDSecureFormatter;
use WeeConnectPay\WordPress\Plugin\includes\RegisterSettings;
use WeeConnectPay\WordPress\Plugin\includes\WeeConnectPayOrderProcessor;
use WeeConnectPay\WordPress\Plugin\includes\WeeConnectPayController;
use WeeConnectPay\WordPress\Plugin\includes\WeeConnectPayHelper;
use WeeConnectPay\WordPress\Plugin\includes\WeeConnectPaySettingsCallback;
use WeeConnectPay\WordPress\Plugin\includes\WeeConnectPayUtilities;
use WeeConnectPay\WordPress\Plugin\includes\WeeConnectPayWooProductImport;

/**
 * The admin-specific functionality of the plugin.
 *
 * Defines the plugin name, version, and two examples hooks for how to
 * enqueue the admin-specific stylesheet and JavaScript.
 *
 * @package    WeeConnectPay
 * @subpackage WeeConnectPay/admin
 * @author     ParogDev <integration@cspaiement.com>
 */
class WeeConnectPayAdmin {


	/**
	 * The ID of this plugin.
	 *
	 * @since    1.0.0
	 * @access   private
	 * @var      string $pluginName The ID of this plugin.
	 */
	private $pluginName;

	/**
	 * The version of this plugin.
	 *
	 * @since    1.0.0
	 * @access   private
	 * @var      string $version The current version of this plugin.
	 */
	private $version;

	/**
	 * An instance of the WeeConnectPaySettingsCallback class
	 *
	 * @since    1.0.0
	 * @access   private
	 * @var WeeConnectPaySettingsCallback Class holding settings callback functions
	 */
	private $settingsCallback;

	/**
	 * An instance of the RegisterSettings class
	 *
	 * @since    1.0.0
	 * @access   private
	 * @var RegisterSettings
	 */
	private $settings;

	/**
	 * Initialize the class and set its properties.
	 *
	 * @param string $pluginName The name of this plugin.
	 * @param string $version The version of this plugin.
	 *
	 * @since    1.0.0
	 */
	public function __construct( string $pluginName, string $version ) {
		$this->pluginName       = $pluginName;
		$this->version          = $version;
		$this->settingsCallback = new WeeConnectPaySettingsCallback();
		$this->settings         = new RegisterSettings( $this->settingsCallback );
	}

	/**
	 * Register the stylesheets for the admin area.
	 *
	 * @since    1.0.0
	 */
	public function enqueueStyles() {

		/**
		 * This function is provided for demonstration purposes only.
		 *
		 * An instance of this class should be passed to the run() function
		 * defined in WeeConnectPayLoader as all of the hooks are defined
		 * in that particular class.
		 *
		 * The WeeConnectPayLoader will then create the relationship
		 * between the defined hooks and the functions defined in this
		 * class.
		 */

//		 PostProcess CSS
		      wp_enqueue_style(
		          $this->pluginName,
		          plugin_dir_url( __FILE__ ) . 'css/weeconnectpay-admin.css',
		          array(),
		          time(),
		          'all'
		      );

	}

	/**
	 * Register the JavaScript for the admin area.
	 * @updated  2.0.4
	 * @since    1.0.0
	 */
	public function enqueueScripts() {

		/**
		 * This function is provided for demonstration purposes only.
		 *
		 * An instance of this class should be passed to the run() function
		 * defined in WeeConnectPayLoader as all of the hooks are defined
		 * in that particular class.
		 *
		 * The WeeConnectPayLoader will then create the relationship
		 * between the defined hooks and the functions defined in this
		 * class.
		 */

		wp_enqueue_script(
			$this->pluginName,
			plugin_dir_url( __FILE__ ) . 'js/weeconnectpay-admin.js',
			array( 'jquery' ),
			$this->version,
			false
		);
	}

	/**
	 * Add the top level menu for the admin area — redirects to the WooCommerce gateway settings page.
	 *
	 * @since   1.0.0
	 * @updated 3.18.0
	 */
	public function addToplevelMenu() {
		add_menu_page(
			'WeeConnectPay Settings',
			'WeeConnectPay',
			'manage_options',
			'weeconnectpay',
			array( $this, 'redirectToGatewaySettings' ),
			'dashicons-admin-generic',
			null
		);
	}

	/**
	 * Redirects to the WooCommerce gateway settings page.
	 *
	 * @since 3.18.0
	 * @return void
	 */
	public function redirectToGatewaySettings() {
		$url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=weeconnectpay' );
		if ( ! headers_sent() ) {
			wp_safe_redirect( $url );
			exit;
		} else {
			echo '<script>window.location.href = ' . wp_json_encode( $url ) . ';</script>';
			echo '<p>Redirecting to <a href="' . esc_url( $url ) . '">WeeConnectPay settings</a>...</p>';
		}
	}

	/**
	 * Adds plugin action links.
	 *
	 * @param  $actions
	 *
	 * @return array
	 * @since 1.0.1
	 */
	public function pluginActionLinks( $actions ): array {
		$woocommerce_settings_url = WeeConnectPayUtilities::getSettingsURL();
		$plugin_links             = array(
			"<a href=$woocommerce_settings_url>" . __( 'WooCommerce Settings', 'weeconnectpay' ) . '</a>',
		);

		return array_merge( $plugin_links, $actions );
	}


	/**
	 * Ajax init_weeconnectpay_import handler
	 */
	public function init_weeconnectpay_import() {
		$import = new WeeConnectPayWooProductImport();
		$import->process_import();
		wp_send_json_success( 'It works' );
	}

	/**
	 * Load the admin page partial
	 *
	 * @since   1.0.0
	 * @noinspection PhpIncludeInspection
	 */
	public function loadAdminPageContent() {
		require_once plugin_dir_path( __FILE__ ) . 'partials/weeconnectpayAdminDisplay.php';
	}


	/**
	 * Registers the settings
	 *
	 * @since 1.0.0
	 */
	public function registerSettings() {
		$registerSettings = new RegisterSettings( $this->settingsCallback );
		$registerSettings->run();
	}

	public function register_routes() {
			$WeeConnectPayController = new WeeConnectPayController();
			$WeeConnectPayController->register_routes();
	}

	/**
	 *  Adds search additional fields to query when using the WooCommerce order search bar
	 *
	 * @since 2.2.0
	 * @param array $search_fields
	 *
	 * @return array
	 */
	function added_wc_search_fields( array $search_fields ): array {
		$search_fields[] = 'weeconnectpay_clover_order_uuid';
		$search_fields[] = 'weeconnectpay_clover_payment_uuid';
		return $search_fields;
	}


	/**
	 * Since we do not deal with orders but simply the ordering of an array, this can be used for both hpos and legacy.
	 * The name is specified since we do use a different hook for it in each case.
	 *
	 * @param $columns
	 *
	 * @return array
	 * @since 3.9.0
	 */
	function add_custom_orders_column_card_brand_hpos_and_legacy($columns): array {
		$reordered_columns = array();

		foreach( $columns as $key => $column){
			$reordered_columns[$key] = $column;

			if( $key ===  'shipping_address' ){
				// Inserting after "Total" column
				$reordered_columns['card-brand'] = __( 'Card Brand','weeconnectpay');
			}
		}
		return $reordered_columns;
	}


	/**
	 * Populates the "Card Brand" column with data from the order if available.
	 *
	 * @param $column
	 * @param $order
	 *
	 * @return void
	 * @since 3.9.0
	 */
	public function populate_custom_orders_column_card_brand_hpos($column, $order): void {
		$this->populate_custom_orders_column_card_brand_wrapper( $column, $order );
	}


	/**
	 * Populates the "Card Brand" column with data from the order if available.
	 *
	 * @param $column
	 * @param $post_id
	 *
	 * @return void
	 * @since 3.9.0
	 */
	public function populate_custom_orders_column_card_brand_legacy($column, $post_id): void {
		$order = wc_get_order( $post_id );

		$this->populate_custom_orders_column_card_brand_wrapper( $column, $order );
	}

	/**
	 * Wrapper to ensure that if we change anything, it will be changed for both legacy and hpos versions
	 * @param $column
	 * @param $order
	 *
	 * @return void
	 * @since 3.9.0
	 */
	protected function populate_custom_orders_column_card_brand_wrapper( $column, $order ): void {
		if ( $order instanceof WC_Order && $column === 'card-brand' ) {
			// Get custom order metadata
			$cardBrand = $order->get_meta( 'weeconnectpay_card_brand' );
			if ( ! empty( $cardBrand ) ) {
				echo esc_html( $cardBrand );
			}
		}
	}

    public static function weeconnectpay_refund_charge_callback()
    {
        // Check nonce for security.
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'weeconnectpay_refund_charge_nonce')) {
            wp_send_json_error('Invalid nonce.');
        }

        // Verify current user permissions.
        if (!current_user_can('manage_woocommerce')) {
            wp_send_json_error('Insufficient permissions.');
        }

        // Get and validate the order, charge ID, charge type, and refund amount.
        $order_id    = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
        $charge_id   = isset( $_POST['charge_id'] ) ? sanitize_text_field( wp_unslash( $_POST['charge_id'] ) ) : '';
        $charge_type = isset( $_POST['charge_type'] ) ? sanitize_text_field( wp_unslash( $_POST['charge_type'] ) ) : '';

        // Refund amount here is expected as raw cents.
        $refund_amount = isset( $_POST['refund_amount'] ) ? absint( $_POST['refund_amount'] ) : 0;

        // Reason for the refund/void — defaults to 'requested_by_customer' if omitted.
        $reason = isset( $_POST['reason'] ) ? sanitize_text_field( wp_unslash( $_POST['reason'] ) ) : 'requested_by_customer';
        $allowed_reasons = array( 'requested_by_customer', 'duplicate', 'fraudulent' );
        if ( ! in_array( $reason, $allowed_reasons, true ) ) {
            $reason = 'requested_by_customer';
        }

        if ( !$order_id || empty( $charge_id ) || !$refund_amount ) {
            wp_send_json_error( 'Missing order ID, charge ID, or refund amount.' );
        }

        // Load the order.
        $order = wc_get_order($order_id);
        if (!$order) {
            wp_send_json_error( 'Order not found.' );
        }

        // For order-based charges, only full refunds are supported.
        $clover_order_uuid = $order->get_meta( 'weeconnectpay_clover_order_uuid' );
        if ( ! empty( $clover_order_uuid ) && 'payment' === $charge_type ) {
            $charges = $order->get_meta( 'weeconnectpay_charges', true );
            if ( is_array( $charges ) && isset( $charges[ $charge_id ] ) ) {
                $original_amount = (int) $charges[ $charge_id ]['amount'];
                if ( $refund_amount !== $original_amount ) {
                    wp_send_json_error(
                        __( 'Only full refunds are supported for order-based charges.', 'weeconnectpay' )
                    );
                }
            }
        }

        // Determine if this is a void (preauth) or refund
        $is_void = 'preauth' === $charge_type;

        // Translatable strings for void vs refund
        /* translators: %s: charge ID */
        $reason_text = $is_void
            ? sprintf( __( 'Void for charge %s', 'weeconnectpay' ), $charge_id )
            : sprintf( __( 'Refund for charge %s', 'weeconnectpay' ), $charge_id );

        try {
            // Process the refund/void via the static helper function.
            $refundData = WeeConnectPayHelper::processCloverChargeRefund( $charge_id, $refund_amount, $reason );

            // Record the refund event and update charge status accordingly.
            $refund_id     = isset( $refundData['id'] ) ? $refundData['id'] : '';
            $updatedCharge = WeeConnectPayHelper::addRefundEventToCharge(
                $order,
                $charge_id,
                $refund_id,
                $refund_amount,
                $reason,
                $is_void
            );

            $new_status              = $updatedCharge['status'];
            $charge_total_refunded   = WeeConnectPayHelper::getChargeRefundedTotal( $updatedCharge );
            $charge_remaining_cents  = (int) $updatedCharge['amount'] - $charge_total_refunded;

            // Create a WooCommerce refund.
            // Convert the refund amount from cents to dollars for WooCommerce.
            $refund = wc_create_refund([
                'amount'    => wc_format_decimal( $refund_amount / 100, 2 ),
                'reason'    => $reason_text,
                'order_id'  => $order_id,
            ]);
            if (is_wp_error($refund)) {
                $error_message = $is_void
                    ? __( 'Void creation failed:', 'weeconnectpay' )
                    : __( 'Refund creation failed:', 'weeconnectpay' );
                throw new Exception( $error_message . ' ' . $refund->get_error_message() );
            }

            // Add refund/void notes using the helper
            WeeConnectPayHelper::addRefundNotesToOrder($order, [
                'result' => 'success',
                'data' => $refundData
            ], $reason_text);

        } catch (Exception $e) {
            LogService::error( sprintf(
                'Exception during %s for order #%d, charge %s: %s',
                $is_void ? 'void' : 'refund',
                $order_id,
                $charge_id,
                $e->getMessage()
            ) );
            wp_send_json_error($e->getMessage());
        }

        // Return enriched response with status and remaining amount info.
        wp_send_json_success( array(
            'clover_data'               => $refundData,
            'new_status'                => $new_status,
            'refund_amount_cents'       => $refund_amount,
            'charge_total_refunded_cents' => $charge_total_refunded,
            'charge_remaining_cents'    => $charge_remaining_cents,
            'refund_event'              => end( $updatedCharge['refunds'] ),
        ) );
    }

	/**
	 * Add WeeConnectPay custom messages to the shop_order messages array.
	 * 
	 * Used by both woocommerce_order_updated_messages (HPOS) and post_updated_messages (legacy)
	 * filters to add custom success messages for WeeConnectPay actions.
	 * 
	 * Priority 20 ensures this runs AFTER WooCommerce's filter (priority 10).
	 * 
	 * @since 3.17.0
	 * @access public
	 * @param array $messages Existing messages array
	 * @return array Modified messages array
	 */
	public function add_custom_order_messages( $messages ) {
		// Only add messages if shop_order messages exist (after WooCommerce has set them)
		if ( isset( $messages['shop_order'] ) && is_array( $messages['shop_order'] ) ) {
			// Merge our custom message codes into the existing array
			// Message code 701: Capture charge success
			$messages['shop_order'][701] = __( 'Preauthorized charge captured successfully.', 'weeconnectpay' );
			
			// Message code 702: Refund charge success (not used yet)
			$messages['shop_order'][702] = __( 'Charge refunded successfully.', 'weeconnectpay' );
		}
		
		return $messages;
	}

	/**
	 * Handle capture charge AJAX request
	 * 
	 * This method processes the capture of a preauthorized charge
	 * and stores a success message in a transient for display after page reload.
	 * 
	 * @since 3.17.0
	 * @access public
	 */
	public static function weeconnectpay_capture_charge_callback() {
		LogService::info( 'Capture charge AJAX handler called' );
		
		// Get and sanitize request data
		$order_id             = isset($_REQUEST['order_id']) ? absint($_REQUEST['order_id']) : 0;
		$capture_charge_nonce = isset($_REQUEST['capture_charge_nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['capture_charge_nonce'])) : '';

		LogService::info( sprintf( 'Received order_id: %s, nonce: %s', $order_id, $capture_charge_nonce ? 'present' : 'missing' ) );

		// Verify nonce
		if ( empty( $order_id ) || ! wp_verify_nonce( $capture_charge_nonce, "capture_charge_{$order_id}_nonce" ) ) {
			LogService::error( 'Invalid nonce or missing order ID in capture charge request' );
			wp_send_json_error( 'Invalid nonce or missing order ID.' );
		}

		// Verify current user permissions
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			LogService::error( 'Insufficient permissions for capture charge request' );
			wp_send_json_error( 'Insufficient permissions.' );
		}

		LogService::info( 'Capturing charge for order #' . $order_id );

		// Load the order
		$order = wc_get_order( $order_id );
		
		if ( ! $order ) {
			LogService::error( sprintf( 'Order #%d not found', $order_id ) );
			wp_send_json_error( 'Order not found.' );
		}
		
		// Validate order eligibility before creating processor (performance optimization)
		if ( ! WeeConnectPayHelper::canCaptureCharge( $order ) ) {
			LogService::error( sprintf( 'Order #%d is not eligible for charge capture', $order_id ) );
			wp_send_json_error( 'Order is not eligible for charge capture.' );
		}
		
		try {
			// Only create processor instance after validation passes
			$integrationSettings = new IntegrationSettings();
			$processor = new WeeConnectPayOrderProcessor( $integrationSettings );

			// Capture the charge
			$response = $processor->capturePreauthorizedCharge( $order );
			LogService::info( sprintf( 'Capture response for order #%d: %s', $order_id, wp_json_encode( $response ) ) );

			$message_code = 701; // Message code 701 = "Preauthorized charge captured successfully."

			// Handles known cases of failure.
			if ( isset( $response['result'], $response['message'] ) && $response['result'] === 'fail' ) {
				if ( $response['message'] === 'cannot capture a non AUTH payment' ) {
					$response['message'] = __( 'The preauthorized charge has already been captured. Did you capture it directly in the Clover dashboard?', 'weeconnectpay' );
				}
			}

			// Build redirect URL with message parameter (WooCommerce pattern)
			$redirect_url = add_query_arg( 'message', $message_code, wp_get_referer() );
			$response['redirect_url'] = $redirect_url;

			wp_send_json_success( $response );
		} catch ( Exception $e ) {
			LogService::error( sprintf( 'Exception during capture for order #%d: %s', $order_id, $e->getMessage() ) );
			wp_send_json_error( $e->getMessage() );
		}
	}



    /**
     * Handle log download requests
     * 
     * @access public
     */
    public function handle_log_download(): void {
        if (!current_user_can('manage_woocommerce')) {
            return;
        }

        if (isset($_POST['weeconnectpay_download_logs'])) {
            $filters = [
                'level' => isset($_GET['log_level']) ? sanitize_text_field($_GET['log_level']) : '',
                'date_from' => isset($_GET['log_date_from']) ? sanitize_text_field($_GET['log_date_from']) : '',
                'date_to' => isset($_GET['log_date_to']) ? sanitize_text_field($_GET['log_date_to']) : '',
            ];

            $logger = new \WeeConnectPay\Integrations\Logger();
            $filepath = $logger->downloadLogs($filters);
            if ($filepath && file_exists($filepath)) {
                header('Content-Type: text/plain');
                header('Content-Disposition: attachment; filename="' . basename($filepath) . '"');
                header('Content-Length: ' . filesize($filepath));
                readfile($filepath);
                unlink($filepath); // Clean up the temporary file
                exit;
            }
        }
    }

	/**
	 * Detect plugin version changes and record the transition in the (update-safe) log.
	 *
	 * Runs on admin_init. Compares the persisted plugin version against the running version:
	 * - No stored version yet (first run after version tracking shipped): record a baseline
	 *   marker and persist the current version. We cannot know the prior version because it
	 *   was never stored, so we do not claim an "update from X".
	 * - Stored version differs from current: a plugin update happened. Write an audit entry
	 *   so the version transition that previously coincided with log loss is now visible.
	 * The version is persisted regardless of debug mode; the log entry itself is gated by
	 * debug mode (LogService), which is correct since there is nothing to preserve otherwise.
	 *
	 * @access public
	 * @return void
	 */
	public function maybeRecordPluginVersionTransition(): void {
		try {
			$settings       = new IntegrationSettings();
			$storedVersion  = $settings->getPluginVersion();
			$currentVersion = $this->version;

			if ( $storedVersion === $currentVersion ) {
				return;
			}

			if ( $storedVersion === '' ) {
				LogService::info( sprintf( 'WeeConnectPay version tracking initialized at v%s', $currentVersion ) );
			} else {
				LogService::info( sprintf( 'Plugin updated from v%s to v%s', $storedVersion, $currentVersion ) );
			}

			$settings->setPluginVersion( $currentVersion );
		} catch ( \Throwable $e ) {
			error_log( '[WeeConnectPay] Failed to record plugin version transition: ' . $e->getMessage() );
		}
	}

	/**
	 * Add WeeConnectPay Charges meta box to order page
	 */
	public function add_weeconnectpay_charges_meta_box() {
		// Check if we're on the order screen
		$screen = get_current_screen();
		if ( ! $screen || wc_get_page_screen_id( 'shop-order' ) !== $screen->id ) {
			return;
		}

		// Get the order ID from the current admin screen (HPOS uses ?id=N, legacy uses ?post=N).
		$order_id = WeeConnectPayUtilities::getOrderIdFromCurrentScreen();
		if ( ! $order_id ) {
			return;
		}

		try {
			$order = wc_get_order( $order_id );
			if ( ! $order instanceof WC_Order ) {
				return;
			}

			// Check if there are any charges for this order
			$charges = $order->get_meta( 'weeconnectpay_charges', true );
			if ( ! is_array( $charges ) || empty( $charges ) ) {
				return;
			}

			// Add the meta box
			add_meta_box(
				'weeconnectpay_charges_meta_box',
				__( 'WeeConnectPay Charges', 'weeconnectpay' ),
				array( $this, 'display_weeconnectpay_charges_table' ),
				wc_get_page_screen_id( 'shop-order' ),
				'normal',
				'high'
			);
		} catch ( Exception $e ) {
			LogService::error( 'Failed to add WeeConnectPay charges meta box: ' . $e->getMessage() );
			return;
		}
	}

	/**
	 * Display the WeeConnectPay Credit Card Charges table on the WooCommerce order edit page.
	 *
	 * Renders charge cards with clickable headers that reveal a transaction timeline
	 * (charge created, refund events) and allow further refund actions. Supports
	 * multiple partial refunds until fully refunded.
	 *
	 * @param WC_Order $order The order object
	 */
	function display_weeconnectpay_charges_table( $order ) {
		if ( ! $order instanceof WC_Order ) {
			return;
		}

		// Retrieve the charges saved via your helper
		$charges = $order->get_meta( 'weeconnectpay_charges', true );

		// Only proceed if charges exist and are an array
		if ( ! is_array( $charges ) || empty( $charges ) ) {
			return;
		}

		$refund_nonce = wp_create_nonce( 'weeconnectpay_refund_charge_nonce' );
		$order_id     = $order->get_id();

		// SVG icons reused across cards
		$svg_refund  = '<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M3 10h10a5 5 0 0 1 0 10H9M3 10l4-4M3 10l4 4"/></svg>';
		$svg_void    = '<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M6 18L18 6M6 6l12 12"/></svg>';
		$svg_warning = '<svg class="wcp-confirm-bar-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 9v2m0 4h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z"/></svg>';
		$svg_error   = '<svg class="wcp-error-bar-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 1 1-18 0 9 9 0 0 1 18 0z"/></svg>';
		$svg_chevron = '<svg class="wcp-chevron" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="16" height="16"><path d="M19 9l-7 7-7-7"/></svg>';

		// Type label mapping
		$type_labels = array(
			'payment' => __( 'Charge', 'weeconnectpay' ),
			'preauth' => __( 'Pre-auth', 'weeconnectpay' ),
			'capture' => __( 'Capture', 'weeconnectpay' ),
		);

		// Badge label mapping
		$badge_labels = array(
			'success'              => __( 'Success', 'weeconnectpay' ),
			'partially_refunded'   => __( 'Partial Refund', 'weeconnectpay' ),
			'refunded'             => __( 'Refunded', 'weeconnectpay' ),
			'voided'               => __( 'Voided', 'weeconnectpay' ),
			'captured'             => __( 'Captured', 'weeconnectpay' ),
		);

		// Reason labels for timeline display
		$reason_labels = array(
			'requested_by_customer' => __( 'Requested by customer', 'weeconnectpay' ),
			'duplicate'             => __( 'Duplicate', 'weeconnectpay' ),
			'fraudulent'            => __( 'Fraudulent', 'weeconnectpay' ),
			'wc_refund'             => __( 'WooCommerce Refund', 'weeconnectpay' ),
			'wc_return'             => __( 'WooCommerce Return', 'weeconnectpay' ),
		);
		?>
		<div class="wcp-cards">
		<?php
		$total_refunded_cents = (int) round( (float) $order->get_total_refunded() * 100 );
		$refund_pool          = $total_refunded_cents;
		$has_clover_order     = ! empty( $order->get_meta( 'weeconnectpay_clover_order_uuid' ) );
		?>
		<?php foreach ( $charges as $charge ) :
			$charge_id      = $charge['charge_id'];
			$charge_type    = $charge['type'] ?? 'payment';
			$amount_cents   = (int) $charge['amount'];
			$display_amount = number_format( $amount_cents / 100, 2, '.', '' );
			$currency       = $charge['currency'] ?? 'CAD';
			$card_type      = $charge['card_type'] ?? '';
			$card_last4     = $charge['card_last4'] ?? '';
			$status         = $charge['status'] ?? '';

			// Use event-based refund total if available, otherwise fall back to pool-based calc.
			$has_refund_events = isset( $charge['refunds'] ) && is_array( $charge['refunds'] ) && ! empty( $charge['refunds'] );
			if ( $has_refund_events ) {
				$charge_refunded_cents = WeeConnectPayHelper::getChargeRefundedTotal( $charge );
			} elseif ( in_array( $status, array( 'refunded', 'voided' ), true ) ) {
				$charge_refunded_cents = $amount_cents;
				$refund_pool           = max( 0, $refund_pool - $amount_cents );
			} elseif ( 'success' === $status ) {
				$charge_refunded_cents = min( $refund_pool, $amount_cents );
				$refund_pool          -= $charge_refunded_cents;
			} else {
				$charge_refunded_cents = 0;
			}
			$charge_remaining_cents = $amount_cents - $charge_refunded_cents;
			$remaining_display      = number_format( $charge_remaining_cents / 100, 2, '.', '' );
			$refunded_display       = number_format( $charge_refunded_cents / 100, 2, '.', '' );

			$threeds_data          = isset( $charge['threeds'] ) && is_array( $charge['threeds'] ) ? $charge['threeds'] : null;
			$is_preauth            = 'preauth' === $charge_type;
			$is_actionable         = in_array( $status, array( 'success', 'partially_refunded' ), true ) && $charge_remaining_cents > 0;
			$is_order_charge       = $has_clover_order && 'payment' === $charge_type;

			// Order charges with ANY refund history can no longer be refunded further.
			$order_charge_has_returns = $is_order_charge && $has_refund_events;
			if ( $order_charge_has_returns ) {
				$is_actionable = false;
			}

			$type_label     = $type_labels[ $charge_type ] ?? ucfirst( $charge_type );
			$badge_label    = $badge_labels[ $status ] ?? ucfirst( $status );
			$is_fully_done  = in_array( $status, array( 'refunded', 'voided' ), true );

			// Determine if the card body has meaningful content to show
			$has_body_content = $has_refund_events || $is_actionable || $order_charge_has_returns;

			// Expiration date
			$exp_month = $charge['card_exp_month'] ?? '';
			$exp_year  = $charge['card_exp_year'] ?? '';
			$exp_display = '';
			if ( ! empty( $exp_month ) && ! empty( $exp_year ) ) {
				$exp_display = str_pad( $exp_month, 2, '0', STR_PAD_LEFT ) . '/' . $exp_year;
			}
			?>
			<div class="wcp-card"
				data-charge-id="<?php echo esc_attr( $charge_id ); ?>"
				data-order-id="<?php echo esc_attr( $order_id ); ?>"
				data-charge-type="<?php echo esc_attr( $charge_type ); ?>"
				data-amount-cents="<?php echo esc_attr( $amount_cents ); ?>"
				data-amount-display="<?php echo esc_attr( $display_amount ); ?>"
				data-currency="<?php echo esc_attr( $currency ); ?>"
				data-card-type="<?php echo esc_attr( $card_type ); ?>"
				data-card-last4="<?php echo esc_attr( $card_last4 ); ?>"
				data-status="<?php echo esc_attr( $status ); ?>"
				data-refunded-cents="<?php echo esc_attr( $charge_refunded_cents ); ?>"
				data-remaining-cents="<?php echo esc_attr( $charge_remaining_cents ); ?>"
				data-nonce="<?php echo esc_attr( $refund_nonce ); ?>"
				data-is-preauth="<?php echo $is_preauth ? '1' : '0'; ?>"
				data-is-order-charge="<?php echo $is_order_charge ? '1' : '0'; ?>">

				<!-- Clickable card header -->
				<div class="wcp-card-header<?php echo $has_body_content ? ' wcp-card-header-clickable' : ''; ?>">
					<div class="wcp-card-amount-block">
						<div class="wcp-card-amount<?php echo $is_fully_done ? ' wcp-card-amount-muted' : ''; ?>">$<?php echo esc_html( $display_amount ); ?></div>
						<div class="wcp-card-currency"><?php echo esc_html( $currency ); ?></div>
					</div>
					<div class="wcp-card-details">
						<div class="wcp-card-detail">
							<span class="wcp-card-detail-label"><?php esc_html_e( 'Charge ID', 'weeconnectpay' ); ?></span>
							<span class="wcp-card-detail-value mono"><?php echo esc_html( $charge_id ); ?></span>
						</div>
						<div class="wcp-card-detail">
							<span class="wcp-card-detail-label"><?php esc_html_e( 'Card', 'weeconnectpay' ); ?></span>
							<span class="wcp-card-detail-value"><?php echo esc_html( $card_type . ' · ' . $card_last4 ); ?></span>
						</div>
						<?php if ( ! empty( $exp_display ) ) : ?>
						<div class="wcp-card-detail">
							<span class="wcp-card-detail-label"><?php esc_html_e( 'Expires', 'weeconnectpay' ); ?></span>
							<span class="wcp-card-detail-value"><?php echo esc_html( $exp_display ); ?></span>
						</div>
						<?php endif; ?>
						<div class="wcp-card-detail">
							<span class="wcp-card-detail-label"><?php esc_html_e( 'Type', 'weeconnectpay' ); ?></span>
							<span class="wcp-card-detail-value"><?php echo esc_html( $type_label ); ?></span>
						</div>
					</div>
					<div class="wcp-card-right">
						<?php
						// 3-D Secure badge — ThreeDSecureFormatter::renderBadge() returns
						// pre-escaped HTML and an empty string when no 3DS data is present.
						// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
						echo ThreeDSecureFormatter::renderBadge( $threeds_data );
						?>
						<span class="wcp-badge wcp-badge-<?php echo esc_attr( $status ); ?>"><?php echo esc_html( $badge_label ); ?></span>
						<?php if ( $has_body_content ) : ?>
						<?php
						// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static SVG markup
						echo $svg_chevron;
						?>
						<?php else : ?>
						<span class="wcp-chevron-placeholder"></span>
						<?php endif; ?>
					</div>
				</div>

				<?php if ( $has_body_content ) : ?>
				<!-- Expandable card body (timeline + actions) -->
				<div class="wcp-card-body" style="display:none;">

					<?php if ( $has_refund_events || $is_fully_done ) : ?>
					<!-- Transaction timeline -->
					<div class="wcp-timeline">
						<div class="wcp-timeline-title"><?php esc_html_e( 'Transaction History', 'weeconnectpay' ); ?></div>
						<div class="wcp-timeline-events">
							<!-- Original charge event -->
							<div class="wcp-timeline-event">
								<div class="wcp-timeline-dot wcp-timeline-dot-success"></div>
								<div class="wcp-timeline-content">
									<div class="wcp-timeline-event-label"><?php echo esc_html( $type_label ); ?></div>
									<div class="wcp-timeline-event-amount">$<?php echo esc_html( $display_amount ); ?> <?php echo esc_html( $currency ); ?></div>
								</div>
							</div>

							<?php if ( $has_refund_events ) : ?>
							<?php foreach ( $charge['refunds'] as $refund_event ) :
								$evt_amount   = (int) ( $refund_event['amount'] ?? 0 );
								$evt_display  = number_format( $evt_amount / 100, 2, '.', '' );
								$evt_reason   = $refund_event['reason'] ?? '';
								$evt_label    = $reason_labels[ $evt_reason ] ?? $evt_reason;
								$evt_time     = $refund_event['timestamp'] ?? 0;
								$evt_time_str = $evt_time > 0 ? date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $evt_time ) : '';
								$evt_is_void  = ( 'voided' === $status && $evt_amount === $amount_cents );
								?>
								<div class="wcp-timeline-event">
									<div class="wcp-timeline-dot <?php echo $evt_is_void ? 'wcp-timeline-dot-void' : 'wcp-timeline-dot-refund'; ?>"></div>
									<div class="wcp-timeline-content">
										<div class="wcp-timeline-event-label"><?php echo $evt_is_void ? esc_html__( 'Voided', 'weeconnectpay' ) : esc_html__( 'Refund', 'weeconnectpay' ); ?></div>
										<div class="wcp-timeline-event-amount">-$<?php echo esc_html( $evt_display ); ?> <?php echo esc_html( $currency ); ?></div>
										<?php if ( ! empty( $evt_label ) ) : ?>
										<div class="wcp-timeline-event-reason"><?php echo esc_html( $evt_label ); ?></div>
										<?php endif; ?>
										<?php if ( ! empty( $evt_time_str ) ) : ?>
										<div class="wcp-timeline-event-time"><?php echo esc_html( $evt_time_str ); ?></div>
										<?php endif; ?>
									</div>
								</div>
							<?php endforeach; ?>
							<?php endif; ?>
						</div>

						<?php if ( ! $is_fully_done && $charge_refunded_cents > 0 ) : ?>
						<!-- Remaining summary -->
						<div class="wcp-timeline-summary">
							<span class="wcp-timeline-summary-label"><?php esc_html_e( 'Remaining:', 'weeconnectpay' ); ?></span>
							<span class="wcp-timeline-summary-amount">$<?php echo esc_html( $remaining_display ); ?></span>
							<span class="wcp-timeline-summary-of">
								<?php
								/* translators: %s: original dollar amount */
								printf( esc_html__( 'of $%s', 'weeconnectpay' ), esc_html( $display_amount ) );
								?>
							</span>
						</div>
						<?php endif; ?>
					</div>
					<?php endif; ?>

					<?php if ( $order_charge_has_returns && ! $is_fully_done ) : ?>
					<div class="wcp-order-charge-blocked">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="14" height="14"><circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/></svg>
						<span><?php esc_html_e( 'A return has been processed for this order. Order-based charges cannot be refunded further once any items have been returned.', 'weeconnectpay' ); ?></span>
					</div>
					<?php endif; ?>

					<?php if ( $is_actionable ) : ?>
					<!-- Refund trigger row -->
					<div class="wcp-refund-trigger-row">
						<?php
						if ( $is_preauth ) {
							$trigger_label = __( 'Void', 'weeconnectpay' );
							$trigger_svg   = $svg_void;
						} elseif ( $charge_refunded_cents > 0 ) {
							$trigger_label = __( 'Refund More', 'weeconnectpay' );
							$trigger_svg   = $svg_refund;
						} else {
							$trigger_label = __( 'Refund', 'weeconnectpay' );
							$trigger_svg   = $svg_refund;
						}
						?>
						<button type="button" class="wcp-btn wcp-btn-trigger wcp-open-refund-btn">
							<?php
							// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static SVG markup
							echo $trigger_svg;
							?>
							<?php echo esc_html( $trigger_label ); ?>
						</button>
					</div>

					<!-- Expandable refund/void form -->
					<div class="wcp-card-refund" style="display:none;">
						<div class="wcp-refund-grid">
							<?php if ( ! $is_preauth ) : ?>
							<!-- Left: Breakdown bar -->
							<div class="wcp-breakdown">
								<div class="wcp-breakdown-title"><?php esc_html_e( 'Amount Breakdown', 'weeconnectpay' ); ?></div>
								<?php
								$already_refunded_pct = $amount_cents > 0 ? round( $charge_refunded_cents / $amount_cents * 100, 2 ) : 0;
								$refunding_init_pct   = $amount_cents > 0 ? round( $charge_remaining_cents / $amount_cents * 100, 2 ) : 100;
								?>
								<div class="wcp-breakdown-bar">
									<div class="wcp-breakdown-already-refunded" style="width:<?php echo esc_attr( $already_refunded_pct ); ?>%;"><?php echo $already_refunded_pct >= 15 ? '$' . esc_html( $refunded_display ) : ''; ?></div>
									<div class="wcp-breakdown-refunded" style="width:<?php echo esc_attr( $refunding_init_pct ); ?>%;"><?php echo $refunding_init_pct >= 15 ? '$' . esc_html( $remaining_display ) : ''; ?></div>
									<div class="wcp-breakdown-original" style="width:0%;"></div>
								</div>
								<div class="wcp-breakdown-legend">
									<?php if ( $charge_refunded_cents > 0 ) : ?>
									<div class="wcp-breakdown-legend-item">
										<span class="wcp-breakdown-legend-dot" style="background:#d5d5d5;"></span>
										<span><?php esc_html_e( 'Already Refunded:', 'weeconnectpay' ); ?></span>
										<span class="wcp-breakdown-legend-amount">$<?php echo esc_html( $refunded_display ); ?></span>
									</div>
									<?php endif; ?>
									<div class="wcp-breakdown-legend-item">
										<span class="wcp-breakdown-legend-dot" style="background:#f39c12;"></span>
										<span><?php esc_html_e( 'Refunding:', 'weeconnectpay' ); ?></span>
										<span class="wcp-breakdown-legend-amount wcp-legend-refunding">$<?php echo esc_html( $remaining_display ); ?></span>
									</div>
									<div class="wcp-breakdown-legend-item">
										<span class="wcp-breakdown-legend-dot" style="background:#59B210;"></span>
										<span><?php esc_html_e( 'Remaining:', 'weeconnectpay' ); ?></span>
										<span class="wcp-breakdown-legend-amount wcp-legend-remaining">$0.00</span>
									</div>
									<div class="wcp-breakdown-legend-item">
										<span class="wcp-breakdown-legend-dot" style="background:#e5e5e5;"></span>
										<span><?php esc_html_e( 'Original:', 'weeconnectpay' ); ?></span>
										<span class="wcp-breakdown-legend-amount">$<?php echo esc_html( $display_amount ); ?></span>
									</div>
								</div>
							</div>

							<!-- Right: Refund form -->
							<div class="wcp-refund-form">
								<label class="wcp-label"><?php esc_html_e( 'Refund Amount', 'weeconnectpay' ); ?></label>
								<div class="wcp-amount-input-wrapper">
									<span class="wcp-amount-prefix"><?php echo esc_html( $currency ); ?> $</span>
									<input class="wcp-amount-input" type="text" value="<?php echo esc_attr( $remaining_display ); ?>" />
								</div>
								<div class="wcp-quick-presets">
									<button type="button" class="wcp-quick-btn" data-preset="25">25%</button>
									<button type="button" class="wcp-quick-btn" data-preset="50">50%</button>
									<button type="button" class="wcp-quick-btn" data-preset="75">75%</button>
									<button type="button" class="wcp-quick-btn" data-preset="custom"><?php esc_html_e( 'Custom', 'weeconnectpay' ); ?></button>
									<button type="button" class="wcp-quick-btn active" data-preset="100"><?php esc_html_e( 'Full', 'weeconnectpay' ); ?></button>
								</div>
								<?php if ( 'capture' === $charge_type ) : ?>
								<div class="wcp-capture-partial-notice" style="display:none;">
									<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="14" height="14"><path d="M12 9v2m0 4h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z"/></svg>
									<span><?php esc_html_e( 'Partial refunds on captured pre-authorizations may only be available after the daily batch has been closed out. If the partial refund is declined, you can try a full refund instead.', 'weeconnectpay' ); ?></span>
								</div>
								<?php endif; ?>
								<?php if ( $has_clover_order && 'capture' !== $charge_type ) : ?>
								<div class="wcp-order-partial-notice" style="display:none;">
									<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="14" height="14"><circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/></svg>
									<span><?php esc_html_e( 'Partial refunds are not supported for order-based charges. Only a full refund of the entire charge amount can be processed.', 'weeconnectpay' ); ?></span>
								</div>
								<?php endif; ?>
								<label class="wcp-label"><?php esc_html_e( 'Reason', 'weeconnectpay' ); ?></label>
								<select class="wcp-reason-select">
									<option value=""><?php esc_html_e( 'Select a reason...', 'weeconnectpay' ); ?></option>
									<option value="requested_by_customer"><?php esc_html_e( 'Requested by customer', 'weeconnectpay' ); ?></option>
									<option value="duplicate"><?php esc_html_e( 'Duplicate charge', 'weeconnectpay' ); ?></option>
									<option value="fraudulent"><?php esc_html_e( 'Fraudulent', 'weeconnectpay' ); ?></option>
								</select>
								<div class="wcp-refund-actions">
									<button type="button" class="wcp-btn wcp-btn-safe wcp-cancel-btn"><?php esc_html_e( 'Cancel', 'weeconnectpay' ); ?></button>
									<button type="button" class="wcp-btn wcp-btn-confirm wcp-action-btn">
										<?php
										// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static SVG markup
										echo $svg_refund;
										?>
										<?php
										/* translators: %s: dollar amount */
										printf( esc_html__( 'Refund $%s', 'weeconnectpay' ), esc_html( $remaining_display ) );
										?>
									</button>
								</div>

								<!-- Confirmation bar -->
								<div class="wcp-confirm-bar">
									<?php
									// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static SVG markup
									echo $svg_warning;
									?>
									<div class="wcp-confirm-bar-text"></div>
									<div class="wcp-confirm-bar-actions">
										<button type="button" class="wcp-btn wcp-btn-goback wcp-goback-btn"><?php esc_html_e( 'Go Back', 'weeconnectpay' ); ?></button>
										<button type="button" class="wcp-btn wcp-btn-confirm-final wcp-final-btn"></button>
									</div>
								</div>

								<!-- Error bar -->
								<div class="wcp-error-bar">
									<?php
									// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static SVG markup
									echo $svg_error;
									?>
									<div class="wcp-error-bar-text"></div>
									<div class="wcp-error-bar-actions">
										<button type="button" class="wcp-btn wcp-btn-dismiss wcp-dismiss-btn"><?php esc_html_e( 'Dismiss', 'weeconnectpay' ); ?></button>
									</div>
								</div>
							</div>
							<?php else : ?>
							<!-- Left: Void info panel -->
							<div class="wcp-void-info">
								<div class="wcp-void-info-title"><?php esc_html_e( 'Pre-Authorization Void', 'weeconnectpay' ); ?></div>
								<div class="wcp-void-info-amount">$<?php echo esc_html( $display_amount ); ?></div>
								<div class="wcp-void-info-desc">
									<?php
									printf(
										/* translators: %1$s: dollar amount, %2$s: currency code */
										esc_html__( 'Voiding this pre-authorization will release the held funds back to the cardholder. The full amount of %1$s %2$s will be released.', 'weeconnectpay' ),
										'<strong>$' . esc_html( $display_amount ) . '</strong>',
										esc_html( $currency )
									);
									?>
								</div>
								<div style="display:flex; gap:12px; flex-wrap:wrap; margin-top:4px;">
									<div class="wcp-breakdown-legend-item">
										<span class="wcp-breakdown-legend-dot" style="background:#dc3545;"></span>
										<span><?php esc_html_e( 'Held:', 'weeconnectpay' ); ?></span>
										<span class="wcp-breakdown-legend-amount">$<?php echo esc_html( $display_amount ); ?></span>
									</div>
									<div class="wcp-breakdown-legend-item">
										<span class="wcp-breakdown-legend-dot" style="background:#e5e5e5;"></span>
										<span><?php esc_html_e( 'After void:', 'weeconnectpay' ); ?></span>
										<span class="wcp-breakdown-legend-amount">$0.00</span>
									</div>
								</div>
							</div>

							<!-- Right: Void form -->
							<div class="wcp-refund-form">
								<label class="wcp-label"><?php esc_html_e( 'Reason for Void', 'weeconnectpay' ); ?></label>
								<select class="wcp-reason-select">
									<option value=""><?php esc_html_e( 'Select a reason...', 'weeconnectpay' ); ?></option>
									<option value="requested_by_customer"><?php esc_html_e( 'Requested by customer', 'weeconnectpay' ); ?></option>
									<option value="duplicate"><?php esc_html_e( 'Duplicate authorization', 'weeconnectpay' ); ?></option>
									<option value="fraudulent"><?php esc_html_e( 'Fraudulent', 'weeconnectpay' ); ?></option>
								</select>
								<div class="wcp-refund-actions">
									<button type="button" class="wcp-btn wcp-btn-safe wcp-cancel-btn"><?php esc_html_e( 'Cancel', 'weeconnectpay' ); ?></button>
									<button type="button" class="wcp-btn wcp-btn-confirm wcp-action-btn">
										<?php
										// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static SVG markup
										echo $svg_void;
										?>
										<?php
										/* translators: %s: dollar amount */
										printf( esc_html__( 'Void $%s', 'weeconnectpay' ), esc_html( $display_amount ) );
										?>
									</button>
								</div>

								<!-- Confirmation bar -->
								<div class="wcp-confirm-bar">
									<?php
									// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static SVG markup
									echo $svg_warning;
									?>
									<div class="wcp-confirm-bar-text"></div>
									<div class="wcp-confirm-bar-actions">
										<button type="button" class="wcp-btn wcp-btn-goback wcp-goback-btn"><?php esc_html_e( 'Go Back', 'weeconnectpay' ); ?></button>
										<button type="button" class="wcp-btn wcp-btn-confirm-final wcp-final-btn"></button>
									</div>
								</div>

								<!-- Error bar -->
								<div class="wcp-error-bar">
									<?php
									// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static SVG markup
									echo $svg_error;
									?>
									<div class="wcp-error-bar-text"></div>
									<div class="wcp-error-bar-actions">
										<button type="button" class="wcp-btn wcp-btn-dismiss wcp-dismiss-btn"><?php esc_html_e( 'Dismiss', 'weeconnectpay' ); ?></button>
									</div>
								</div>
							</div>
							<?php endif; ?>
						</div>
					</div>
					<?php endif; ?>
				</div>
				<?php endif; ?>
			</div>
		<?php endforeach; ?>
		</div>

		<script type="text/javascript">
		jQuery(document).ready(function($) {
			if (typeof ajaxurl === 'undefined') {
				var ajaxurl = '<?php echo admin_url( 'admin-ajax.php' ); ?>';
			}

			var i18n = {
				successVoid: '<?php echo esc_js( __( 'Charge voided successfully.', 'weeconnectpay' ) ); ?>',
				successRefund: '<?php echo esc_js( __( 'Charge refunded successfully.', 'weeconnectpay' ) ); ?>',
				failedVoid: '<?php echo esc_js( __( 'Void failed:', 'weeconnectpay' ) ); ?>',
				failedRefund: '<?php echo esc_js( __( 'Refund failed:', 'weeconnectpay' ) ); ?>',
				errorVoid: '<?php echo esc_js( __( 'An error occurred while processing the void.', 'weeconnectpay' ) ); ?>',
				errorRefund: '<?php echo esc_js( __( 'An error occurred while processing the refund.', 'weeconnectpay' ) ); ?>',
				confirmRefundText: '<?php echo esc_js( __( 'This will refund', 'weeconnectpay' ) ); ?>',
				confirmVoidText: '<?php echo esc_js( __( 'This will void the', 'weeconnectpay' ) ); ?>',
				toCard: '<?php echo esc_js( __( 'to', 'weeconnectpay' ) ); ?>',
				preAuthOn: '<?php echo esc_js( __( 'pre-authorization on', 'weeconnectpay' ) ); ?>',
				ending: '<?php echo esc_js( __( 'ending', 'weeconnectpay' ) ); ?>',
				cannotBeUndone: '<?php echo esc_js( __( 'This cannot be undone.', 'weeconnectpay' ) ); ?>',
				yesRefund: '<?php echo esc_js( __( 'Yes, Refund', 'weeconnectpay' ) ); ?>',
				yesVoid: '<?php echo esc_js( __( 'Yes, Void', 'weeconnectpay' ) ); ?>',
				processing: '<?php echo esc_js( __( 'Processing...', 'weeconnectpay' ) ); ?>',
				selectReason: '<?php echo esc_js( __( 'Please select a reason.', 'weeconnectpay' ) ); ?>',
				enterAmount: '<?php echo esc_js( __( 'Please enter a valid refund amount.', 'weeconnectpay' ) ); ?>',
				amountExceeds: '<?php echo esc_js( __( 'Refund amount cannot exceed the remaining charge amount.', 'weeconnectpay' ) ); ?>',
				refundLabel: '<?php echo esc_js( __( 'Refund', 'weeconnectpay' ) ); ?>',
				refundMoreLabel: '<?php echo esc_js( __( 'Refund More', 'weeconnectpay' ) ); ?>',
				voidLabel: '<?php echo esc_js( __( 'Void', 'weeconnectpay' ) ); ?>',
				justNow: '<?php echo esc_js( __( 'Just now', 'weeconnectpay' ) ); ?>',
				refundEvent: '<?php echo esc_js( __( 'Refund', 'weeconnectpay' ) ); ?>',
				voidedEvent: '<?php echo esc_js( __( 'Voided', 'weeconnectpay' ) ); ?>',
				remaining: '<?php echo esc_js( __( 'Remaining:', 'weeconnectpay' ) ); ?>',
				/* translators: %s: original dollar amount */
				ofAmount: '<?php echo esc_js( __( 'of $%s', 'weeconnectpay' ) ); ?>',
				badgeSuccess: '<?php echo esc_js( __( 'Success', 'weeconnectpay' ) ); ?>',
				badgePartialRefund: '<?php echo esc_js( __( 'Partial Refund', 'weeconnectpay' ) ); ?>',
				badgeRefunded: '<?php echo esc_js( __( 'Refunded', 'weeconnectpay' ) ); ?>',
				badgeVoided: '<?php echo esc_js( __( 'Voided', 'weeconnectpay' ) ); ?>',
				reasonRequestedByCustomer: '<?php echo esc_js( __( 'Requested by customer', 'weeconnectpay' ) ); ?>',
				reasonDuplicate: '<?php echo esc_js( __( 'Duplicate', 'weeconnectpay' ) ); ?>',
				reasonFraudulent: '<?php echo esc_js( __( 'Fraudulent', 'weeconnectpay' ) ); ?>',
				capturePartialError: '<?php echo esc_js( __( 'This partial refund was declined because the transaction has not been closed out yet. Partial refunds on captured pre-authorizations are only available after the daily batch closeout. You can try a full refund, or retry after the batch has closed.', 'weeconnectpay' ) ); ?>',
				orderPartialError: '<?php echo esc_js( __( 'Partial refunds are not supported for order-based charges. Only a full refund of the entire charge amount can be processed.', 'weeconnectpay' ) ); ?>'
			};

			var reasonLabels = {
				'requested_by_customer': i18n.reasonRequestedByCustomer,
				'duplicate': i18n.reasonDuplicate,
				'fraudulent': i18n.reasonFraudulent,
				'wc_refund': '<?php echo esc_js( __( 'WooCommerce Refund', 'weeconnectpay' ) ); ?>',
				'wc_return': '<?php echo esc_js( __( 'WooCommerce Return', 'weeconnectpay' ) ); ?>'
			};

			var badgeLabels = {
				'success': i18n.badgeSuccess,
				'partially_refunded': i18n.badgePartialRefund,
				'refunded': i18n.badgeRefunded,
				'voided': i18n.badgeVoided
			};

			var $cards = $('.wcp-cards');

			function formatCents(cents) {
				return (cents / 100).toFixed(2);
			}

			function resetCardForm($card) {
				var totalCents = parseInt($card.data('amount-cents'), 10);
				var refundedCents = parseInt($card.data('refunded-cents'), 10) || 0;
				var remainingCents = parseInt($card.data('remaining-cents'), 10) || totalCents;
				var remainingDisplay = formatCents(remainingCents);
				$card.find('.wcp-amount-input').val(remainingDisplay);
				$card.find('.wcp-quick-btn').removeClass('active');
				$card.find('.wcp-quick-btn[data-preset="100"]').addClass('active');
				updateBreakdown($card, remainingCents, totalCents, refundedCents);
				$card.find('.wcp-reason-select').val('');
				$card.find('.wcp-confirm-bar').removeClass('wcp-confirm-bar-visible');
				$card.find('.wcp-error-bar').removeClass('wcp-error-bar-visible');
				$card.find('.wcp-action-btn').prop('disabled', false);
				$card.find('.wcp-capture-partial-notice').hide();
				$card.find('.wcp-order-partial-notice').hide();
				updateActionButtonLabel($card);
			}

			function showErrorBar($card, message) {
				$card.find('.wcp-confirm-bar').removeClass('wcp-confirm-bar-visible');
				$card.find('.wcp-error-bar-text').text(message);
				$card.find('.wcp-error-bar').addClass('wcp-error-bar-visible');
				$card.find('.wcp-btn').prop('disabled', false);
				$card.find('.wcp-reason-select').prop('disabled', false);
				$card.find('.wcp-amount-input').prop('disabled', false);
			}

			function hideErrorBar($card) {
				$card.find('.wcp-error-bar').removeClass('wcp-error-bar-visible');
				$card.find('.wcp-action-btn').prop('disabled', false);
			}

			function updateCaptureNotice($card) {
				var $notice = $card.find('.wcp-capture-partial-notice');
				if ($notice.length === 0) return;
				var remainingCents = parseInt($card.data('remaining-cents'), 10) || parseInt($card.data('amount-cents'), 10);
				var val = parseFloat($card.find('.wcp-amount-input').val());
				var refundCents = isNaN(val) ? 0 : Math.round(val * 100);
				if (refundCents > 0 && refundCents < remainingCents) {
					$notice.slideDown(150);
				} else {
					$notice.slideUp(150);
				}
			}

			function updateOrderNotice($card) {
				var $notice = $card.find('.wcp-order-partial-notice');
				if ($notice.length === 0) return;
				var remainingCents = parseInt($card.data('remaining-cents'), 10) || parseInt($card.data('amount-cents'), 10);
				var val = parseFloat($card.find('.wcp-amount-input').val());
				var refundCents = isNaN(val) ? 0 : Math.round(val * 100);
				if (refundCents > 0 && refundCents < remainingCents) {
					$notice.slideDown(150);
				} else {
					$notice.slideUp(150);
				}
			}

			function updateBreakdown($card, refundCents, totalCents, alreadyRefundedCents) {
				alreadyRefundedCents = alreadyRefundedCents || 0;
				var remainCents = totalCents - alreadyRefundedCents - refundCents;
				if (remainCents < 0) remainCents = 0;

				var alreadyPct = totalCents > 0 ? (alreadyRefundedCents / totalCents * 100) : 0;
				var refundPct = totalCents > 0 ? (refundCents / totalCents * 100) : 0;
				var remainPct = 100 - alreadyPct - refundPct;
				if (remainPct < 0) remainPct = 0;

				var $bar = $card.find('.wcp-breakdown-bar');
				var $alrSeg = $bar.find('.wcp-breakdown-already-refunded');
				var $refSeg = $bar.find('.wcp-breakdown-refunded');
				var $remSeg = $bar.find('.wcp-breakdown-original');

				$alrSeg.css('width', alreadyPct + '%');
				$alrSeg.text(alreadyPct >= 15 ? '$' + formatCents(alreadyRefundedCents) : '');

				$refSeg.css('width', refundPct + '%');
				$refSeg.text(refundPct >= 15 ? '$' + formatCents(refundCents) : '');

				$remSeg.css('width', remainPct + '%');
				$remSeg.text(remainPct >= 15 ? '$' + formatCents(remainCents) : '');

				$card.find('.wcp-legend-refunding').text('$' + formatCents(refundCents));
				$card.find('.wcp-legend-remaining').text('$' + formatCents(remainCents));
			}

			function updateActionButtonLabel($card) {
				var isPreauth = $card.data('is-preauth') === 1 || $card.data('is-preauth') === '1';
				var $btn = $card.find('.wcp-action-btn');
				var amountText;

				if (isPreauth) {
					amountText = '$' + $card.data('amount-display');
					$btn.html($btn.find('svg').prop('outerHTML') + ' ' + i18n.voidLabel + ' ' + amountText);
				} else {
					var $input = $card.find('.wcp-amount-input');
					var val = parseFloat($input.val());
					amountText = '$' + (isNaN(val) ? '0.00' : val.toFixed(2));
					$btn.html($btn.find('svg').prop('outerHTML') + ' ' + i18n.refundLabel + ' ' + amountText);
				}
			}

			// 1. Clickable card header — toggle body visibility
			$cards.on('click', '.wcp-card-header-clickable', function(e) {
				e.preventDefault();
				var $card = $(this).closest('.wcp-card');
				var $body = $card.find('.wcp-card-body');
				var $chevron = $(this).find('.wcp-chevron');

				if ($card.hasClass('wcp-card-open')) {
					$body.slideUp(200);
					$card.removeClass('wcp-card-open');
					$chevron.removeClass('wcp-chevron-open');
				} else {
					$body.slideDown(250);
					$card.addClass('wcp-card-open');
					$chevron.addClass('wcp-chevron-open');
				}
			});

			// 2. Open refund form button (inside body)
			$cards.on('click', '.wcp-open-refund-btn', function(e) {
				e.preventDefault();
				var $card = $(this).closest('.wcp-card');
				resetCardForm($card);
				$card.find('.wcp-refund-trigger-row').slideUp(150, function() {
					$card.find('.wcp-card-refund').slideDown(250);
				});
				$card.addClass('wcp-card-expanded');
			});

			// 3. Cancel button — collapse refund form only (keep body open)
			$cards.on('click', '.wcp-cancel-btn', function(e) {
				e.preventDefault();
				var $card = $(this).closest('.wcp-card');
				$card.find('.wcp-card-refund').slideUp(200, function() {
					$card.find('.wcp-refund-trigger-row').slideDown(150);
				});
				$card.removeClass('wcp-card-expanded');
				$card.find('.wcp-confirm-bar').removeClass('wcp-confirm-bar-visible');
				$card.find('.wcp-error-bar').removeClass('wcp-error-bar-visible');
			});

			// 4. Quick preset buttons
			$cards.on('click', '.wcp-quick-btn', function(e) {
				e.preventDefault();
				var $card = $(this).closest('.wcp-card');
				var preset = $(this).data('preset');
				var totalCents = parseInt($card.data('amount-cents'), 10);
				var refundedCents = parseInt($card.data('refunded-cents'), 10) || 0;
				var remainingCents = parseInt($card.data('remaining-cents'), 10) || totalCents;

				$card.find('.wcp-quick-btn').removeClass('active');
				$(this).addClass('active');

				if (preset === 'custom') {
					$card.find('.wcp-amount-input').focus().select();
					return;
				}

				var pct = parseInt(preset, 10);
				var refundCents;
				if (pct === 100) {
					refundCents = remainingCents;
				} else {
					refundCents = Math.min(Math.round(remainingCents * pct / 100), remainingCents);
				}
				var refundDisplay = formatCents(refundCents);
				$card.find('.wcp-amount-input').val(refundDisplay);
				updateBreakdown($card, refundCents, totalCents, refundedCents);
				updateActionButtonLabel($card);
				updateCaptureNotice($card);
				updateOrderNotice($card);
				$card.find('.wcp-confirm-bar').removeClass('wcp-confirm-bar-visible');
				$card.find('.wcp-action-btn').prop('disabled', false);
			});

			// 5. Amount input change
			$cards.on('input', '.wcp-amount-input', function() {
				var $card = $(this).closest('.wcp-card');
				var totalCents = parseInt($card.data('amount-cents'), 10);
				var refundedCents = parseInt($card.data('refunded-cents'), 10) || 0;
				var remainingCents = parseInt($card.data('remaining-cents'), 10) || totalCents;
				var val = parseFloat($(this).val());
				if (isNaN(val) || val < 0) val = 0;
				if (val > remainingCents / 100) val = remainingCents / 100;
				var refundCents = Math.round(val * 100);

				updateBreakdown($card, refundCents, totalCents, refundedCents);

				// Auto-detect matching preset
				$card.find('.wcp-quick-btn').removeClass('active');
				var pctMatch = remainingCents > 0 ? Math.round(refundCents / remainingCents * 100) : 0;
				var matched = false;
				$card.find('.wcp-quick-btn').each(function() {
					var p = $(this).data('preset');
					if (p !== 'custom' && parseInt(p, 10) === pctMatch) {
						$(this).addClass('active');
						matched = true;
					}
				});
				if (!matched && refundCents === remainingCents) {
					$card.find('.wcp-quick-btn[data-preset="100"]').addClass('active');
					matched = true;
				}
				if (!matched) {
					$card.find('.wcp-quick-btn[data-preset="custom"]').addClass('active');
				}

				updateActionButtonLabel($card);
				updateCaptureNotice($card);
				updateOrderNotice($card);
				$card.find('.wcp-confirm-bar').removeClass('wcp-confirm-bar-visible');
				$card.find('.wcp-action-btn').prop('disabled', false);
			});

			// 6. Action button — validate and show confirmation bar
			$cards.on('click', '.wcp-action-btn', function(e) {
				e.preventDefault();
				var $card = $(this).closest('.wcp-card');
				var isPreauth = $card.data('is-preauth') === 1 || $card.data('is-preauth') === '1';
				var totalCents = parseInt($card.data('amount-cents'), 10);
				var reason = $card.find('.wcp-reason-select').val();

				if (!reason) {
					showErrorBar($card, i18n.selectReason);
					return;
				}

				var refundCents;
				if (isPreauth) {
					refundCents = totalCents;
				} else {
					var remainingCents = parseInt($card.data('remaining-cents'), 10) || totalCents;
					var val = parseFloat($card.find('.wcp-amount-input').val());
					if (isNaN(val) || val <= 0) {
						showErrorBar($card, i18n.enterAmount);
						return;
					}
					refundCents = Math.round(val * 100);
					if (refundCents > remainingCents) {
						showErrorBar($card, i18n.amountExceeds);
						return;
					}
				}

				// Build confirmation text
				var amountStr = '$' + formatCents(refundCents);
				var cardType = $card.data('card-type');
				var last4 = $card.data('card-last4');
				var cardStr = cardType + ' ' + i18n.ending + ' ' + last4;
				var confirmText;

				if (isPreauth) {
					confirmText = i18n.confirmVoidText + ' <strong>' + amountStr + '</strong> ' + i18n.preAuthOn + ' <strong>' + cardStr + '</strong>. ' + i18n.cannotBeUndone;
				} else {
					confirmText = i18n.confirmRefundText + ' <strong>' + amountStr + '</strong> ' + i18n.toCard + ' <strong>' + cardStr + '</strong>. ' + i18n.cannotBeUndone;
				}

				$card.find('.wcp-error-bar').removeClass('wcp-error-bar-visible');
				var $bar = $card.find('.wcp-confirm-bar');
				$bar.find('.wcp-confirm-bar-text').html(confirmText);
				var finalLabel = isPreauth ? i18n.yesVoid + ' ' + amountStr : i18n.yesRefund + ' ' + amountStr;
				$bar.find('.wcp-final-btn').text(finalLabel).data('refund-cents', refundCents);
				$bar.addClass('wcp-confirm-bar-visible');
				$(this).prop('disabled', true);
			});

			// 7. Go Back button — hide confirmation bar
			$cards.on('click', '.wcp-goback-btn', function(e) {
				e.preventDefault();
				var $card = $(this).closest('.wcp-card');
				$card.find('.wcp-confirm-bar').removeClass('wcp-confirm-bar-visible');
				$card.find('.wcp-action-btn').prop('disabled', false);
			});

			// 8. Dismiss button — hide error bar
			$cards.on('click', '.wcp-dismiss-btn', function(e) {
				e.preventDefault();
				hideErrorBar($(this).closest('.wcp-card'));
			});

			// 9. Final confirm button — fire AJAX
			$cards.on('click', '.wcp-final-btn', function(e) {
				e.preventDefault();
				var $card = $(this).closest('.wcp-card');
				var refundCents = $(this).data('refund-cents');
				var isPreauth = $card.data('is-preauth') === 1 || $card.data('is-preauth') === '1';
				var reason = $card.find('.wcp-reason-select').val();

				// Disable all buttons
				$card.find('.wcp-btn').prop('disabled', true);
				$(this).text(i18n.processing);

				var failedMsg = isPreauth ? i18n.failedVoid : i18n.failedRefund;
				var errorMsg = isPreauth ? i18n.errorVoid : i18n.errorRefund;

				$.ajax({
					url: ajaxurl,
					type: 'POST',
					dataType: 'json',
					data: {
						action: 'weeconnectpay_refund_charge',
						order_id: $card.data('order-id'),
						charge_id: $card.data('charge-id'),
						charge_type: $card.data('charge-type'),
						refund_amount: refundCents,
						reason: reason,
						nonce: $card.data('nonce')
					},
					success: function(response) {
						if (response.success) {
							var data = response.data;
							var newStatus = data.new_status;
							var newRemainingCents = data.charge_remaining_cents;
							var newRefundedCents = data.charge_total_refunded_cents;
							var totalCents = parseInt($card.data('amount-cents'), 10);
							var currency = $card.data('currency');

							// Update card data attributes
							$card.data('status', newStatus);
							$card.data('refunded-cents', newRefundedCents);
							$card.data('remaining-cents', newRemainingCents);

							// Update badge
							var badgeLabel = badgeLabels[newStatus] || newStatus;
							$card.find('.wcp-badge')
								.removeClass('wcp-badge-success wcp-badge-partially_refunded wcp-badge-refunded wcp-badge-voided')
								.addClass('wcp-badge-' + newStatus)
								.text(badgeLabel);

							// Append new timeline event
							var $events = $card.find('.wcp-timeline-events');
							if ($events.length === 0) {
								// Create timeline section if it doesn't exist yet
								var timelineHtml = '<div class="wcp-timeline">' +
									'<div class="wcp-timeline-title">' + '<?php echo esc_js( __( 'Transaction History', 'weeconnectpay' ) ); ?>' + '</div>' +
									'<div class="wcp-timeline-events">' +
									'<div class="wcp-timeline-event">' +
									'<div class="wcp-timeline-dot wcp-timeline-dot-success"></div>' +
									'<div class="wcp-timeline-content">' +
									'<div class="wcp-timeline-event-label">' + '<?php echo esc_js( __( 'Charge', 'weeconnectpay' ) ); ?>' + '</div>' +
									'<div class="wcp-timeline-event-amount">$' + $card.data('amount-display') + ' ' + currency + '</div>' +
									'</div></div></div></div>';
								$card.find('.wcp-card-body').prepend(timelineHtml);
								$events = $card.find('.wcp-timeline-events');
							}

							var dotClass = isPreauth ? 'wcp-timeline-dot-void' : 'wcp-timeline-dot-refund';
							var eventLabel = isPreauth ? i18n.voidedEvent : i18n.refundEvent;
							var reasonLabel = reasonLabels[reason] || reason;
							var eventHtml = '<div class="wcp-timeline-event wcp-timeline-event-new">' +
								'<div class="wcp-timeline-dot ' + dotClass + '"></div>' +
								'<div class="wcp-timeline-content">' +
								'<div class="wcp-timeline-event-label">' + eventLabel + '</div>' +
								'<div class="wcp-timeline-event-amount">-$' + formatCents(refundCents) + ' ' + currency + '</div>' +
								'<div class="wcp-timeline-event-reason">' + reasonLabel + '</div>' +
								'<div class="wcp-timeline-event-time">' + i18n.justNow + '</div>' +
								'</div></div>';
							$events.append(eventHtml);

							// Update or create remaining summary
							var $timeline = $card.find('.wcp-timeline');
							var $summary = $timeline.find('.wcp-timeline-summary');
							if (newStatus === 'refunded' || newStatus === 'voided') {
								// Fully refunded/voided
								$summary.remove();
								$card.find('.wcp-card-amount').addClass('wcp-card-amount-muted');
								// Collapse and remove refund form + trigger
								$card.find('.wcp-card-refund').slideUp(200, function() { $(this).remove(); });
								$card.find('.wcp-refund-trigger-row').remove();
								$card.removeClass('wcp-card-expanded');
							} else {
								// Partially refunded — update summary and keep refund button
								if ($summary.length === 0) {
									var summaryHtml = '<div class="wcp-timeline-summary">' +
										'<span class="wcp-timeline-summary-label">' + i18n.remaining + '</span> ' +
										'<span class="wcp-timeline-summary-amount">$' + formatCents(newRemainingCents) + '</span> ' +
										'<span class="wcp-timeline-summary-of">' + i18n.ofAmount.replace('%s', formatCents(totalCents)) + '</span>' +
										'</div>';
									$timeline.append(summaryHtml);
								} else {
									$summary.find('.wcp-timeline-summary-amount').text('$' + formatCents(newRemainingCents));
								}
								// Collapse refund form, restore trigger row
								$card.find('.wcp-card-refund').slideUp(200, function() {
									$card.find('.wcp-refund-trigger-row').slideDown(150);
								});
								$card.removeClass('wcp-card-expanded');
								// Update trigger label to "Refund More"
								var $triggerBtn = $card.find('.wcp-open-refund-btn');
								var triggerSvg = $triggerBtn.find('svg').prop('outerHTML');
								$triggerBtn.html(triggerSvg + ' ' + i18n.refundMoreLabel);
							}

							// Reload after delay for WC order totals sync
							setTimeout(function() {
								window.location.reload();
							}, 2500);
						} else {
							var chargeType = $card.data('charge-type');
							var errorText = response.data || '';
							if (chargeType === 'capture' && errorText.indexOf('pre-authorization') !== -1) {
								showErrorBar($card, i18n.capturePartialError);
							} else if (errorText.indexOf('not supported by this api') !== -1 || errorText.indexOf('full refunds are supported for order') !== -1) {
								showErrorBar($card, i18n.orderPartialError);
							} else {
								showErrorBar($card, failedMsg + ' ' + errorText);
							}
						}
					},
					error: function(jqXHR, textStatus, errorThrown) {
						console.error('AJAX error:', textStatus, errorThrown);
						showErrorBar($card, errorMsg);
					}
				});
			});
		});
		</script>
		<?php
	}

}
