<?php
/**
 * Admin: Payouts Tab
 *
 * @package     AffiliateWP
 * @subpackage  Admin/Payouts
 * @copyright   Copyright (c) 2025, Sandhills Development, LLC
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       2.29.0
 */

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

/**
 * Sets up the Payouts tab.
 *
 * @since 2.29.0
 */
class AffiliateWP_Admin_Payouts_Tab {

	/**
	 * Constructor.
	 *
	 * @since 2.29.0
	 */
	public function __construct() {
		// Include the payment methods registry early.
		require_once AFFILIATEWP_PLUGIN_DIR . 'includes/admin/payouts/class-payment-methods.php';

		// Hook to display our custom content.
		add_action( 'affwp_settings_tab_payouts_content', [ $this, 'render_payouts_content' ] );

		// Add sanitization filter for payouts tab.
		add_filter( 'affwp_settings_payouts_sanitize', [ $this, 'sanitize_payouts_settings' ] );

		/*
		 * The settings getter's `0`-is-valid allow-list for the two failure
		 * toggles is registered unconditionally in payout-functions.php
		 * (affwp_allow_zero_for_failure_toggles), so the opt-out is honored in
		 * cron/Action Scheduler context too, not just on this admin tab. No
		 * admin-only registration needed here.
		 */
	}

	/**
	 * Render the custom Payouts tab content.
	 *
	 * @since 2.29.0
	 *
	 * @return void
	 */
	public function render_payouts_content() {
		// Register all payment methods (idempotent - safe to call multiple times).
		$this->register_payment_methods();

		// Include our custom view.
		include AFFILIATEWP_PLUGIN_DIR . 'includes/admin/payouts/views/payouts-tab.php';
	}

	/**
	 * Register all payment methods.
	 *
	 * @since 2.29.0
	 *
	 * @return void
	 */
	private function register_payment_methods() {

		// Stripe Payouts.
		$stripe_status = $this->get_stripe_status();
		$test_mode     = affiliate_wp()->settings->get( 'stripe_test_mode', false );

		$stripe_config = [
			'name'              => __( 'Stripe', 'affiliate-wp' ),
			'description'       => __( 'Pay affiliates directly to their bank accounts via Stripe Connect', 'affiliate-wp' ),
			'icon'              => AFFILIATEWP_PLUGIN_URL . 'includes/payouts/methods/stripe-payouts/assets/icon.svg',
			'status'            => $stripe_status,
			'type'              => 'core',
			'settings_callback' => [ $this, 'render_stripe_settings' ],
		];

		// Add custom status labels.
		if ( 'active' === $stripe_status ) {
			$stripe_config['status_label'] = $test_mode ? __( 'Sandbox Mode', 'affiliate-wp' ) : __( 'Live Mode', 'affiliate-wp' );
		} elseif ( 'setup_required' === $stripe_status ) {
			$stripe_config['status_label'] = $test_mode
				? __( 'Setup Required (Sandbox)', 'affiliate-wp' )
				: __( 'Setup Required (Live)', 'affiliate-wp' );
		}

		AffiliateWP_Payment_Methods::register( 'stripe', $stripe_config );

		// PayPal Payouts.
		$paypal_status    = $this->get_paypal_status();
		$paypal_instance  = \AffiliateWP\Core\Payouts\Methods\PayPalPayouts\AffiliateWP_PayPal_Payouts::instance();
		$paypal_test_mode = $paypal_instance->is_test_mode();

		$paypal_config = [
			'name'              => __( 'PayPal', 'affiliate-wp' ),
			'description'       => __( 'Send instant payments to affiliates via PayPal', 'affiliate-wp' ),
			'icon'              => AFFILIATEWP_PLUGIN_URL . 'includes/payouts/methods/paypal-payouts/assets/icon.svg',
			'status'            => $paypal_status,
			'type'              => 'core',
			'settings_callback' => [ $this, 'render_paypal_settings' ],
			'has_new_settings'  => true,
		];

		// Add custom status labels only when enabled.
		if ( 'active' === $paypal_status ) {
			$paypal_config['status_label'] = $paypal_test_mode ? __( 'Sandbox Mode', 'affiliate-wp' ) : __( 'Live Mode', 'affiliate-wp' );
		} elseif ( 'setup_required' === $paypal_status ) {
			$paypal_config['status_label'] = $paypal_test_mode
				? __( 'Setup Required (Sandbox)', 'affiliate-wp' )
				: __( 'Setup Required (Live)', 'affiliate-wp' );
		}
		// When status is 'available' (disabled but has creds), no custom label - no badge shows.

		AffiliateWP_Payment_Methods::register( 'paypal', $paypal_config );

		// Store Credit - Check if it needs to be registered.
		if ( ! AffiliateWP_Payment_Methods::exists( 'store_credit' ) ) {

			// Store Credit.
			AffiliateWP_Payment_Methods::register(
				'store_credit',
				[
					'name'              => __( 'Store Credit', 'affiliate-wp' ),
					'description'       => __( 'Pay affiliates with store credit for your shop', 'affiliate-wp' ),
					'icon'              => AFFILIATEWP_PLUGIN_URL . 'includes/payouts/methods/store-credit/assets/icon.svg',
					'status'            => $this->get_store_credit_status(),
					'type'              => 'core',
					'settings_callback' => [ $this, 'render_store_credit_settings' ],
				]
			);

		}

		// Payouts Service.
		$payouts_connection_status = affiliate_wp()->settings->get( 'payouts_service_connection_status', '' );
		$payouts_enabled           = affiliate_wp()->settings->get( 'enable_payouts_service', false );

		// Only show the Payouts Service if connected (grandfathering existing users).
		if ( 'active' === $payouts_connection_status ) {

			$is_payouts_sunset_passed = affwp_is_payouts_service_sunset_passed();

			// Determine status based on both connection and enabled state.
			$payouts_config = [
				'name'              => __( 'Payouts Service', 'affiliate-wp' ),
				'description'       => __( 'Pay your affiliates directly from a credit or debit card', 'affiliate-wp' ),
				'icon'              => AFFILIATEWP_PLUGIN_URL . 'includes/payouts/methods/payouts-service/assets/icon.svg',
				'type'              => 'core',
				'settings_callback' => [ $this, 'render_payouts_service_settings' ],
				'has_new_settings'  => true,
			];

			if ( $is_payouts_sunset_passed ) {
				// Post-shutdown: show as retired.
				$payouts_config['status']       = 'retired';
				$payouts_config['status_label'] = __( 'Retired', 'affiliate-wp' );
			} elseif ( $payouts_enabled ) {
				$payouts_config['status']       = 'active';
				$payouts_config['status_label'] = __( 'Connected', 'affiliate-wp' );
			} else {
				$payouts_config['status']       = 'available';
				$payouts_config['status_label'] = __( 'Ready', 'affiliate-wp' );
			}

			/*
			 * Card ID matches the registry ID ('payouts-service'); canonical registration
			 * is in includes/payouts/methods/payouts-service/init.php. Setting/meta/URL keys
			 * keep the underscore form because those are persisted DB/URL contracts.
			 */
			AffiliateWP_Payment_Methods::register( 'payouts-service', $payouts_config );
		}

		/**
		 * Fires after payment methods are registered.
		 *
		 * @since 2.29.0
		 */
		do_action( 'affwp_register_payment_methods' );
	}

