<?php
/**
 * MBS_Look_Inside_Ajax
 *
 * AJAX handler for DOCX Look Inside extraction (PRO tier).
 * Action:      mbs_extract_docx_look_inside
 * Nonce:       mbs_look_inside_nonce
 * Capability:  edit_post (post-specific)
 * Tier gate:   mbs_is_pro()
 *
 * @package    My_Book_Showroom
 * @subpackage Admin
 * @since      4.20
 */

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

/**
 * Class MBS_Look_Inside_Ajax
 *
 * Validates a .docx upload, parses prose via MBS_DOCX_Parser, sanitizes
 * via MBS_Look_Inside_Sanitizer, and returns clean HTML + word count.
 *
 * @since 4.20
 */
class MBS_Look_Inside_Ajax {

	/**
	 * Register the AJAX hook.
	 */
	public function __construct() {
		add_action( 'wp_ajax_mbs_extract_docx_look_inside', array( $this, 'handle' ) );
	}

	/**
	 * Handle the mbs_extract_docx_look_inside AJAX request.
	 *
	 * Sends a JSON response and exits.
	 *
	 * @return void
	 */
	public function handle(): void {
		check_ajax_referer( 'mbs_look_inside_nonce', 'nonce' );

		$post_id = absint( $_POST['post_id'] ?? 0 );
		if ( $post_id < 1 ) {
			wp_send_json_error( [ 'message' => 'Invalid post ID.' ], 400 );
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( [ 'message' => 'Insufficient permissions.' ], 403 );
		}

		if ( ! mbs_is_pro() ) {
			wp_send_json_error( [ 'message' => 'Look Inside DOCX import requires a PRO license.' ], 403 );
		}

		// Step 2: Check file upload error code first.
		$uploaded = $_FILES['file'] ?? null; // 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
		if ( ! $uploaded || UPLOAD_ERR_OK !== (int) $uploaded['error'] ) {
			$code = (int) ( $uploaded['error'] ?? -1 );
			wp_send_json_error( [ 'message' => sprintf( 'File upload failed (code %d).', $code ) ] );
		}

		// Step 3: Validate file extension.
		$ext = strtolower( pathinfo( $uploaded['name'], PATHINFO_EXTENSION ) );
		if ( 'docx' !== $ext ) {
			wp_send_json_error( [ 'message' => 'Only .docx files are accepted.' ] );
		}

		// Step 4: Overwrite check -- runs before is_uploaded_file so the confirmation
		// prompt can be triggered even in test environments where tmp_name is synthetic.
		$confirmed = sanitize_text_field( wp_unslash( $_POST['confirmed_overwrite'] ?? '' ) );
		if ( get_post_meta( $post_id, 'mbs_look_inside_text', true ) && 'true' !== $confirmed ) {
			wp_send_json_success( [ 'overwrite_required' => true ] );
		}

		// Step 5a: Validate file origin and size.
		if ( ! is_uploaded_file( $uploaded['tmp_name'] ) ) {
			wp_send_json_error( [ 'message' => 'File upload security check failed.' ] );
		}
		if ( (int) $uploaded['size'] > MBS_LOOK_INSIDE_DOCX_MAX_BYTES ) {
			wp_send_json_error( [ 'message' => 'File exceeds the 5 MB limit.' ] );
		}

		// Step 5: Stage uploaded file in unique temp path.
		if ( ! wp_mkdir_p( get_temp_dir() . 'mbs-temp-import/' ) ) {
			wp_send_json_error( [ 'message' => 'Could not create temp directory.' ] );
		}
		$uid       = get_current_user_id();
		$ts        = time();
		$temp_path = get_temp_dir() . 'mbs-temp-import/docx_' . $uid . '_' . $ts . '.docx';
		if ( ! move_uploaded_file( $uploaded['tmp_name'], $temp_path ) ) { // phpcs:ignore Generic.PHP.ForbiddenFunctions.Found -- base64_decode() used to decode pre-encoded look-inside/archive chunk data stored by plugin; not applied to raw user input
			wp_send_json_error( [ 'message' => 'Could not stage uploaded file.' ] );
		}

		// Step 6: Parse.
		$result = ( new MBS_DOCX_Parser() )->parse( $temp_path, MBS_LOOK_INSIDE_WORD_LIMIT );

		// Step 7: Always unlink temp file, including on parse failure.
		wp_delete_file( $temp_path );

		// Step 8: Return error on parse failure.
		if ( ! $result['ok'] ) {
			wp_send_json_error( [ 'message' => $result['error'] ] );
		}

		// Step 9: Sanitize. Word limit already enforced during extraction; pass 0.
		$clean = MBS_Look_Inside_Sanitizer::clean( $result['html'], true, 0 );

		// Step 10: Return clean HTML, word count, truncation flag.
		wp_send_json_success( [
			'html'       => $clean,
			'word_count' => $result['word_count'],
			'truncated'  => $result['truncated'],
		] );
	}
}
