<?php
/**
 * File Path: includes/admin/tabs/imp-exp/class-mbs-tab-child-archive.php
 *
 * Import / Export Tab — Archive Child Handler
 *
 * Handles the three AJAX actions that drive the MBS Archive Import feature:
 *
 *   mbs_archive_import_start  — Receive uploaded ZIP; parse + restore taxonomy
 *                               + apply settings; return session token + total.
 *   mbs_archive_import_book   — Hydrate one book (called once per book by the
 *                               JS polling loop).
 *   mbs_archive_import_media  — Sideload one book's media (second pass after all
 *                               books are hydrated).
 *
 * Archive export is retired — use MBS_Exporter_Zip (Export Catalog + Media,
 * PRO-gated) as the authoritative export path.
 *
 * Security: every handler opens with $this->verify_import_export_request() which
 * calls check_ajax_referer( 'mbs_import_export', 'nonce' ) and verifies
 * current_user_can( 'manage_options' ). Import has no PRO gate.
 *
 * @package MyBookShowroom
 * @since   1.0.0
 */

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

// phpcs:disable WordPress.Security.NonceVerification -- nonce verified in parent class verify_save() / check_admin_referer() before $_POST access; PHPCS cannot trace cross-method nonce verification
// phpcs:disable WordPress.Security.ValidatedSanitizedInput -- input sanitized at point of use (sanitize_text_field, absint, wp_kses_post, etc.); false positives from PHPCS not tracing sanitization helpers called before this method
class MBS_Tab_Child_Archive {

	use MBS_Ajax_Import_Export_Guard;

	// -------------------------------------------------------------------------
	// Import — Phase A: parse + prepare
	// -------------------------------------------------------------------------

	/**
	 * AJAX: Receive the uploaded ZIP, parse it, restore taxonomy terms, apply
	 * settings, and return a session token with the total book count so the JS
	 * can start the polling loop.
	 *
	 * No PRO gate — import is always available. Nonce: mbs_import_export.
	 * Expects multipart/form-data with field name 'archive_file'.
	 *
	 * @return void  Sends JSON response.
	 * @since  1.0.0
	 */
	public function handle_ajax_mbs_archive_import_start(): void {
		$this->verify_import_export_request();

		if (
			empty( $_FILES['archive_file'] ) ||
			! isset( $_FILES['archive_file']['error'] ) ||
			UPLOAD_ERR_OK !== (int) $_FILES['archive_file']['error']
		) {
			$err_code = isset( $_FILES['archive_file']['error'] ) ? (int) $_FILES['archive_file']['error'] : -1;
			wp_send_json_error( array(
				'message' => sprintf(
					/* translators: %d: PHP upload error code */
					__( 'File upload failed (error code %d).', 'my-book-showroom' ),
					$err_code
				),
			) );
		}

		$original_name = sanitize_file_name( wp_unslash( $_FILES['archive_file']['name'] ?? '' ) );
		$tmp_name      = $_FILES['archive_file']['tmp_name'];

		// ADM-026: Verify the file came from a real HTTP upload.
		if ( ! is_uploaded_file( $tmp_name ) ) {
			wp_send_json_error( array( 'message' => __( 'Invalid file upload.', 'my-book-showroom' ) ) );
		}

		// ADM-026: Inspect file content — do not rely on browser-supplied type.
		$check = wp_check_filetype_and_ext( $tmp_name, $original_name );
		if ( empty( $check['ext'] ) || 'zip' !== $check['ext'] ) {
			wp_send_json_error( array( 'message' => __( 'Only .zip archive files are accepted.', 'my-book-showroom' ) ) );
		}

		// Move the uploaded file to a safe temp location for processing.
		$upload_dir = get_temp_dir() . 'mbs-import-uploads/';
		wp_mkdir_p( $upload_dir );
		$tmp_path = $upload_dir . wp_generate_uuid4() . '.zip';

		// phpcs:ignore WordPress.PHP.NoSilencedErrors
		if ( ! @move_uploaded_file( $tmp_name, $tmp_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( array( 'message' => __( 'Failed to move uploaded archive file.', 'my-book-showroom' ) ) );
		}

		$result = MBS_Import_Orchestrator::prepare( $tmp_path );

		// Remove the uploaded copy — orchestrator has already extracted contents.
		// phpcs:ignore WordPress.PHP.NoSilencedErrors
		wp_delete_file( $tmp_path );

		if ( is_wp_error( $result ) ) {
			wp_send_json_error( array( 'message' => $result->get_error_message() ) );
		}

		wp_send_json_success( $result );
	}

	// -------------------------------------------------------------------------
	// Import — Phase B: hydrate one book
	// -------------------------------------------------------------------------

	/**
	 * AJAX: Hydrate the book at position $index using the stored session.
	 *
	 * Called once per book by the JS polling loop. Returns a progress snapshot
	 * so the JS can update the progress bar and fire the next request.
	 *
	 * POST params: token (string), index (int), options (array, optional).
	 *
	 * @return void  Sends JSON response.
	 * @since  1.0.0
	 */
	public function handle_ajax_mbs_archive_import_book(): void {
		$this->verify_import_export_request();

		$token   = isset( $_POST['token'] ) ? sanitize_text_field( wp_unslash( $_POST['token'] ) ) : '';
		$index   = isset( $_POST['index'] ) ? (int) $_POST['index'] : -1;
		$options = isset( $_POST['options'] ) && is_array( $_POST['options'] )
			? array_map( 'sanitize_text_field', wp_unslash( $_POST['options'] ) )
			: array();

		if ( '' === $token || $index < 0 ) {
			wp_send_json_error( array( 'message' => __( 'Missing token or book index.', 'my-book-showroom' ) ) );
		}

		$result = MBS_Import_Orchestrator::hydrate_book( $token, $index, $options );

		if ( is_wp_error( $result ) ) {
			wp_send_json_error( array( 'message' => $result->get_error_message() ) );
		}

		wp_send_json_success( $result );
	}

	// -------------------------------------------------------------------------
	// Import — Phase C: sideload media for one book
	// -------------------------------------------------------------------------

	/**
	 * AJAX: Sideload all media for the book at position $index.
	 *
	 * Called once per book by the JS media polling loop, which runs after all
	 * books have been hydrated. On the last book the orchestrator cleans up the
	 * extracted temp directory and deletes the session transient.
	 *
	 * POST params: token (string), index (int).
	 *
	 * @return void  Sends JSON response.
	 * @since  1.0.0
	 */
	public function handle_ajax_mbs_archive_import_media(): void {
		$this->verify_import_export_request();

		$token = isset( $_POST['token'] ) ? sanitize_text_field( wp_unslash( $_POST['token'] ) ) : '';
		$index = isset( $_POST['index'] ) ? (int) $_POST['index'] : -1;

		if ( '' === $token || $index < 0 ) {
			wp_send_json_error( array( 'message' => __( 'Missing token or book index.', 'my-book-showroom' ) ) );
		}

		$result = MBS_Import_Orchestrator::sideload_media( $token, $index );

		if ( is_wp_error( $result ) ) {
			wp_send_json_error( array( 'message' => $result->get_error_message() ) );
		}

		wp_send_json_success( $result );
	}
}