	/**
	 * Get payment method status for providers with test/live modes.
	 *
	 * Used for Stripe and PayPal which share the same status logic.
	 *
	 * @since 2.29.0
	 *
	 * @param string $method_key  The method key (e.g., 'stripe_payouts', 'paypal_payouts').
	 * @param array  $credentials Array with 'test' and 'live' boolean values for credentials.
	 * @param bool   $test_mode   Whether test mode is enabled.
	 * @return string|null Status or null if no status should be shown.
	 */
	private function get_payment_provider_status( $method_key, $credentials, $test_mode ) {
		$is_enabled = affiliate_wp()->settings->get( $method_key, false );

		if ( ! $is_enabled ) {
			// Return 'available' so the toggle still shows but no badge is displayed.
			return 'available';
		}

		$has_test_creds = $credentials['test'];
		$has_live_creds = $credentials['live'];

		// Check credentials for current mode.
		$has_required_creds = $test_mode ? $has_test_creds : $has_live_creds;

		if ( $has_required_creds ) {
			return 'active';
		}

		// Enabled but missing credentials.
		return 'setup_required';
	}

	/**
	 * Get Stripe status.
	 *
	 * @since 2.29.0
	 *
	 * @return string|null Status or null if no status should be shown.
	 */
	private function get_stripe_status() {
		$test_mode = affiliate_wp()->settings->get( 'stripe_test_mode', false );

		$credentials = [
			'test' => ! empty( affiliate_wp()->settings->get( 'stripe_test_secret_key' ) ),
			'live' => ! empty( affiliate_wp()->settings->get( 'stripe_live_secret_key' ) ),
		];

		return $this->get_payment_provider_status( 'stripe_payouts', $credentials, $test_mode );
	}

