<?php //phpcs:disable

/**
 * The file contains definition of WLC_Algolia class
 *
 * @package WLC\Algolia
 *
 */

namespace wlc_algolia;

use Helper\Helper;

defined( 'ABSPATH' ) || die( 'Direct file access is forbidden' );

/**
 * Ajax class
 */
class Algolia_Index_Ajax extends Client_Helper {
	//phpcs:enable
	/**
	 * Init Hooks.
	 *
	 * @return void
	 */
	public function hooks(): void {
		add_action( 'wp_ajax_wlc_algolia_handle_post_index', array( $this, 'wlc_handle_post_index' ) );
		add_action( 'wp_ajax_wlc_algolia_handle_post_type_index', array( $this, 'wlc_handle_post_type_index' ) );
		add_action( 'wp_ajax_wlc_algolia_process_post_type_index_batch', array( $this, 'wlc_process_post_type_index_batch' ) );
		add_action( 'wp_ajax_wlc_algolia_get_index_progress', array( $this, 'get_index_progress_callback' ) );
	}

	/**
	 * Post Type Index.
	 *
	 * @return void
	 */
	public function wlc_handle_post_type_index(): void {
		$selected_post_type = array();
		$selected_fields    = array();

		if ( ! current_user_can( 'manage_options' ) ) {
			$this->wlc_handle_error_json_send( __( 'You are not allowed to perform this action.', 'wlc-search-integration-with-algolia' ), 403 );
		}

		if ( ! check_ajax_referer( 'wlc_algolia_post_type_index', 'algolia_cpt_nonce', false ) ) {
			$this->wlc_handle_error_json_send( '', 400 );
		}

		if ( ! empty( $_POST['post_type'] ) ) {
			if ( $this->wlc_check_algolia_api_credentials() ) {
				// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
				$post_type = sanitize_key( wp_unslash( $_POST['post_type'] ) );
				// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
				$index_name = isset( $_POST['index_name'] ) ? sanitize_text_field( wp_unslash( $_POST['index_name'] ) ) : '';

				$cpt_settings = get_option( 'wlc_algolia_settings_cpt_' . $post_type );

				if ( is_array( $cpt_settings ) ) {
					$selected_post_type = Algolia_Index_Helper::wlc_prepare_selected_fields( $cpt_settings, 'post_type' );
					$selected_fields    = Algolia_Index_Helper::wlc_prepare_selected_fields( $cpt_settings, false );
				}

				// Start a background-ish job: return quickly, then process in batches via separate AJAX calls.
				$batch_size = 500;

				if ( empty( $selected_fields ) ) {
					$default_fields  = Algolia_Index_Helper::wlc_get_default_index_fields();
					$selected_fields = $default_fields[ $post_type ] ?? array();
				}

				$args                           = Algolia_Index_Helper::wlc_prepare_args( $post_type );
				$args['posts_per_page']         = $batch_size;
				$args['paged']                  = 1;
				$args['fields']                 = 'ids';
				$args['no_found_rows']          = false;
				$args['update_post_meta_cache'] = false;
				$args['update_post_term_cache'] = false;
				if ( 'attachment' === $post_type && empty( $args['post_status'] ) ) {
					$args['post_status'] = 'inherit';
				}

				$query         = new \WP_Query( $args );
				$total_batches = max( 1, (int) $query->max_num_pages );

				$job_key = 'wlc_algolia_index_job_' . sanitize_key( $post_type );

				// Full reindex should start from a clean index to avoid stale records
				// (e.g. old IDs that no longer exist in WordPress).
				$this->algolia_client->clear_index( $index_name );

				update_option(
					$job_key,
					array(
						'post_type'       => $post_type,
						'index_name'      => $index_name,
						'selected_fields' => $selected_fields,
						'page'            => 1,
						'total_batches'   => $total_batches,
						'batch_size'      => $batch_size,
						'started_at'      => time(),
					),
					false
				);

				update_option( 'wlc_algolia_index_progress_' . sanitize_key( $post_type ), 0, false );

				wp_send_json_success(
					array(
						'message'       => esc_html__( 'Indexing started.', 'wlc-search-integration-with-algolia' ),
						'post_type'     => $post_type,
						'total_batches' => $total_batches,
					)
				);
			} else {
				$this->wlc_handle_error_json_send(
					sprintf( "There was an error during indexing. Please check your <a href=%s>API credentials!</a>",//phpcs:ignore
						esc_url( admin_url( 'admin.php?page=wlc-algolia&tab=general' ) ) ) //phpcs:ignore
				);
			}
		}

		wp_die();
	}

