<?php
/**
 * File Path: includes/class-mbs-esp-widget.php
 *
 * ESP Reader Signup Widget -- F-026-5
 *
 * Renders the subscriber signup form on book/author pages (injected via
 * mbs_after_book_display filter) and as [mbs_esp_signup] shortcode.
 * Handles the AJAX subscribe action for both logged-in and guest visitors.
 *
 * @package MyBookShowroom
 * @since   1.1.0
 */

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

class MBS_ESP_Widget {

	/** Default consent checkbox label -- used when no custom text is saved. */
	public const DEFAULT_CONSENT_TEXT = 'I agree to receive email updates about new books and author news. You can unsubscribe at any time.';

	/**
	 * Register WordPress hooks for the signup widget.
	 *
	 * Called once from MBS_Plugin::load_broadcast_and_esp(). Keeps hook
	 * registration in the class that owns the callbacks (SRP — R1-001).
	 *
	 * @return void
	 * @since 1.2.0
	 */
	public static function register_hooks(): void {
		add_action( 'wp_ajax_mbs_esp_subscribe',        array( 'MBS_ESP_Widget', 'handle_subscribe' ) );
		add_action( 'wp_ajax_nopriv_mbs_esp_subscribe', array( 'MBS_ESP_Widget', 'handle_subscribe' ) );
		add_filter( 'mbs_after_book_display', array( 'MBS_ESP_Widget', 'inject_after_book' ), 10, 2 );
	}

	/**
	 * Render the signup widget HTML.
	 * Returns empty string when any render condition is unmet.
	 * Silent -- no HTML comment on FREE or misconfigured.
	 *
	 * @param int $post_id  Book post ID for tag resolution (0 = no book context).
	 * @return string       Widget HTML, or empty string if conditions not met.
	 */
	public static function render( int $post_id = 0 ): string {
		if ( ! mbs_is_pro() ) {
			return '';
		}
		if ( '1' !== mbs_get_setting( 'email', 'widget_enabled', '0' ) ) {
			return '';
		}
		$platform = mbs_get_setting( 'email', 'platform', '' );
		if ( '' === $platform ) {
			return '';
		}
		$list_id = mbs_get_setting( 'email', 'default_list_id', '' );
		if ( '' === $list_id ) {
			return '';
		}
		// Verify a key is configured (adapter will not connect without one).
		if ( '' === MBS_ESP_Registry::get_api_key() ) {
			return '';
		}

		$consent_text = mbs_get_setting( 'email', 'consent_text',
			/* translators: Default consent checkbox label on signup form. */
			__( self::DEFAULT_CONSENT_TEXT, 'my-book-showroom' ) // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText -- DEFAULT_CONSENT_TEXT is a string constant defined in this class, not a variable
		);

		ob_start();
		mbs_get_template_part( 'partials/esp-signup-widget', array(
			'post_id'      => $post_id,
			'consent_text' => $consent_text,
		) );
		return (string) ob_get_clean();
	}

	/**
	 * Shortcode handler for [mbs_esp_signup].
	 * Accepts no attributes -- settings-driven.
	 *
	 * @return string Widget HTML, or empty string on FREE / misconfigured.
	 */
	public static function shortcode(): string {
		$post_id = (int) get_the_ID();
		return self::render( $post_id > 0 ? $post_id : 0 );
	}

	/**
	 * Filter callback for mbs_after_book_display.
	 * Appends the signup widget to the after-book area when enabled.
	 *
	 * @param string $content  Existing after-display HTML.
	 * @param int    $post_id  Current book post ID.
	 * @return string
	 */
	public static function inject_after_book( string $content, int $post_id ): string {
		return $content . self::render( $post_id );
	}