	/**
	 * Get PayPal status.
	 *
	 * @since 2.29.0
	 *
	 * @return string|null Status or null if no status should be shown.
	 */
	private function get_paypal_status() {
		// Initialize PayPal instance to check credentials.
		$paypal_instance = \AffiliateWP\Core\Payouts\Methods\PayPalPayouts\AffiliateWP_PayPal_Payouts::instance();
		$test_mode       = $paypal_instance->is_test_mode();
		$payout_mode     = affiliate_wp()->settings->get( 'paypal_payout_mode', 'masspay' );

		// Determine which credentials we have.
		if ( 'api' == $payout_mode ) {
			$credentials = [
				'test' => ! empty( affiliate_wp()->settings->get( 'paypal_test_client_id' ) )
						&& ! empty( affiliate_wp()->settings->get( 'paypal_test_secret' ) ),
				'live' => ! empty( affiliate_wp()->settings->get( 'paypal_live_client_id' ) )
						&& ! empty( affiliate_wp()->settings->get( 'paypal_live_secret' ) ),
			];
		} else {
			$credentials = [
				'test' => ! empty( affiliate_wp()->settings->get( 'paypal_test_username' ) )
						&& ! empty( affiliate_wp()->settings->get( 'paypal_test_password' ) )
						&& ! empty( affiliate_wp()->settings->get( 'paypal_test_signature' ) ),
				'live' => ! empty( affiliate_wp()->settings->get( 'paypal_live_username' ) )
						&& ! empty( affiliate_wp()->settings->get( 'paypal_live_password' ) )
						&& ! empty( affiliate_wp()->settings->get( 'paypal_live_signature' ) ),
			];
		}

		return $this->get_payment_provider_status( 'paypal_payouts', $credentials, $test_mode );
	}

	/**
	 * Get Store Credit status.
	 *
	 * @since 2.29.0
	 *
	 * @return string Status.
	 */
	private function get_store_credit_status() {
		// Check if plugins are installed.
		$woocommerce_installed = class_exists( 'WooCommerce' );
		$edd_installed         = class_exists( 'Easy_Digital_Downloads' );

		// Check if integrations are enabled.
		$enabled_integrations = array_keys( affiliate_wp()->integrations->get_enabled_integrations() );
		$woocommerce_enabled  = in_array( 'woocommerce', $enabled_integrations );
		$edd_enabled          = in_array( 'edd', $enabled_integrations );

		// Check if we have any integration enabled.
		$has_integration = ( $woocommerce_installed && $woocommerce_enabled ) || ( $edd_installed && $edd_enabled );

		// If plugins are installed but integration not enabled, show "Setup Required".
		if ( ( $woocommerce_installed || $edd_installed ) && ! $has_integration ) {
			return 'setup_required'; // This will show "Setup Required" badge.
		}

		// If no plugins installed at all.
		if ( ! $woocommerce_installed && ! $edd_installed ) {
			return 'available'; // Can be installed.
		}

		// If integration is enabled, check if Store Credit itself is enabled.
		if ( $has_integration ) {
			return affiliate_wp()->settings->get( 'store-credit' ) ? 'active' : 'available';
		}

		return 'available';
	}

	/**
	 * Sanitize payouts settings.
	 *
	 * @since 2.29.0
	 *
	 * @param array $input The input settings array.
	 * @return array The sanitized settings.
	 */
	public function sanitize_payouts_settings( $input ) {
		// Define checkbox fields that need to be sanitized.
		$checkbox_fields = [
			'allow_affiliate_payout_method_selection',
			'store-credit',
			'store-credit-woocommerce-subscriptions',
			'manual_payouts',
			'enable_payouts_service',
			'paypal_payouts',
			'stripe_payouts',
			'stripe_test_mode',
			'stripe_cross_border_payouts',
			'auto_email_payout_failures',
			'auto_retry_failed_payouts',
		];

		// Ensure checkbox values are boolean
		foreach ( $checkbox_fields as $field ) {
			if ( isset( $input[ $field ] ) ) {
				// Convert to boolean: '1' or 1 becomes true, everything else false
				$input[ $field ] = ! empty( $input[ $field ] ) ? 1 : 0;
			} else {
				// If not set, it means checkbox was unchecked
				$input[ $field ] = 0;
			}
		}

		// Cross-border (international) payouts require a US/UK Stripe platform — never persist it
		// enabled for any other platform country, even if the request says so.
		if ( ! empty( $input['stripe_cross_border_payouts'] )
			&& function_exists( 'affwp_stripe_payouts_platform_supports_cross_border' )
			&& ! affwp_stripe_payouts_platform_supports_cross_border()
		) {
			$input['stripe_cross_border_payouts'] = 0;
		}

		// Sanitize Stripe text fields
		$stripe_text_fields = [
			'stripe_test_secret_key',
			'stripe_live_secret_key',
			'stripe_webhook_secret',
		];

		foreach ( $stripe_text_fields as $field ) {
			if ( isset( $input[ $field ] ) ) {
				$input[ $field ] = sanitize_text_field( $input[ $field ] );
			}
		}

		/*
		 * Default Payout Method is a method-key string — sanitize_key is enough since the
		 * options list is controlled server-side by affwp_get_payout_methods_for_display().
		 */
		if ( isset( $input['default_payout_method'] ) ) {
			$input['default_payout_method'] = sanitize_key( $input['default_payout_method'] );
		}

		$this->save_affiliate_selectable_methods( $input );

		return $input;
	}

