<?php
/**
 * In-product feature discovery — public helpers, AJAX, and client wiring.
 *
 * @package     AffiliateWP
 * @subpackage  Admin/Discovery
 * @since       2.34.0
 */

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

/**
 * Returns the feature-discovery coordinator singleton.
 *
 * @since 2.34.0
 *
 * @return AffiliateWP_Feature_Discovery
 */
function affwp_feature_discovery() {
	return AffiliateWP_Feature_Discovery::instance();
}

/**
 * Capability required to see and act on discovery surfaces.
 *
 * @since 2.34.0
 *
 * @return string
 */
function affwp_discovery_capability() {
	/**
	 * Filters the capability gating in-product feature discovery surfaces.
	 *
	 * @since 2.34.0
	 *
	 * @param string $capability Capability slug.
	 */
	return apply_filters( 'affwp_discovery_capability', 'manage_affiliate_options' );
}

/**
 * Renders a discovery's in-page notice, or an empty string when it shouldn't show.
 *
 * The notice is the active nudge: a brand-accent feature notice with an eyebrow, a
 * benefit headline, a primary "Set it up" CTA that deep-links to the feature, a secondary
 * "Learn more" docs link, and a permanent dismiss.
 *
 * @since 2.34.0
 *
 * @param string $id Discovery id.
 * @return string Notice HTML, or '' when suppressed.
 */
function affwp_discovery_render_notice( $id ) {

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

	$discovery = affwp_feature_discovery();

	if ( ! $discovery->should_show_notice( $id ) ) {
		return '';
	}

	$def    = $discovery->get( $id );
	$notice = isset( $def['notice'] ) && is_array( $def['notice'] ) ? $def['notice'] : [];

	$eyebrow   = isset( $notice['eyebrow'] ) ? $notice['eyebrow'] : __( 'New', 'affiliate-wp' );
	$headline  = isset( $notice['heading'] ) ? $notice['heading'] : '';
	$body      = isset( $notice['body'] ) ? $notice['body'] : '';
	$cta_label = isset( $notice['cta_label'] ) ? $notice['cta_label'] : __( 'Set it up', 'affiliate-wp' );
	$docs_url  = isset( $def['docs_url'] ) ? $def['docs_url'] : '';
	$fragment  = isset( $def['cta_target'] ) ? (string) wp_parse_url( $def['cta_target'], PHP_URL_FRAGMENT ) : '';

	if ( '' === $headline && '' === $body ) {
		return '';
	}

	$id_js = esc_js( $id );

	// Primary CTA: record the follow (which also clears the pills server-side), hide the notice,
	// then deep-link into the feature. The short-lived deepLink flag tells the card's panel-open
	// watcher to skip its own badge-clear request so the two writes don't race.
	$cta_click = sprintf(
		"window.affwpDiscovery && window.affwpDiscovery.event('%s','notice_cta_followed'); discoveryVisible = false;%s",
		$id_js,
		'' !== $fragment
			? sprintf(
				" window.__affwpDiscoveryDeepLink = true; setTimeout(function(){ window.__affwpDiscoveryDeepLink = false; }, 800); window.location.hash = '%s';",
				esc_js( $fragment )
			)
			: ''
	);

	$cta_button = affwp_render_button(
		[
			'text'       => $cta_label,
			'variant'    => 'primary',
			'size'       => 'sm',
			'attributes' => [ '@click' => $cta_click ],
		]
	);

	$learn_more = '';
	if ( '' !== $docs_url ) {
		$learn_url = function_exists( 'affwp_utm_link' )
			? affwp_utm_link( $docs_url, 'feature-notice', $id )
			: $docs_url;
		$learn_more = sprintf(
			'<a href="%s" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-1 text-sm font-medium text-affwp-brand-600 hover:text-affwp-brand-700">%s%s</a>',
			esc_url( $learn_url ),
			esc_html__( 'Learn more', 'affiliate-wp' ),
			\AffiliateWP\Utils\Icons::get( 'share-up-right' )
		);
	}

	$actions = sprintf(
		'<div class="flex flex-wrap gap-3 items-center mt-1">%s%s</div>',
		$cta_button,
		$learn_more
	);

	$heading_html = sprintf(
		'<span class="block text-[11px] font-semibold tracking-wider uppercase text-affwp-brand-600 mb-0.5">%s</span>%s',
		esc_html( $eyebrow ),
		esc_html( $headline )
	);

	$notice_html = affwp_render_notice(
		[
			'variant'       => 'feature',
			'heading'       => $heading_html,
			'body'          => esc_html( $body ),
			'actions'       => $actions,
			'dismissable'   => true,
			'dismiss_attrs' => [
				'@click' => sprintf(
					"discoveryVisible = false; window.affwpDiscovery && window.affwpDiscovery.event('%s','notice_dismissed');",
					$id_js
				),
			],
			'attributes'    => [
				'x-show'  => 'discoveryVisible',
				'x-cloak' => true,
			],
		]
	);

	return sprintf(
		'<div class="mb-6" x-data="{ discoveryVisible: true }">%s</div>',
		$notice_html
	);
}