	/**
	 * AJAX subscribe handler.
	 * Hooked on wp_ajax_mbs_esp_subscribe and wp_ajax_nopriv_mbs_esp_subscribe.
	 */
	public static function handle_subscribe(): void {
		// 1. Nonce verification.
		if ( ! check_ajax_referer( 'mbs_esp_subscribe', '_ajax_nonce', false ) ) {
			wp_send_json_error( array( 'message' => __( 'Security check failed.', 'my-book-showroom' ) ) );
			return;
		}

		// 1b. Rate limit: max 5 subscribe attempts per IP per 60 seconds.
		$client_ip  = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) );
		$rate_key   = 'mbs_esp_rl_' . get_current_blog_id() . '_' . md5( $client_ip );
		$rate_count = (int) get_transient( $rate_key );
		if ( $rate_count >= 5 ) {
			wp_send_json_error( array( 'message' => __( 'Too many requests. Please try again later.', 'my-book-showroom' ) ), 429 );
			return;
		}
		set_transient( $rate_key, $rate_count + 1, 60 );

		// 2. PRO gate.
		if ( ! mbs_is_pro() ) {
			wp_send_json_error( array( 'message' => __( 'PRO license required.', 'my-book-showroom' ) ) );
			return;
		}

		// 3. Email sanitization and validation.
		$email = sanitize_email( wp_unslash( $_POST['mbs_esp_email'] ?? '' ) );
		if ( ! is_email( $email ) ) {
			wp_send_json_error( array( 'message' => __( 'Please enter a valid email address.', 'my-book-showroom' ) ) );
			return;
		}

		// 4. Name sanitization.
		$name = sanitize_text_field( wp_unslash( $_POST['mbs_esp_name'] ?? '' ) );

		// 5. Consent check — always enforced regardless of double opt-in setting (V2-C-040).
		$double_optin = '1' === mbs_get_setting( 'email', 'double_optin', '0' );
		if ( '1' !== ( $_POST['mbs_esp_consent'] ?? '' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- input sanitized via sanitize_* helpers or validated before use; see inline sanitization
			wp_send_json_error( array( 'message' => __( 'Please tick the consent checkbox to subscribe.', 'my-book-showroom' ) ) );
			return;
		}

		// 6. Book post ID.
		$post_id = absint( $_POST['mbs_esp_book_id'] ?? 0 );

		// 7. Build tag array.
		$tags = self::build_tags( $post_id );

		// 8. Get adapter.
		$adapter = MBS_ESP_Registry::get_adapter();
		if ( null === $adapter ) {
			wp_send_json_error( array( 'message' => __( 'Something went wrong. Please try again.', 'my-book-showroom' ) ) );
			return;
		}

		// 9. Default list ID.
		$list_id = mbs_get_setting( 'email', 'default_list_id', '' );
		if ( '' === $list_id ) {
			wp_send_json_error( array( 'message' => __( 'Something went wrong. Please try again.', 'my-book-showroom' ) ) );
			return;
		}

		// 10. Subscribe.
		$subscribed = $adapter->subscribe( $email, $name, $list_id, $double_optin );
		if ( ! $subscribed ) {
			wp_send_json_error( array( 'message' => __( 'Something went wrong. Please try again.', 'my-book-showroom' ) ) );
			return;
		}

		// 11. Apply tags if platform supports them.
		if ( ! empty( $tags ) && $adapter->supports_tags() ) {
			$adapter->tag_contact( $email, $tags, $list_id );
		}

		wp_send_json_success( array(
			/* translators: Success message shown after email signup. */
			'message' => __( 'Thank you! Check your inbox to confirm.', 'my-book-showroom' ),
		) );
	}

	/**
	 * Build the tag array for a book post.
	 * Respects mbs_setting_email_tag_book / tag_series / tag_genre toggles.
	 * Returns empty array when post_id <= 0 or all toggles are off.
	 *
	 * @param int $post_id
	 * @return string[]
	 */
	private static function build_tags( int $post_id ): array {
		$tags = array();
		if ( $post_id <= 0 ) {
			return $tags;
		}

		if ( '1' === mbs_get_setting( 'email', 'tag_book', '1' ) ) {
			$title = get_the_title( $post_id );
			if ( ! empty( $title ) ) {
				$tags[] = $title;
			}
		}

		if ( '1' === mbs_get_setting( 'email', 'tag_series', '1' ) ) {
			$series_terms = get_the_terms( $post_id, MBS_TAX_SERIES );
			if ( ! empty( $series_terms ) && ! is_wp_error( $series_terms ) ) {
				$tags[] = $series_terms[0]->name;
			}
		}

		if ( '1' === mbs_get_setting( 'email', 'tag_genre', '1' ) ) {
			$genre_terms = get_the_terms( $post_id, MBS_TAX_GENRE );
			if ( ! empty( $genre_terms ) && ! is_wp_error( $genre_terms ) ) {
				$tags[] = $genre_terms[0]->name;
			}
		}

		return $tags;
	}
}