	/**
	 * Per-method "Offer in affiliate payout picker" toggles.
	 *
	 * Maps each method's selectable key to its method id. These are the methods that
	 * can appear in the affiliate-facing picker (Manual and the Payouts Service are
	 * admin-only and excluded).
	 *
	 * @since 2.33.0
	 *
	 * @return array<string,string> selectable_key => method_id
	 */
	private function affiliate_selectable_toggle_keys() {
		return [
			'stripe_affiliate_selectable'       => 'stripe',
			'paypal_affiliate_selectable'       => 'paypal',
			'store_credit_affiliate_selectable' => 'store_credit',
		];
	}

	/**
	 * Persists the per-method picker-visibility toggles to the canonical
	 * `affwp_hidden_affiliate_payout_methods` option.
	 *
	 * The toggles live outside `affwp_settings` so the affiliate picker
	 * (affwp_get_payout_methods_for_display) and the per-affiliate override read one
	 * source of truth. Toggle ON = offered; OFF/absent = hidden from every affiliate's
	 * picker, while admins can still assign the method via the affiliate's Payout Method
	 * override. The submitted toggle keys are removed from `$input` so they don't
	 * double-store into `affwp_settings`.
	 *
	 * @since 2.33.0
	 *
	 * @param array $input Submitted payouts settings (passed by reference).
	 */
	private function save_affiliate_selectable_methods( array &$input ) {

		/*
		 * Read toggle state from $_POST, not $input: WordPress runs this sanitize filter
		 * more than once per save and feeds each run's output back as $input, so the keys
		 * unset below would read as "unchecked" on later passes. $_POST is stable.
		 *
		 * Each interactive toggle renders a hidden companion (value 0), so an unchecked box
		 * still submits its key. A key that is ENTIRELY ABSENT therefore means the toggle
		 * was not interactive on this form (the locked default, or affiliate choice is off);
		 * in that case leave the method's current visibility untouched rather than silently
		 * hiding a method the admin never saw a working control for.
		 */
		$posted = ( isset( $_POST['affwp_settings'] ) && is_array( $_POST['affwp_settings'] ) )
			? wp_unslash( $_POST['affwp_settings'] )
			: array();

		$hidden = get_option( 'affwp_hidden_affiliate_payout_methods', array() );
		$hidden = is_array( $hidden ) ? $hidden : array();

		$default = isset( $input['default_payout_method'] )
			? $input['default_payout_method']
			: ( function_exists( 'affwp_get_default_payout_method' ) ? affwp_get_default_payout_method() : '' );

		foreach ( $this->affiliate_selectable_toggle_keys() as $toggle_key => $method_key ) {

			// Don't persist the toggle into affwp_settings — the option is canonical.
			unset( $input[ $toggle_key ] );

			// The default method is always available, so it's never hidden.
			if ( $method_key === $default ) {
				$hidden = array_diff( $hidden, array( $method_key ) );
				continue;
			}

			// Not submitted means the toggle was disabled; leave current visibility as-is.
			if ( ! array_key_exists( $toggle_key, $posted ) ) {
				continue;
			}

			if ( ! empty( $posted[ $toggle_key ] ) ) {
				$hidden = array_diff( $hidden, array( $method_key ) );
			} elseif ( ! in_array( $method_key, $hidden, true ) ) {
				$hidden[] = $method_key;
			}
		}

		update_option( 'affwp_hidden_affiliate_payout_methods', array_values( $hidden ) );
	}

