<?php
/**
 * Affiliate Area — Payout Method field
 *
 * Renders a dropdown that lets an affiliate choose how they get paid. Replaces the
 * legacy Store Credit opt-in checkbox with a proper per-affiliate method selector.
 *
 * @package     AffiliateWP
 * @subpackage  AffiliateDashboard
 * @since       2.33.0
 */

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

/**
 * Renders the Payout Method field above the Affiliate Area settings submit button.
 *
 * @since 2.33.0
 *
 * @param int $affiliate_id Affiliate ID.
 * @param int $user_id      User ID for this affiliate.
 */
function affwp_affiliate_area_render_payout_method_field( $affiliate_id, $user_id ) {

	/**
	 * Filters whether to show the affiliate-area payout method dropdown.
	 *
	 * Returning false here lets integrations hide the field (e.g. when a store wants
	 * to keep method assignment admin-only).
	 *
	 * @since 2.33.0
	 *
	 * @param bool $show         Whether to render the dropdown. Default true.
	 * @param int  $affiliate_id Affiliate ID.
	 */
	if ( false === apply_filters( 'affwp_show_affiliate_area_payout_method_field', true, $affiliate_id ) ) {
		return;
	}

	$picker = affwp_get_affiliate_picker_options( $affiliate_id );

	if ( ! $picker['should_render'] ) {
		return;
	}

	$current_value = $picker['current_value'];
	$is_locked     = ! empty( $picker['is_locked_by_admin'] );

	// Admin override active: show the resolved method as a read-only line rather than a disabled control.
	if ( $is_locked ) {
		$locked_label = isset( $picker['options'][ $current_value ] )
			? $picker['options'][ $current_value ]
			: $current_value;
		?>
		<div class="affwp-wrap affwp-payout-method-wrap">
			<p class="affwp-payout-method-static"><?php echo esc_html( $locked_label ); ?></p>
		</div>
		<?php
		return;
	}
	?>

	<div class="affwp-wrap affwp-payout-method-wrap">
		<label for="affwp-payout-method">
			<?php esc_html_e( 'Payout Method', 'affiliate-wp' ); ?>
		</label>

		<select
			name="affwp_payout_method"
			id="affwp-payout-method"
			x-model="method"
		>
			<?php foreach ( $picker['options'] as $method_key => $method_label ) : ?>
				<option value="<?php echo esc_attr( $method_key ); ?>"<?php selected( (string) $method_key, (string) $current_value ); ?>>
					<?php echo esc_html( $method_label ); ?>
				</option>
			<?php endforeach; ?>
		</select>
	</div>

	<?php
}
/*
 * The renderer is invoked directly from templates/dashboard-tab-settings.php so it sits
 * above the Stripe/balance/email block rather than interleaving with other integrations on
 * the same action. Kept global so templates and third-party code can call it where needed.
 */

/**
 * Saves the affiliate's chosen payout method when they submit the Affiliate Area settings form.
 *
 * @since 2.33.0
 *
 * @param array $data Submitted form data.
 */
function affwp_affiliate_area_save_payout_method( $data ) {

	if ( empty( $data['affiliate_id'] ) ) {
		return;
	}

	if ( ! isset( $data['affwp_payout_method'] ) ) {
		return;
	}

	// Selector was hidden — ignore any submitted value (guards against crafted POSTs).
	if ( ! affwp_affiliates_can_select_payout_method() ) {
		return;
	}

	$result = affwp_save_affiliate_picker_choice(
		absint( $data['affiliate_id'] ),
		$data['affwp_payout_method']
	);

	/*
	 * The shared picker helper returns WP_Error for real validation failures
	 * — method not in the affiliate's display set, method disabled site-wide
	 * between page load and submit, or affiliate-not-found. Without this
	 * branch the error vanished silently: the affiliate hit Save, saw the
	 * page reload with the "profile updated" notice (because
	 * affwp_update_profile_settings always redirects with that flag), and
	 * never knew their selection didn't stick.
	 *
	 * Stash the message in a short-lived per-user transient that the
	 * dashboard template reads + clears on the next render. Pairs with the
	 * affiliate-area notice block above the tabbed UI.
	 */
	if ( is_wp_error( $result ) ) {
		set_transient(
			'affwp_payout_method_save_error_' . get_current_user_id(),
			$result->get_error_message(),
			MINUTE_IN_SECONDS
		);
	}
}
add_action( 'affwp_update_affiliate_profile_settings', 'affwp_affiliate_area_save_payout_method', 10, 1 );