add_action( 'wp_ajax_affwp_discovery_event', 'affwp_ajax_discovery_event' );

/**
 * Persists a per-user discovery interaction (notice dismiss, CTA follow, badge clear).
 *
 * A single generic endpoint for every registered discovery so the system stays
 * reusable: it validates the id is registered and the event is a client-settable
 * flag, then records it and bumps the matching funnel counter.
 *
 * @since 2.34.0
 */
function affwp_ajax_discovery_event() {

	check_ajax_referer( 'affwp_discovery_event' );

	if ( ! current_user_can( affwp_discovery_capability() ) ) {
		wp_send_json_error( [ 'message' => __( 'Permission denied.', 'affiliate-wp' ) ], 403 );
	}

	$id    = isset( $_POST['id'] ) ? sanitize_key( wp_unslash( $_POST['id'] ) ) : '';
	$event = isset( $_POST['event'] ) ? sanitize_key( wp_unslash( $_POST['event'] ) ) : '';

	$discovery = affwp_feature_discovery();

	if ( '' === $id || null === $discovery->get( $id ) ) {
		wp_send_json_error( [ 'message' => __( 'Unknown discovery.', 'affiliate-wp' ) ], 400 );
	}

	// Only flags the client is allowed to set. tab_seen is server-side only.
	$client_events = [ 'notice_dismissed', 'notice_cta_followed', 'badge_cleared' ];

	if ( ! in_array( $event, $client_events, true ) ) {
		wp_send_json_error( [ 'message' => __( 'Invalid event.', 'affiliate-wp' ) ], 400 );
	}

	$discovery->set_flag( $id, $event, true );

	// Following the CTA opens the Stripe section, which is also what clears the NEW pills.
	// Set both in this single write so the badge-clear isn't lost to a racing request.
	if ( 'notice_cta_followed' === $event ) {
		$discovery->set_flag( $id, 'badge_cleared', true );
	}

	wp_send_json_success();
}

/**
 * Returns the client config (ajax url, nonce, action) for discovery surfaces.
 *
 * @since 2.34.0
 *
 * @return array
 */
function affwp_discovery_js_config() {
	return [
		'ajaxurl' => admin_url( 'admin-ajax.php' ),
		'action'  => 'affwp_discovery_event',
		'nonce'   => wp_create_nonce( 'affwp_discovery_event' ),
	];
}

add_action( 'admin_print_footer_scripts', 'affwp_discovery_print_footer_js' );

/**
 * Prints the tiny client helper that POSTs discovery events.
 *
 * Exposes window.affwpDiscovery.event( id, event ) — used by the notice dismiss
 * button, the primary CTA, and the accordion-expand clear. Printed once on the
 * AffiliateWP settings screen so surfaces can wire to it without a build step.
 *
 * @since 2.34.0
 */
function affwp_discovery_print_footer_js() {

	// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin screen check; no state change.
	if ( ! is_admin() || ! isset( $_GET['page'] ) || 'affiliate-wp-settings' !== sanitize_key( wp_unslash( $_GET['page'] ) ) ) {
		return;
	}

	if ( ! current_user_can( affwp_discovery_capability() ) ) {
		return;
	}

	$config = affwp_discovery_js_config();
	?>
	<script>
	( function () {
		var cfg = <?php echo wp_json_encode( $config ); ?>;
		window.affwpDiscovery = window.affwpDiscovery || {};
		window.affwpDiscovery.config = cfg;
		window.affwpDiscovery.event = function ( id, event ) {
			if ( ! id || ! event ) {
				return Promise.resolve();
			}
			var body = new URLSearchParams();
			body.append( 'action', cfg.action );
			body.append( '_wpnonce', cfg.nonce );
			body.append( 'id', id );
			body.append( 'event', event );
			return fetch( cfg.ajaxurl, {
				method: 'POST',
				credentials: 'same-origin',
				headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
				body: body.toString(),
			} ).catch( function () {} );
		};
	}() );
	</script>
	<?php
}