	/**
	 * Render Manual Payouts settings.
	 *
	 * @since 2.29.0
	 *
	 * @return void
	 */
	public function render_manual_settings() {
		?>
		<div class="overflow-hidden">
			<div class="space-y-4">
			<!-- Hidden field to handle the checkbox value from toggle switch -->
			<input type="hidden"
					id="affwp_settings_manual_payouts"
					name="affwp_settings[manual_payouts]"
					value="<?php echo affiliate_wp()->settings->get( 'manual_payouts' ) ? '1' : ''; ?>">

			<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
				<div class="flex">
					<div class="flex-shrink-0">
						<svg class="h-5 w-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
							<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
						</svg>
					</div>
					<div class="ml-3">
						<h3 class="text-sm font-medium text-blue-800">
							<?php esc_html_e( 'Manual Payouts Configuration', 'affiliate-wp' ); ?>
						</h3>
						<p class="mt-2 text-blue-700">
							<?php esc_html_e( 'Allow marking referrals as paid manually. Useful for payments made via bank transfer, check, or other offline methods. Use the toggle switch above to enable or disable this payment method.', 'affiliate-wp' ); ?>
						</p>
					</div>
				</div>
			</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Render Payouts Service settings.
	 *
	 * @since 2.29.0
	 *
	 * @return void
	 */
	public function render_payouts_service_settings() {
		// Get current settings
		$description       = affiliate_wp()->settings->get( 'payouts_service_description', '' );
		$notice            = affiliate_wp()->settings->get( 'payouts_service_notice', '' );
		$connection_status = affiliate_wp()->settings->get( 'payouts_service_connection_status', '' );

		// Non-connected sites: show "no longer accepting new connections" message and return early.
		if ( 'active' !== $connection_status ) {
			$is_past_sunset = affwp_is_payouts_service_sunset_passed();
			?>
			<div class="max-w-3xl">
				<div class="rounded-lg border border-gray-200 bg-gray-50 p-8 text-center">
					<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
						<path stroke-linecap="round" stroke-linejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
					</svg>
					<h3 class="mt-4 text-base font-semibold text-gray-900">
						<?php
						if ( $is_past_sunset ) {
							esc_html_e( 'Payouts Service Has Been Shut Down', 'affiliate-wp' );
						} else {
							esc_html_e( 'Payouts Service Unavailable', 'affiliate-wp' );
						}
						?>
					</h3>
					<p class="mt-2 text-sm text-gray-600">
						<?php
						if ( $is_past_sunset ) {
							esc_html_e( 'The Payouts Service was shut down on September 30, 2026. Please configure another payout method above.', 'affiliate-wp' );
						} else {
							esc_html_e( 'The Payouts Service is no longer accepting new connections and will be shut down on September 30, 2026. Please configure another payout method above.', 'affiliate-wp' );
						}
						?>
					</p>
					<p class="mt-3">
						<?php
						affwp_link(
							[
								'text'     => __( 'Learn more', 'affiliate-wp' ),
								'href'     => affwp_utm_link( AFFWP_PAYOUTS_SERVICE_SUNSET_URL, 'payouts-service-sunset', 'disabled-registration' ),
								'external' => true,
								'variant'  => 'primary',
							]
						);
						?>
					</p>
				</div>
			</div>
			<?php
			return;
		}

		// Build connection status with button styling
		$connection_button_html = '';
		if ( 'active' === $connection_status ) {
			$payouts_service_disconnect_url = wp_nonce_url( add_query_arg( [ 'affwp_action' => 'payouts_service_disconnect' ] ), 'payouts_service_disconnect', 'payouts_service_disconnect_nonce' );
			$connection_button_html         = '<div class="space-y-3">';
			/* translators: Payouts Service name retrieved from the PAYOUTS_SERVICE_NAME constant */
			$connection_button_html .= '<p class="text-sm text-green-700 font-medium flex items-center gap-2">';
			$connection_button_html .= '<svg class="w-5 h-5 text-green-600" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path></svg>';
			$connection_button_html .= sprintf( __( 'Your website is connected to the %s', 'affiliate-wp' ), PAYOUTS_SERVICE_NAME );
			$connection_button_html .= '</p>';
			/* translators: Payouts Service name retrieved from the PAYOUTS_SERVICE_NAME constant */
			$connection_button_html .= affwp_render_button(
				[
					'text'    => sprintf( __( 'Disconnect from %s', 'affiliate-wp' ), PAYOUTS_SERVICE_NAME ),
					'variant' => 'secondary',
					'href'    => $payouts_service_disconnect_url,
				]
			);
			$connection_button_html .= '</div>';
		} elseif ( 'inactive' === $connection_status ) {
			$payouts_service_reconnect_url = wp_nonce_url( add_query_arg( [ 'affwp_action' => 'payouts_service_reconnect' ] ), 'payouts_service_reconnect', 'payouts_service_reconnect_nonce' );
			$connection_button_html        = '<div class="space-y-3">';
			$connection_button_html       .= '<p class="text-sm text-orange-700 font-medium flex items-center gap-2">';
			$connection_button_html       .= '<svg class="w-5 h-5 text-orange-600" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path></svg>';
			$connection_button_html       .= __( 'Connection inactive', 'affiliate-wp' );
			$connection_button_html       .= '</p>';
			/* translators: Payouts Service name retrieved from the PAYOUTS_SERVICE_NAME constant */
			$connection_button_html .= affwp_render_button(
				[
					'text'    => sprintf( __( 'Reconnect to %s', 'affiliate-wp' ), PAYOUTS_SERVICE_NAME ),
					'variant' => 'primary',
					'href'    => $payouts_service_reconnect_url,
				]
			);
			/* translators: Payouts Service name retrieved from the PAYOUTS_SERVICE_NAME constant */
			$connection_button_html .= '<p class="text-sm text-gray-500">';
			$connection_button_html .= sprintf( __( 'Have questions about connecting with the %s? See the ', 'affiliate-wp' ), PAYOUTS_SERVICE_NAME );
			$connection_button_html .= affwp_render_link(
				[
					'text'     => __( 'documentation', 'affiliate-wp' ),
					'href'     => PAYOUTS_SERVICE_DOCS_URL,
					'external' => true,
					'variant'  => 'inline',
				]
			);
			$connection_button_html .= '.</p>';
			$connection_button_html .= '</div>';
		} else {
			$payouts_service_connect_url = add_query_arg(
				[
					'affwp_version' => AFFILIATEWP_VERSION,
					'site_url'      => home_url(),
					'redirect_url'  => urlencode( affwp_admin_url( 'settings', [ 'tab' => 'payouts' ] ) ),
					'token'         => str_pad( wp_rand( wp_rand(), PHP_INT_MAX ), 100, wp_rand(), STR_PAD_BOTH ),
				],
				PAYOUTS_SERVICE_URL . '/connect-site'
			);
			$connection_button_html      = '<div class="space-y-3">';
			/* translators: Payouts Service name retrieved from the PAYOUTS_SERVICE_NAME constant */
			$connection_button_html .= affwp_render_button(
				[
					'text'    => sprintf( __( 'Connect to %s', 'affiliate-wp' ), PAYOUTS_SERVICE_NAME ),
					'variant' => 'primary',
					'size'    => 'md',
					'href'    => $payouts_service_connect_url,
				]
			);
			/* translators: Payouts Service name retrieved from the PAYOUTS_SERVICE_NAME constant */
			$connection_button_html .= '<p class="text-sm text-gray-500">';
			$connection_button_html .= sprintf( __( 'Have questions about connecting with the %s? See the ', 'affiliate-wp' ), PAYOUTS_SERVICE_NAME );
			$connection_button_html .= affwp_render_link(
				[
					'text'     => __( 'documentation', 'affiliate-wp' ),
					'href'     => PAYOUTS_SERVICE_DOCS_URL,
					'external' => true,
					'variant'  => 'inline',
				]
			);
			$connection_button_html .= '.</p>';
			$connection_button_html .= '</div>';
		}
		?>
		<div class="max-w-3xl">

			<!-- Header Section -->
			<div class="mb-8">
				<h3 class="text-xl font-semibold text-gray-900 mb-2"><?php _e( 'Payouts Service Configuration', 'affiliate-wp' ); ?></h3>

				<?php
				$is_past_sunset = affwp_is_payouts_service_sunset_passed();

				$ps_heading = $is_past_sunset
					? esc_html__( 'The Payouts Service was shut down on September 30, 2026.', 'affiliate-wp' )
					: esc_html__( 'The Payouts Service is shutting down on September 30, 2026.', 'affiliate-wp' );

				$ps_body_text = $is_past_sunset
					? __( 'Please switch to another payout method to continue paying your affiliates. Your existing data will not be affected.', 'affiliate-wp' )
					: __( 'Please switch to another payout method before then. Your existing data will not be affected.', 'affiliate-wp' );

				$ps_learn_more = sprintf(
					'<a href="%s" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-1 font-medium text-yellow-900 underline decoration-yellow-500 underline-offset-4 hover:decoration-yellow-700">%s<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/></svg></a>',
					esc_url( affwp_utm_link( AFFWP_PAYOUTS_SERVICE_SUNSET_URL, 'payouts-service-sunset', 'config-panel-warning' ) ),
					esc_html__( 'Learn more', 'affiliate-wp' )
				);

				affwp_notice(
					[
						'variant' => 'warning',
						'heading' => $ps_heading,
						'body'    => esc_html( $ps_body_text ) . ' ' . $ps_learn_more,
						'class'   => 'mt-3 mb-4',
					]
				);
				?>
			</div>

			<!-- Main Settings -->
			<div class="bg-white rounded-lg border border-gray-200">

				<!-- Connection Status -->
				<div class="border-b border-gray-200 last:border-b-0">
					<div class="p-6">
						<h4 class="text-base font-medium text-gray-900 mb-3"><?php _e( 'Connection Status', 'affiliate-wp' ); ?></h4>
						<?php echo $connection_button_html; // Already escaped by affwp_render_button() and affwp_render_link(). ?>
					</div>
				</div>

				<!-- Registration Form Description -->
				<div class="border-b border-gray-200 last:border-b-0">
					<div class="p-6">
						<label for="affwp_settings_payouts_service_description" class="block text-base font-medium text-gray-900">
							<?php _e( 'Registration Form Description', 'affiliate-wp' ); ?>
						</label>
						<div class="mt-2">
							<textarea id="affwp_settings_payouts_service_description"
										name="affwp_settings[payouts_service_description]"
										rows="4"
										class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-affwp-brand-500 sm:text-sm/6"
										placeholder="<?php esc_attr_e( 'Explain to affiliates how to register for the Payouts Service', 'affiliate-wp' ); ?>"><?php echo esc_textarea( $description ); ?></textarea>
							<p class="mt-2 text-sm text-gray-500">
								<?php _e( 'This will be displayed above the Payouts Service registration form fields. Here you can explain to your affiliates how/why to register for the Payouts Service.', 'affiliate-wp' ); ?>
							</p>
						</div>
					</div>
				</div>

				<!-- Payouts Service Notice -->
				<div class="border-b border-gray-200 last:border-b-0">
					<div class="p-6">
						<label for="affwp_settings_payouts_service_notice" class="block text-base font-medium text-gray-900">
							<?php _e( 'Payouts Service Notice', 'affiliate-wp' ); ?>
						</label>
						<div class="mt-2">
							<textarea id="affwp_settings_payouts_service_notice"
										name="affwp_settings[payouts_service_notice]"
										rows="4"
										class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-affwp-brand-500 sm:text-sm/6"
										placeholder="<?php esc_attr_e( 'Enter a notice for affiliates who have not registered their payout account', 'affiliate-wp' ); ?>"><?php echo esc_textarea( $notice ); ?></textarea>
							<p class="mt-2 text-sm text-gray-500">
								<?php _e( 'This will be displayed at the top of each tab of the Affiliate Area for affiliates that have not registered their payout account.', 'affiliate-wp' ); ?>
							</p>
						</div>
					</div>
				</div>

			</div>
		</div>
		<?php
	}

	/**
	 * Render Stripe settings.
	 *
	 * @since 2.29.0
	 *
	 * @return void
	 */
	public function render_stripe_settings() {
		// Include the Stripe Payouts settings panel template
		$template_path = AFFILIATEWP_PLUGIN_DIR . 'includes/payouts/methods/stripe-payouts/templates/settings-panel.php';

		if ( file_exists( $template_path ) ) {
			include $template_path;
		} else {
			// Fallback if template doesn't exist
			?>
			<div class="overflow-hidden">
				<div class="space-y-6">
					<?php
					affwp_notice(
						[
							'variant' => 'warning',
							'heading' => esc_html__( 'Configuration Error', 'affiliate-wp' ),
							'body'    => esc_html__( 'The Stripe Payouts settings template could not be loaded. Please check your installation.', 'affiliate-wp' ),
						]
					);
					?>
				</div>
			</div>
			<?php
		}
	}

	/**
	 * Render PayPal settings.
	 *
	 * @since 2.29.0
	 *
	 * @return void
	 */
	public function render_paypal_settings() {
		// Initialize PayPal instance and call its render method.
		$paypal_instance = \AffiliateWP\Core\Payouts\Methods\PayPalPayouts\AffiliateWP_PayPal_Payouts::instance();
		if ( $paypal_instance && method_exists( $paypal_instance, 'render_settings_panel' ) ) {
			$paypal_instance->render_settings_panel();
		}
	}

	/**
	 * Render Store Credit settings.
	 *
	 * @since 2.29.0
	 *
	 * @return void
	 */
	public function render_store_credit_settings() {
		// Include the Store Credit settings panel template
		$template_path = AFFILIATEWP_PLUGIN_DIR . 'includes/payouts/methods/store-credit/templates/settings-panel.php';

		if ( file_exists( $template_path ) ) {
			include $template_path;
		}
	}

	/**
	 * Renders the per-method "Let affiliates choose <Method>" toggle near the top of a
	 * method's Configure panel. Called from each method's settings-panel template (right
	 * after the panel header) so it leads the settings and inherits the panel's width.
	 *
	 * Reflects the canonical `affwp_hidden_affiliate_payout_methods` option: checked =
	 * offered in the picker, unchecked = hidden (admin-assign-only via the affiliate's
	 * Payout Method override). The default payout method is always available, so its
	 * toggle is locked on. Saved by save_affiliate_selectable_methods().
	 *
	 * @since 2.33.0
	 *
	 * @param string $method_key Method id (stripe, paypal, store_credit).
	 */
	public static function render_affiliate_selectable_field( $method_key ) {

		if ( ! function_exists( 'affwp_get_default_payout_method' ) ) {
			return;
		}

		$hidden       = get_option( 'affwp_hidden_affiliate_payout_methods', array() );
		$hidden       = is_array( $hidden ) ? $hidden : array();
		$is_default   = ( affwp_get_default_payout_method() === $method_key );
		$allow_choose = function_exists( 'affwp_affiliates_can_select_payout_method' )
			? affwp_affiliates_can_select_payout_method()
			: false;

		// Default method is always offered (toggle locked on); the toggle is also inert when affiliates can't choose their own method.
		$offered  = $is_default ? true : ! in_array( $method_key, $hidden, true );
		$disabled = $is_default || ! $allow_choose;

		$method_label = function_exists( 'affwp_get_payout_method_display_label' )
			? affwp_get_payout_method_display_label( $method_key )
			: ucfirst( str_replace( array( '_', '-' ), ' ', $method_key ) );

		// Row-level click-to-toggle, a mouse convenience matching the other payout
		// toggle rows. Skipped when the toggle is locked, and guarded so selecting
		// text in the row doesn't flip it.
		$row_class       = 'flex gap-6 justify-between items-center p-5';
		$card_xdata      = '';
		$row_click       = '';
		$toggle_wrapper  = '';
		$selectable_args = array(
			'name'     => 'affwp_settings[' . $method_key . '_affiliate_selectable]',
			/* translators: %s: Payout method name (e.g. Store Credit, PayPal). */
			'label'    => sprintf( __( 'Let affiliates choose %s', 'affiliate-wp' ), $method_label ),
			'checked'  => $offered,
			'disabled' => $disabled,
			'size'     => 'md',
			'color'    => 'blue',
		);

		if ( ! $disabled ) {
			$row_class                      .= ' cursor-pointer';
			$card_xdata                      = sprintf( ' x-data="{ methodSelectable: %s }"', $offered ? 'true' : 'false' );
			$row_click                       = ' @click="if ( ! window.getSelection().isCollapsed ) return; methodSelectable = !methodSelectable; $refs.toggle_method_selectable.querySelector(\'input[type=checkbox]\').click()"';
			$toggle_wrapper                  = ' @click.stop x-ref="toggle_method_selectable"';
			$selectable_args['alpine_model'] = 'methodSelectable';
		}
		?>
		<div class="mb-6 overflow-hidden bg-white rounded-lg border border-gray-200"<?php echo $card_xdata; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static Alpine markup; only value is a literal boolean. ?>>
			<div class="<?php echo esc_attr( $row_class ); ?>"<?php echo $row_click; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static Alpine handler; escaping would corrupt the directive. ?>>
				<div class="flex-1 min-w-0">
					<span class="block mb-1 text-base font-medium text-gray-900">
						<?php
						/* translators: %s: Payout method name (e.g. Store Credit, PayPal). */
						echo esc_html( sprintf( __( 'Let affiliates choose %s', 'affiliate-wp' ), $method_label ) );
						?>
					</span>
					<p class="text-sm text-gray-600">
						<?php
						if ( $is_default ) {
							esc_html_e( 'Always available because this is the default payout method.', 'affiliate-wp' );
						} elseif ( ! $allow_choose ) {
							printf(
								/* translators: %s: Payout method name (e.g. Store Credit, PayPal). */
								esc_html__( "Affiliates can't choose their own payout method right now, so this has no effect. Turn on 'Allow affiliates to choose their payout method' above to use it. You can still assign %s to specific affiliates on the Edit Affiliate screen.", 'affiliate-wp' ),
								esc_html( $method_label )
							);
						} else {
							printf(
								/* translators: %s: Payout method name (e.g. Store Credit, PayPal). */
								esc_html__( "%s appears in each affiliate's payout method options. When off, it's hidden there, but you can still assign it to specific affiliates on the Edit Affiliate screen.", 'affiliate-wp' ),
								esc_html( $method_label )
							);
						}
						?>
					</p>
				</div>
				<div class="shrink-0"<?php echo $toggle_wrapper; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static Alpine attributes; escaping would corrupt the directives. ?>>
					<?php
					/*
					 * Hidden companion so an unchecked interactive toggle still submits a value,
					 * letting the save handler tell "unchecked" apart from "not rendered". The
					 * checkbox below shares the name and overrides it with 1 when checked.
					 */
					if ( ! $disabled ) {
						printf(
							'<input type="hidden" name="affwp_settings[%s_affiliate_selectable]" value="0" />',
							esc_attr( $method_key )
						);
					}

					affwp_toggle( $selectable_args );
					?>
				</div>
			</div>
		</div>
		<?php
	}
}

// Initialize the class.
new AffiliateWP_Admin_Payouts_Tab();