/**
 * Returns the affiliate's stored payout country (ISO 3166-1 alpha-2), or ''.
 *
 * Canonical key is `affwp_payout_country` (set when the affiliate answers the payout-country
 * question). Falls back to `affwp_stripe_country` (written at Stripe connect time) so affiliates
 * who connected before the question existed still resolve correctly.
 *
 * @since 2.34.0
 *
 * @param int $affiliate_id Affiliate ID.
 * @return string Uppercase country code, or empty string.
 */
function affwp_get_affiliate_payout_country( $affiliate_id ) {

	$affiliate_id = absint( $affiliate_id );
	if ( ! $affiliate_id ) {
		return '';
	}

	$country = affwp_get_affiliate_meta( $affiliate_id, 'affwp_payout_country', true );
	if ( empty( $country ) ) {
		$country = affwp_get_affiliate_meta( $affiliate_id, 'affwp_stripe_country', true );
	}

	return is_string( $country ) ? strtoupper( $country ) : '';
}

/**
 * Renders the "Which country do you receive payouts in?" field beneath the payout method picker.
 *
 * Shown whenever Stripe is an enabled method — Stripe is the only method whose availability
 * depends on the affiliate's country, so the field reveals as a sub-question once Stripe is the
 * selected method (and stands alone as the first field when Stripe is the only method). Their
 * answer decides whether Stripe is offered or the payout falls back to another method, and it lets
 * the effective-method resolver route unserviceable-country affiliates off Stripe before connect.
 *
 * The field shows for any affiliate missing a country (the lazy prompt for existing affiliates):
 * no migration, the value just fills in the next time they save their settings.
 *
 * @since 2.34.0
 *
 * @param int $affiliate_id Affiliate ID.
 * @param int $user_id      User ID for this affiliate.
 */
function affwp_affiliate_area_render_payout_country_field( $affiliate_id, $user_id ) {

	if ( ! function_exists( 'affwp_is_payout_method_enabled' ) || ! affwp_is_payout_method_enabled( 'stripe' ) ) {
		return;
	}

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

	// Already connected to Stripe? The country was captured at connect and is locked to that
	// account, so re-asking is redundant (and confusing). Skip it.
	if ( function_exists( 'affwp_stripe_payouts_get_affiliate_account_id' )
		&& affwp_stripe_payouts_get_affiliate_account_id( $affiliate_id )
	) {
		return;
	}

	$countries = affwp_get_country_list();
	if ( empty( $countries ) ) {
		return;
	}

	$current   = affwp_get_affiliate_payout_country( $affiliate_id );
	$suggested = function_exists( 'affwp_stripe_payouts_get_domestic_country' )
		? affwp_stripe_payouts_get_domestic_country()
		: '';
	$suggested = ( is_string( $suggested ) && isset( $countries[ $suggested ] ) ) ? $suggested : '';

	/*
	 * Country is a Stripe sub-question: reveal it only while Stripe is the selected method (mirrors
	 * the payment-email field's per-method reveal). When Stripe is the only method the picker is
	 * absent, but the Alpine scope is always present, so `method` is the forced method and the same
	 * x-show works. The neutral prompt shows until a country is chosen; the error replaces it once an
	 * unserviceable country is picked, reading the `serviceable`/`nopeTpl` x-data the Stripe
	 * integration injects via the affwp_affiliate_area_payout_alpine_data filter (defaults are empty,
	 * so this no-ops if Stripe is inactive). No `$store`, so it also evaluates under an add-on's
	 * older bundled Alpine on the same page.
	 */
	?>
	<div
		class="affwp-wrap affwp-payout-country-wrap"
		x-show="method === 'stripe'"
		x-cloak
	>
		<label for="affwp-payout-country">
			<?php esc_html_e( 'Which country do you receive payouts in?', 'affiliate-wp' ); ?>
		</label>
		<select name="affwp_payout_country" id="affwp-payout-country" x-model="country" x-ref="affwpCountrySelect">
			<option value=""<?php selected( '', $current ); ?>><?php esc_html_e( 'Select your country…', 'affiliate-wp' ); ?></option>
			<?php if ( '' !== $suggested ) : ?>
				<optgroup label="<?php esc_attr_e( 'Suggested', 'affiliate-wp' ); ?>">
					<option value="<?php echo esc_attr( $suggested ); ?>"<?php selected( $suggested, $current ); ?>><?php echo esc_html( $countries[ $suggested ] ); ?></option>
				</optgroup>
			<?php endif; ?>
			<optgroup label="<?php esc_attr_e( 'All countries', 'affiliate-wp' ); ?>">
				<?php foreach ( $countries as $affwp_country_code => $affwp_country_name ) : ?>
					<option value="<?php echo esc_attr( $affwp_country_code ); ?>"<?php selected( (string) $affwp_country_code, (string) $current ); ?>><?php echo esc_html( $affwp_country_name ); ?></option>
				<?php endforeach; ?>
			</optgroup>
		</select>
		<p
			id="affwp-payout-country-desc"
			class="affwp-payout-country-desc"
			style="margin-top:8px;font-size:0.9em;color:#646970;"
			x-show="! country"
			x-cloak
		><?php esc_html_e( 'Choose the country where you receive payouts to continue.', 'affiliate-wp' ); ?></p>
		<?php // Base .affwp-notice is yellow (a warning); dropping the -error modifier softens this from an error. ?>
		<div
			id="affwp-payout-country-error"
			class="affwp-notice"
			role="alert"
			style="margin-top:8px;"
			x-show="country && serviceable.length && serviceable.indexOf( country.toUpperCase() ) === -1"
			x-cloak
		>
			<?php
			// Resolve the label from the option whose value matches the Alpine `country` prop (always in
			// sync), not selectedOptions[0] which can still point to the placeholder due to Alpine
			// reactivity timing. Uses a `function` callback (not an arrow) on purpose: the affiliate area
			// runs through wptexturize, and a `=>` inside the attribute makes its tag parser close the
			// <span> early, corrupting the closing quote into a smart quote and breaking the expression.
			?>
			<span x-text="nopeTpl.replace( '%s', $refs.affwpCountrySelect ? ( Array.from( $refs.affwpCountrySelect.options ).find( function( o ) { return o.value === country; } )?.text || country ) : country )"></span>
			<?php // A self-serve method is in their picker → one-click switch (sets the picker, reveals that method's field). ?>
			<button
				type="button"
				class="affwp-payout-switch-link"
				style="background:none;border:0;padding:0;color:inherit;font:inherit;text-decoration:underline;cursor:pointer;"
				x-show="switchTo"
				x-cloak
				@click="method = switchTo"
				x-text="switchTpl.replace( '%s', switchToLabel )"
			></button>
			<?php // Nothing selectable, but the resolver routes them somewhere (e.g. Manual) → say so. ?>
			<span x-show="! switchTo && paidViaLabel" x-cloak x-text="viaTpl.replace( '%s', paidViaLabel )"></span>
			<?php // No alternative at all (Stripe is the only enabled method) → genuine dead end. ?>
			<span x-show="! switchTo && ! paidViaLabel" x-cloak x-text="deadEndMsg"></span>
		</div>
	</div>
	<?php
}