	/**
	 * Process a single batch of post type indexing.
	 *
	 * @return void
	 */
	public function wlc_process_post_type_index_batch(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			$this->wlc_handle_error_json_send( __( 'You are not allowed to perform this action.', 'wlc-search-integration-with-algolia' ), 403 );
		}

		if ( ! check_ajax_referer( 'wlc_algolia_post_type_index', 'algolia_cpt_nonce', false ) ) {
			$this->wlc_handle_error_json_send( '', 400 );
		}

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$post_type = isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( $_POST['post_type'] ) ) : '';
		if ( '' === $post_type ) {
			$this->wlc_handle_error_json_send( esc_html__( 'Missing post type.', 'wlc-search-integration-with-algolia' ), 400 );
		}

		$job_key = 'wlc_algolia_index_job_' . sanitize_key( $post_type );
		$job     = get_option( $job_key );
		if ( ! is_array( $job ) ) {
			$this->wlc_handle_error_json_send( esc_html__( 'No indexing job found. Start indexing again.', 'wlc-search-integration-with-algolia' ), 404 );
		}

		$page            = isset( $job['page'] ) ? absint( $job['page'] ) : 1;
		$total_batches   = isset( $job['total_batches'] ) ? absint( $job['total_batches'] ) : 1;
		$batch_size      = isset( $job['batch_size'] ) ? absint( $job['batch_size'] ) : 500;
		$index_name      = isset( $job['index_name'] ) ? (string) $job['index_name'] : '';
		$selected_fields = isset( $job['selected_fields'] ) && is_array( $job['selected_fields'] ) ? $job['selected_fields'] : array();

		if ( $page > $total_batches ) {
			// Finalize indexing: ensure facet settings are saved one more time at the end.
			// This is a safety measure, but settings should already be saved in first batch.
			$this->algolia_client->update_faceting_settings_for_post_type( $post_type );

			update_option( 'wlc_algolia_index_progress_' . sanitize_key( $post_type ), 100, false );
			delete_option( $job_key );
			wp_send_json_success(
				array(
					'done'     => true,
					'progress' => 100,
				)
			);
		}

		$args                           = Algolia_Index_Helper::wlc_prepare_args( $post_type );
		$args['posts_per_page']         = $batch_size;
		$args['paged']                  = $page;
		$args['fields']                 = 'ids';
		$args['no_found_rows']          = true;
		$args['update_post_meta_cache'] = false;
		$args['update_post_term_cache'] = false;
		if ( 'attachment' === $post_type && empty( $args['post_status'] ) ) {
			$args['post_status'] = 'inherit';
		}

		$query    = new \WP_Query( $args );
		$post_ids = is_array( $query->posts ) ? $query->posts : array();
		$posts    = array();
		foreach ( $post_ids as $pid ) {
			$post = get_post( (int) $pid );
			if ( $post ) {
				$posts[] = $post;
			}
		}

		$payloads = $this->wlc_prepare_payload_data( $posts, true, $selected_fields );
		$this->algolia_client->index_post_batch( $payloads, $index_name, $page, $total_batches, $post_type );

		$next_page   = $page + 1;
		$job['page'] = $next_page;
		update_option( $job_key, $job, false );

		$progress = min( 100, ( $page / max( 1, $total_batches ) ) * 100 );
		update_option( 'wlc_algolia_index_progress_' . sanitize_key( $post_type ), $progress, false );

		wp_send_json_success(
			array(
				'done'        => $next_page > $total_batches,
				'page'        => $page,
				'total_pages' => $total_batches,
				'progress'    => $progress,
			)
		);
	}

	/**
	 * Send Index.
	 *
	 * @param string $post_type Post Type.
	 * @param bool   $cpt Send Custom Attributes/Fields or not.
	 * @param array  $selected_fields Selected Fields from CPT tab.
	 * @param string $spt_section CPT Section.
	 * @param string $index_name Index Name.
	 *
	 * @return void
	 */
	public function wlc_send_index( string $post_type, bool $cpt = false, array $selected_fields = array(), string $spt_section = '', string $index_name = '' ): void {
		$batch_size = 20;
		$results    = array();

		$args = Algolia_Index_Helper::wlc_prepare_args( $post_type );

		// Convert to a paginated query to avoid loading all posts into memory.
		$args['posts_per_page']         = $batch_size;
		$args['paged']                  = 1;
		$args['fields']                 = 'ids';
		$args['no_found_rows']          = false; // We need total batches for progress.
		$args['update_post_meta_cache'] = false;
		$args['update_post_term_cache'] = false;

		// Attachments default to "inherit" in many installs; keep compatibility.
		if ( 'attachment' === $post_type && empty( $args['post_status'] ) ) {
			$args['post_status'] = 'inherit';
		}

		$query         = new \WP_Query( $args );
		$total_batches = max( 1, (int) $query->max_num_pages );

		if ( $query->have_posts() ) {
			$batch_number = 1;

			while ( $batch_number <= $total_batches ) {
				if ( 1 !== $batch_number ) {
					$args['paged'] = $batch_number;
					$query         = new \WP_Query( $args );
				}

				$post_ids    = is_array( $query->posts ) ? $query->posts : array();
				$posts_batch = array();
				foreach ( $post_ids as $post_id ) {
					$post = get_post( (int) $post_id );
					if ( $post ) {
						$posts_batch[] = $post;
					}
				}

				$payloads  = $this->wlc_prepare_payload_data( $posts_batch, $cpt, $selected_fields );
				$result    = $this->algolia_client->index_post_batch( $payloads, $index_name, $batch_number, $total_batches, $spt_section );
				$results[] = $result;

				++$batch_number;
			}

			header( 'Content-Type: application/json' );
			if ( array_search( 'failed', array_column( $results, 'status' ), true ) === false ) {
				wp_send_json_success( $results );
			} else {
				wp_send_json_error( $results );
			}
		} else {
			wp_send_json_error(
				array(
					'message' => sprintf(
						/* translators: %s: Post type slug. */
						esc_html__( 'No posts found to index for post type: %s', 'wlc-search-integration-with-algolia' ),
						esc_html( $post_type )
					),
				)
			);
		}

		wp_die();
	}



	/**
	 * Send payload for indexing.
	 *
	 * @param array  $payloads Array of payloads.
	 * @param int    $posts_count Number of indexed posts.
	 * @param string $spt_section CPT Section.
	 * @param string $index_name Index Name.
	 *
	 * @return array
	 */
	public function wlc_send_payload_to_index( array $payloads, int $posts_count = 0, string $spt_section = '', string $index_name = '' ): array {
		$indexed_posts = array();
		foreach ( $payloads as $payload ) {
			$indexed_posts[] = array(
				'post_id'    => esc_attr( $payload['objectID'] ),
				'post_title' => esc_attr( $payload['title'] ),
			);
		}
		$result = $this->algolia_client->index_post_batch( $payloads, $index_name, $posts_count, $indexed_posts, $spt_section );
		return $result;
	}


	/**
	 * Prepare payload for indexing.
	 *
	 * @param array $posts Array of retrieved posts.
	 * @param bool  $cpt Is CPT Fields set.
	 * @param array $selected_fields Selected fields for post from CPT tab.
	 *
	 * @return array
	 */
	private function wlc_prepare_payload_data( array $posts, bool $cpt = false, array $selected_fields = array() ): array {
		$data = array();
		foreach ( $posts as $post ) {
			if ( $post ) {
				$data[] = $this->algolia_client->payload_data( $post, $cpt, $selected_fields );
			}
		}

		return $data;
	}

	/**
	 * Get Posts.
	 *
	 * @param array $args Array of post args.
	 *
	 * @return array
	 */
	private function wlc_get_posts( array $args ): array {
		return get_posts( $args );
	}

	/**
	 * Single Post Index.
	 *
	 * @return void
	 */
	public function wlc_handle_post_index(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			$this->wlc_handle_error_json_send( __( 'You are not allowed to perform this action.', 'wlc-search-integration-with-algolia' ), 403 );
		}

		if ( ! check_ajax_referer( 'wlc_algolia_post_index', 'algolia_nonce', false ) ) {
			$this->wlc_handle_error_json_send( '', 400 );
		}

		if ( ! empty( $_POST['post_id'] ) ) {
			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$post_id = absint( wp_unslash( $_POST['post_id'] ) );
			$post    = get_post( $post_id );
			if ( $post ) {
				if ( $this->wlc_check_algolia_api_credentials() ) {
					$data   = $this->algolia_client->payload_data( $post );
					$result = $this->send_post_index(
						$data,
						1,
						array(
							'post_id'    => esc_attr( $post->ID ),
							'post_title' => esc_attr( $post->post_title ),
						)
					);

					header( 'Content-Type: application/json' );
					wp_send_json_success( $result, 200 ); //phpcs:ignore
				}
			}
		}

		wp_die();
	}

	/**
	 * Send post index.
	 *
	 * @param array  $data Data to send.
	 * @param int    $posts_count number of posts.
	 * @param array  $indexed_posts array of indexed posts.
	 * @param string $cpt_section CPT Section.
	 * @param string $index_name Index Name.
	 *
	 * @return array
	 */
	private function send_post_index( array $data, int $posts_count, array $indexed_posts, string $cpt_section = '', string $index_name = '' ): array {
		if ( '' === $index_name ) {
			$index_name = $this->algolia_settings->get_index_name();
		}

		return $this->algolia_client->index_post( $data, $index_name, $posts_count, $indexed_posts, $cpt_section );
	}

	/**
	 * Handle Ajax error.
	 *
	 * @param string $message     Error message.
	 * @param int    $status_code HTTP status code.
	 *
	 * @return void
	 */
	public function wlc_handle_error_json_send( string $message = '', int $status_code = 400 ): void {
		if ( '' === $message ) {
			$message = __( 'Could not validate request. Please try again later!', 'wlc-search-integration-with-algolia' );
		}

		$result = array(
			'status'  => 'failed',
			'message' => Helpers\wlc_ksess_text( str_replace( '\\', '', $message ) ), //phpcs:disable
		);

		header( 'Content-Type: application/json' );
		wp_send_json_error( $result, $status_code, JSON_UNESCAPED_SLASHES );
	}

	/**
	 * Retrieves the indexing progress from the wlc_algolia_index_progress option and sends it as JSON.
	 *
	 * @return void
	 */
	public function get_index_progress_callback() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error(
				array( 'message' => __( 'You are not allowed to perform this action.', 'wlc-search-integration-with-algolia' ) ),
				403
			);
		}

		if ( ! check_ajax_referer( 'wlc_algolia_index_progress', 'algolia_progress_nonce', false ) ) {
			wp_send_json_error(
				array( 'message' => __( 'Could not validate request. Please try again later!', 'wlc-search-integration-with-algolia' ) ),
				400
			);
		}

		$post_type = '';
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		if ( isset( $_POST['post_type'] ) ) {
			$post_type = sanitize_key( wp_unslash( $_POST['post_type'] ) );
		}

		$progress_key = 'wlc_algolia_index_progress';
		if ( '' !== $post_type ) {
			$progress_key .= '_' . $post_type;
		}

		$progress = get_option( $progress_key, 0 );

		wp_send_json_success( array( 'progress' => $progress ) );
		wp_die();
	}
}
