<?php
/**
 * File Path: includes/shortcodes/class-mbs-shortcode-search.php
 *
 * [mbs_search] Shortcode — Story 13-8 (Phase 1 / C1 skeleton)
 *
 * Registers the [mbs_search] shortcode and the wp_ajax_mbs_search AJAX
 * handler. The shortcode renders a search input and results container.
 *
 * Phase 1 (C1): Shortcode registered, AJAX handler active, server-side
 * query implemented, input/results HTML scaffolded.
 *
 * Phase 3 (C3): mbs-front.js receives the debounced AJAX wiring and
 * live results update (deferred to Worker 2 / Phase 3).
 *
 * Security:
 *   - AJAX nonce verified on every request (both wp_ajax_ variants).
 *   - All search parameters sanitized via sanitize_text_field().
 *   - All output escaped.
 *
 * @package    My_Book_Showroom
 * @subpackage Includes/Shortcodes
 * @since      1.0.0
 */

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

/**
 * Class MBS_Shortcode_Search
 */
class MBS_Shortcode_Search {

	public function __construct() {
		// Register AJAX handlers for both logged-in and public visitors.
		add_action( 'wp_ajax_mbs_search',        array( $this, 'handle_ajax_search' ) );
		add_action( 'wp_ajax_nopriv_mbs_search', array( $this, 'handle_ajax_search' ) );
	}

	// -------------------------------------------------------------------------
	// Shortcode render
	// -------------------------------------------------------------------------

	/**
	 * Render the [mbs_search] shortcode.
	 *
	 * @param array $atts Shortcode attributes (unused in Phase 1 skeleton).
	 * @return string
	 */
	public static function render( array $atts = array() ): string {
		if ( ! mbs_is_registered() ) {
			return '<!-- MBS: License required for [mbs_search] -->';
		}

		$nonce = wp_create_nonce( 'mbs_search_nonce' );

		ob_start();
		?>
		<div class="mbs-wrapper mbs-search-wrapper"
		     data-nonce="<?php echo esc_attr( $nonce ); ?>"
		     data-ajaxurl="<?php echo esc_attr( admin_url( 'admin-ajax.php' ) ); ?>">

			<label class="screen-reader-text" for="mbs-search-input">
				<?php esc_html_e( 'Search books', 'my-book-showroom' ); ?>
			</label>
			<input
				type="search"
				id="mbs-search-input"
				class="mbs-search-input"
				placeholder="<?php esc_attr_e( 'Search books&hellip;', 'my-book-showroom' ); ?>"
				autocomplete="off"
			>
			<span class="mbs-search-spinner" aria-hidden="true"></span>

			<div class="mbs-search-results mbs-wrapper"
			     role="region"
			     aria-live="polite"
			     aria-atomic="true"
			     aria-label="<?php esc_attr_e( 'Search results', 'my-book-showroom' ); ?>">
				<?php /* Results injected here by mbs-front.js in Phase 3. */ ?>
			</div>

		</div>
		<?php
		return ob_get_clean();
	}

	// -------------------------------------------------------------------------
	// AJAX search handler
	// -------------------------------------------------------------------------

	/**
	 * Handle the wp_ajax_mbs_search request.
	 *
	 * Query: WP_Post titles + mbs_book postmeta (teaser, publisher).
	 * Returns JSON { results: [ { post_id, title, permalink, cover_url, teaser_excerpt } ] }.
	 *
	 * Respects optional $_POST['limit'] capped at 6 (Spec 5 / Phase 3).
	 * All input sanitized. Nonce verified. Output escaped.
	 */
	public function handle_ajax_search(): void {
		// Nonce check — never skip for wp_ajax_ hooks.
		if ( ! check_ajax_referer( 'mbs_search_nonce', 'nonce', false ) ) {
			wp_send_json_error( array( 'message' => 'Invalid nonce.' ), 403 );
			return;
		}

		// Rate limit: max 30 requests per IP per 60 seconds (V5-C-002/search).
		$client_ip  = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
		$rate_key   = 'mbs_search_rl_' . get_current_blog_id() . '_' . md5( $client_ip );
		$rate_count = (int) get_transient( $rate_key );
		if ( $rate_count >= 30 ) {
			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 );

		if ( ! mbs_is_registered() ) {
			wp_send_json_error( array( 'message' => 'License required.' ), 403 );
			return;
		}

		$query = isset( $_POST['query'] )
			? trim( sanitize_text_field( wp_unslash( $_POST['query'] ) ) )
			: '';

		// Cap limit at MAX_RESULTS (6) server-side as a guard (Spec 5 / Phase 3).
		$limit = isset( $_POST['limit'] ) ? min( (int) $_POST['limit'], 6 ) : 6;

		if ( '' === $query ) {
			wp_send_json_success( array( 'results' => array() ) );
			return;
		}

		$results = $this->run_search( $query, $limit );
		wp_send_json_success( array( 'results' => $results ) );
	}

	// -------------------------------------------------------------------------
	// Search query
	// -------------------------------------------------------------------------

	/**
	 * Run the book search query against post titles and selected postmeta.
	 *
	 * @param string $query  Sanitized search string.
	 * @param int    $limit  Maximum number of results.
	 * @return array[]
	 */
	private function run_search( string $query, int $limit ): array {
		$post_ids = MBS_Book_Query::search_by_text( $query, $limit );

		if ( empty( $post_ids ) ) {
			return array();
		}

		$results = array();
		foreach ( $post_ids as $post_id ) {
			$post_id = absint( $post_id );
			$post    = get_post( $post_id );
			if ( ! $post ) {
				continue;
			}

			$teaser      = get_post_meta( $post_id, 'mbs_teaser', true );
			$excerpt_raw = $teaser ?: get_the_excerpt( $post );
			$excerpt     = wp_trim_words( $excerpt_raw, 15 );

			// Cover URL — use MBS_Cover_Renderer per R5; extract src for JSON.
			$cover_html = MBS_Cover_Renderer::render( $post_id );
			$cover_url  = '';
			if ( preg_match( '/src=["\']([^"\']+)["\']/', $cover_html, $m ) ) {
				$cover_url = $m[1];
			}

			$results[] = array(
				'post_id'        => $post_id,
				'title'          => esc_html( get_the_title( $post ) ),
				'permalink'      => esc_url( get_permalink( $post ) ),
				'cover_url'      => esc_url( $cover_url ),
				'teaser_excerpt' => esc_html( $excerpt ),
			);
		}

		return $results;
	}
}