/**
 * Saves the affiliate's payout country when they submit the Affiliate Area settings form.
 *
 * @since 2.34.0
 *
 * @param array $data Submitted form data.
 */
function affwp_affiliate_area_save_payout_country( $data ) {

	if ( empty( $data['affiliate_id'] ) || ! isset( $data['affwp_payout_country'] ) ) {
		return;
	}

	$affiliate_id = absint( $data['affiliate_id'] );
	$country      = strtoupper( sanitize_text_field( $data['affwp_payout_country'] ) );

	// Validate against the known list; ignore anything unrecognized (guards against crafted POSTs).
	$countries = function_exists( 'affwp_get_country_list' ) ? affwp_get_country_list() : array();
	if ( '' !== $country && ! isset( $countries[ $country ] ) ) {
		return;
	}

	affwp_update_affiliate_meta( $affiliate_id, 'affwp_payout_country', $country );
}
add_action( 'affwp_update_affiliate_profile_settings', 'affwp_affiliate_area_save_payout_country', 10, 1 );

/**
 * AJAX: persist the current affiliate's payout country.
 *
 * The Affiliate Area saves the country with the settings form, but the Pro Portal has its own
 * save model that doesn't run that action — so the Portal's country select persists through here
 * instead. Storing it lets the effective-method resolver route an unserviceable-country affiliate
 * off Stripe even though they never complete a connect.
 *
 * @since 2.34.0
 */
function affwp_ajax_save_payout_country() {

	check_ajax_referer( 'affwp_save_payout_country', 'nonce' );

	$affiliate_id = affwp_get_affiliate_id( get_current_user_id() );
	if ( ! $affiliate_id ) {
		wp_send_json_error();
	}

	$country   = isset( $_POST['country'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_POST['country'] ) ) ) : '';
	$countries = function_exists( 'affwp_get_country_list' ) ? affwp_get_country_list() : array();
	if ( '' !== $country && ! isset( $countries[ $country ] ) ) {
		wp_send_json_error();
	}

	affwp_update_affiliate_meta( $affiliate_id, 'affwp_payout_country', $country );
	wp_send_json_success();
}
add_action( 'wp_ajax_affwp_save_payout_country', 'affwp_ajax_save_payout_country' );
