<?php //phpcs:disable

/**
 * Class responsible for initialing search client for Algolia.
 *
 * @package WLC_Algolia
 */

namespace wlc_algolia;

use Algolia\AlgoliaSearch\Api\SearchClient;

/**
 * Algolia_Client class.
 */
class Algolia_Client {
	//phpcs:enable
	/**
	 * Algolia Settings.
	 *
	 * @var Algolia_Settings_Options $algolia_settings
	 */
	public Algolia_Settings_Options $algolia_settings;

	/**
	 * Class construct.
	 *
	 * @param Algolia_Settings_Options $settings Settings.
	 */
	public function __construct( Algolia_Settings_Options $settings ) {
		$this->algolia_settings = $settings;
	}

	/**
	 * Algolia search client.
	 *
	 * @return SearchClient|null
	 */
	private function create_client(): ?SearchClient {
		try {
			return SearchClient::create(
				(string) $this->algolia_settings->get_application_id(),
				(string) $this->algolia_settings->get_write_api_key()
			);
		} catch ( \Throwable $e ) {
			// The v4 client throws when credentials are missing; callers expect null.
			return null;
		}
	}

	/**
	 * Clear all records from an index.
	 *
	 * @param string $index_name Algolia index name.
	 * @return bool
	 */
	public function clear_index( string $index_name ): bool {
		$client = $this->create_client();
		if ( ! $client ) {
			return false;
		}

		try {
			$client->clearObjects( $index_name );
			return true;
		} catch ( \Throwable $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- Silent failure, caller can continue indexing.
			return false;
		}
	}

	/**
	 * Remove one object from an index (e.g. after trash/delete or unpublish).
	 *
	 * @param string $index_name Full Algolia index name (prefix_type).
	 * @param string $object_id  Algolia objectID (same as indexed WordPress post ID).
	 * @return bool
	 */
	public function delete_object( string $index_name, string $object_id ): bool {
		$object_id = trim( $object_id );
		if ( '' === $index_name || '' === $object_id ) {
			return false;
		}

		$client = $this->create_client();
		if ( ! $client ) {
			return false;
		}

		/*
		 * Legacy records may use objectID as JSON number vs string; Algolia treats those as different keys.
		 * Try both so deletes match how the record was first indexed.
		 */
		$variants = array( $object_id );
		if ( is_numeric( $object_id ) ) {
			$variants[] = (int) $object_id;
			$variants[] = (string) (int) $object_id;
		}
		$variants = array_values( array_unique( $variants, SORT_REGULAR ) );

		$ok = false;
		foreach ( $variants as $oid ) {
			try {
				$client->deleteObject( $index_name, (string) $oid );
				$ok = true;
			} catch ( \Throwable $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
				continue;
			}
		}

		return $ok;
	}

	/**
	 * Index single post.
	 *
	 * @param array  $data Data to send.
	 * @param string $index_name Algolia Index Name.
	 * @param int    $posts_count Number of posts.
	 * @param array  $indexed_posts Indexed posts ID and Title.
	 * @param string $cpt_section SPT Section slug.
	 *
	 * @return array
	 */
	public function index_post( array $data, string $index_name, int $posts_count = 0, array $indexed_posts = array(), string $cpt_section = '' ): array {
		$failed = array(
			'status'  => 'failed',
			'message' => esc_html__( 'There was an error during indexing.', 'wlc-search-integration-with-algolia' ),
			'posts'   => $indexed_posts,
		);

		$object_id = $data['objectID'] ?? $data['post_id'] ?? null;
		if ( null === $object_id || '' === $object_id || false === $object_id ) {
			return $failed;
		}

		if ( is_numeric( $object_id ) ) {
			$data['objectID'] = (string) (int) $object_id;
		} else {
			$data['objectID'] = (string) $object_id;
		}
		$data['post_id'] = $data['objectID'];

		$client = $this->create_client();
		if ( null === $client ) {
			return $failed;
		}

		try {
			$client->saveObject( $index_name, $data );
		} catch ( \Throwable $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
			return array(
				'status'  => 'failed',
				'message' => esc_html__( 'There was an error during indexing.', 'wlc-search-integration-with-algolia' ),
				'posts'   => $indexed_posts,
			);
		}

		if ( $cpt_section ) {
			$this->wlc_save_facetes( $client, $index_name, $cpt_section );
		}

		return array(
			'status'        => 'success',
			'message'       => esc_html__( 'Index completed successfully', 'wlc-search-integration-with-algolia' ),
			'indexed_posts' => $indexed_posts,
			'total_posts'   => $posts_count,
		);
	}

	/**
	 * Save facetes for post.
	 *
	 * @param SearchClient $client      Algolia search client.
	 * @param string       $index_name  Algolia index name.
	 * @param string       $cpt_section SPT Section slug.
	 *
	 * @return void
	 */
	public function wlc_save_facetes( SearchClient $client, string $index_name, string $cpt_section = '' ): void {
		$settings = $this->wlc_prepare_facetes_settings_array( $cpt_section );
		if ( ! empty( $settings ) ) {
			// Use setSettings with forwardToReplicas: false to ensure settings are applied to this index
			// Note: setSettings returns a taskID, but we don't wait for it to complete here
			// to avoid blocking the indexing process. Settings will be applied asynchronously by Algolia.
			$client->setSettings( $index_name, $settings, false );
		}
	}

	/**
	 * Indexing payload data.
	 *
	 * @param \WP_Post $post WP Post.
	 * @param bool     $cpt Is CPT Fields set.
	 * @param array    $selected_fields Selected fields for post from CPT tab.
	 *
	 * @return array
	 */
	public function payload_data( \WP_Post $post, bool $cpt = false, array $selected_fields = array() ): array {
		if ( true === $cpt ) {
			$record = $this->wlc_prepare_fields_from_cpt( $selected_fields, $post );
		} else {
			$attributes         = $this->prepare_posts_attributes_for_indexing( $post );
			$record             = $attributes;
			$record['objectID'] = $post->ID;
			$record['title']    = esc_attr( $post->post_title );
		}

		$record = apply_filters(
			'wlc_algolia_algolia_payload_data', //phpcs:ignore
			$record
		);

		if ( ! is_array( $record ) ) {
			$record = array();
		}

		return $this->normalize_record_object_identity( $record, (int) $post->ID );
	}

	/**
	 * Algolia treats objectID 123 (number) and "123" (string) as different records — force one canonical string key.
	 *
	 * @param array $record     Record (after filters).
	 * @param int   $wp_post_id WordPress post ID.
	 * @return array
	 */
	private function normalize_record_object_identity( array $record, int $wp_post_id ): array {
		if ( $wp_post_id <= 0 ) {
			return $record;
		}

		$canonical = (string) $wp_post_id;

		$record['objectID'] = $canonical;
		$record['post_id']  = $canonical;

		return $record;
	}

	/**
	 * Prepare data for post.
	 *
	 * @param array    $selected_fields Selected Fields.
	 * @param \WP_Post $post WP Post.
	 *
	 * @return array
	 */
	public function wlc_prepare_fields_from_cpt( array $selected_fields, \WP_Post $post ): array {
		$record = array();
		$type   = '';
		foreach ( $selected_fields as $field => $options ) {
			if ( isset( $options['type'] ) ) {
				$type = $options['type'];
			}

			$record[ $field ] = $this->wlc_handle_cpt_type( $type, $post->{$field} );
			$record           = $this->wlc_add_records( $record, $field, $type, $post );

		}

		// Always index canonical permalink regardless of selected CPT fields.
		// Frontend result links depend on this value to avoid fallback URLs like ?p=123.
		$record['post_permalink'] = esc_url( get_permalink( $post->ID ) );
		$record                   = $this->wlc_add_author_runtime_fields( $record, $post );
		$record                   = $this->wlc_add_date_runtime_fields( $record, $post );
		$record                   = $this->wlc_add_product_runtime_fields( $record, $post );
		$record                   = $this->wlc_add_price_numeric_fields( $record );

		return $record;
	}

	/**
	 * Add records based of certain fields.
	 *
	 * @param array    $record The records array.
	 * @param string   $field Post field.
	 * @param string   $type  The Field type.
	 * @param \WP_Post $post WP Post.
	 *
	 * @return array
	 */
	private function wlc_add_records( array $record, string $field, string $type, \WP_Post $post ): array {
		if ( 'ID' === $field ) {
			$record['objectID'] = $this->wlc_handle_cpt_type( $type, $post->{$field} );
			$record['post_id']  = $this->wlc_handle_cpt_type( $type, $post->{$field} );
		}

		if ( 'post_title' === $field ) {
			$record['title'] = esc_attr( $this->wlc_handle_cpt_type( $type, $post->{$field} ) );
		}

		if ( 'post_content' === $field ) {
			$record['post_content'] = Algolia_Index_Helper::wlc_prepare_content( $post->{$field} );
		}

		if ( 'images' === $field ) {
			$record['images'] = Algolia_Index_Helper::wlc_get_post_images( $post->ID );
		}

		if ( 'taxonomies' === $field ) {
			$record['taxonomies'] = $this->wlc_prepare_taxonomies( $post );
		}

		if ( 'post_permalink' === $field ) {
			$record['post_permalink'] = esc_url( get_permalink( $post->ID ) );
		}

		return apply_filters( 'wlc_algolia_cpt_records', $record ); //phpcs:ignore
	}

	/**
	 * Extract facetes from CPT settings.
	 *
	 * @param array $selected_fields Selected Fields from CPT.
	 *
	 * @return array
	 */
	protected function wlc_prepare_facetes_from_cpt( array $selected_fields ): array {
		$facetes = array();
		foreach ( $selected_fields as $field => $options ) {
			if ( ! empty( $options['index'] ) && '1' === $options['index'] ) {
				if ( isset( $options['facet'] ) && '1' === $options['facet'] ) {
					$facetes[] = $field;
				}
			}
		}

		return apply_filters( 'wlc_algolia_facetes', $facetes ); //phpcs:ignore
	}

	/**
	 * Prepare Facetes array from settings.
	 *
	 * @param string $cpt_section SPT Section slug.
	 *
	 * @return array
	 */
	protected function wlc_prepare_facetes_settings_array( string $cpt_section = '' ): array {
		$settings                      = array();
		$cpt_settings                  = get_option( 'wlc_algolia_settings_cpt_' . $cpt_section, array() );
		$selected_fields               = Algolia_Index_Helper::wlc_prepare_selected_fields( $cpt_settings, false );
		$default_pagination_limited_to = $this->algolia_settings->get_pagination_limited_to();
		$pagination_limited_to         = (int) apply_filters( 'wlc_algolia_pagination_limited_to', $default_pagination_limited_to, $cpt_section ); // phpcs:ignore
		$pagination_limited_to         = max( 1000, min( 20000, $pagination_limited_to ) );

		if ( ! empty( $selected_fields ) ) {

			$facetes = $this->wlc_prepare_facetes_from_cpt( $selected_fields );

			// Special case: taxonomies are indexed inside the "taxonomies" object.
			// Use the nested facet path that matches indexed records:
			// taxonomies.taxonomies_names.{taxonomy}[] and taxonomies.taxonomies_slugs.{taxonomy}[].
			// Check if taxonomies field is indexed (even if not explicitly set as facet).
			// If taxonomies are indexed, we should add them as facets automatically.
			$taxonomies_indexed = isset( $selected_fields['taxonomies'] )
				&& isset( $selected_fields['taxonomies']['index'] )
				&& '1' === $selected_fields['taxonomies']['index'];

			// Also check if taxonomies is explicitly in facetes array.
			$taxonomies_in_facetes = in_array( 'taxonomies', $facetes, true );

			if ( ( $taxonomies_indexed || $taxonomies_in_facetes ) && '' !== $cpt_section ) {
				// Remove 'taxonomies' from facetes array if present (we'll replace it with specific taxonomy facets).
				$facetes = array_values( array_diff( $facetes, array( 'taxonomies' ) ) );

				$taxonomies = get_object_taxonomies( $cpt_section, 'objects' );
				if ( is_array( $taxonomies ) && ! empty( $taxonomies ) ) {
					foreach ( $taxonomies as $taxonomy ) {
						if ( isset( $taxonomy->name ) && '' !== (string) $taxonomy->name ) {
							$tax_name = (string) $taxonomy->name;
							// Canonical taxonomy facet schema for this plugin.
							// Indexed records store these values under the "taxonomies" object.
							$facetes[] = 'taxonomies.taxonomies_slugs.' . $tax_name;
							$facetes[] = 'taxonomies.taxonomies_names.' . $tax_name;
						}
					}
				}
			}

			$facetes = array_values( array_unique( array_filter( $facetes ) ) );
			if ( in_array( 'post_date', $facetes, true ) || in_array( 'post_date_ts', $facetes, true ) ) {
				$facetes[] = 'post_year';
			}
			if ( 'product' === $cpt_section ) {
				$facetes[] = 'rating_bucket';
				$facetes   = array_values( array_unique( array_filter( $facetes ) ) );
			}

			$settings['attributesForFaceting'] = $facetes;
		}

		$settings['paginationLimitedTo'] = $pagination_limited_to;

		return $settings;
	}

	/**
	 * Update only Algolia index settings for a post type (no reindex).
	 *
	 * @param string $post_type Post type slug.
	 * @return bool
	 */
	public function update_faceting_settings_for_post_type( string $post_type ): bool {
		$post_type = sanitize_key( $post_type );
		if ( '' === $post_type ) {
			return false;
		}

		$client = $this->create_client();
		if ( null === $client ) {
			return false;
		}

		$base_index = (string) $this->algolia_settings->get_index_name();
		if ( '' === $base_index ) {
			return false;
		}

		$index_name = $base_index . '_' . $post_type;
		$this->wlc_save_facetes( $client, $index_name, $post_type );

		return true;
	}

	/**
	 * Prepare post attributes for indexing.
	 *
	 * @param \WP_Post $post Post to index.
	 *
	 * @return array
	 */
	public function prepare_posts_attributes_for_indexing( \WP_Post $post ): array {
		$attributes = array(
			'post_id'        => esc_attr( $post->ID ),
			'post_type'      => esc_attr( $post->post_type ),
			'post_content'   => Algolia_Index_Helper::wlc_prepare_content( $post->post_content ),
			'post_title'     => esc_attr( $post->post_title ),
			'images'         => Algolia_Index_Helper::wlc_get_post_images( $post->ID ),
			'post_excerpt'   => $post->post_excerpt,
			'post_date'      => get_post_time( 'U', false, $post ),
			'post_modified'  => get_post_modified_time( 'U', false, $post ),
			'post_permalink' => get_permalink( $post ),
		);

		$attributes['taxonomies'] = $this->wlc_prepare_taxonomies( $post );
		$attributes               = $this->wlc_add_author_runtime_fields( $attributes, $post );
		$attributes               = $this->wlc_add_date_runtime_fields( $attributes, $post );
		$attributes               = $this->wlc_add_product_runtime_fields( $attributes, $post );
		$attributes               = $this->wlc_add_price_numeric_fields( $attributes );

		return $attributes;
	}

	/**
	 * Add product runtime fields expected by frontend result templates.
	 *
	 * @param array    $record Current Algolia record.
	 * @param \WP_Post $post   Source WP post.
	 *
	 * @return array
	 */
	private function wlc_add_product_runtime_fields( array $record, \WP_Post $post ): array {
		if ( 'product' !== $post->post_type ) {
			return $record;
		}

		$record = $this->wlc_split_product_visibility_and_rating( $record );

		$stock_status = (string) get_post_meta( $post->ID, '_stock_status', true );
		if ( '' !== $stock_status ) {
			$record['_stock_status'] = $stock_status;
			$record['stock_status']  = $stock_status;
		}

		if ( function_exists( 'wc_get_product' ) ) {
			$product = wc_get_product( $post->ID );
			if ( $product ) {
				$computed_status = (string) $product->get_stock_status();
				if ( '' !== $computed_status && '' === $stock_status ) {
					$record['_stock_status'] = $computed_status;
					$record['stock_status']  = $computed_status;
				}
			}
		}

		return $record;
	}

	/**
	 * Add normalized author fields (ID + readable label) for facets/templates.
	 *
	 * @param array    $record Current Algolia record.
	 * @param \WP_Post $post   Source WP post.
	 *
	 * @return array
	 */
	private function wlc_add_author_runtime_fields( array $record, \WP_Post $post ): array {
		$author_id = isset( $post->post_author ) ? (int) $post->post_author : 0;
		if ( $author_id <= 0 ) {
			return $record;
		}

		$record['post_author'] = (string) $author_id;
		$user                  = get_user_by( 'id', $author_id );
		if ( ! $user instanceof \WP_User ) {
			return $record;
		}

		$first_name = trim( (string) get_user_meta( $author_id, 'first_name', true ) );
		$last_name  = trim( (string) get_user_meta( $author_id, 'last_name', true ) );
		$full_name  = trim( $first_name . ' ' . $last_name );
		$nickname   = trim( (string) get_user_meta( $author_id, 'nickname', true ) );
		$label      = '';

		if ( '' !== $full_name ) {
			$label = $full_name;
		} elseif ( '' !== $nickname ) {
			$label = $nickname;
		} else {
			$label = trim( (string) $user->display_name );
		}

		if ( '' !== $label ) {
			$record['post_author_name'] = sanitize_text_field( $label );
		}

		return $record;
	}

	/**
	 * Add normalized date fields for range/year filtering in frontend.
	 *
	 * @param array    $record Current Algolia record.
	 * @param \WP_Post $post   Source WP post.
	 *
	 * @return array
	 */
	private function wlc_add_date_runtime_fields( array $record, \WP_Post $post ): array {
		$timestamp = get_post_time( 'U', false, $post );
		$timestamp = is_numeric( $timestamp ) ? (int) $timestamp : 0;

		if ( $timestamp <= 0 ) {
			return $record;
		}

		$record['post_date_ts'] = $timestamp;
		$record['post_year']    = gmdate( 'Y', $timestamp );
		$record['post_month']   = gmdate( 'Y-m', $timestamp );

		/*
		 * Keep post_date stable as human-readable string for display/facet labels.
		 * Numeric filtering uses post_date_ts.
		 */
		$record['post_date'] = gmdate( 'Y-m-d H:i:s', $timestamp );

		return $record;
	}

	/**
	 * Split WooCommerce product visibility terms into true visibility and rating buckets.
	 *
	 * WooCommerce stores rating pseudo-terms like rated-3 in product_visibility taxonomy.
	 * We keep stock visibility terms there, but move rating into dedicated fields for cleaner faceting.
	 *
	 * @param array $record Current Algolia record.
	 * @return array
	 */
	private function wlc_split_product_visibility_and_rating( array $record ): array {
		$slug_values = array();
		if ( isset( $record['taxonomies']['taxonomies_slugs']['product_visibility'] ) && is_array( $record['taxonomies']['taxonomies_slugs']['product_visibility'] ) ) {
			$slug_values = $record['taxonomies']['taxonomies_slugs']['product_visibility'];
		}

		if ( empty( $slug_values ) ) {
			return $record;
		}

		$visibility_slugs = array();
		$rating_bucket    = null;

		foreach ( $slug_values as $slug ) {
			$slug = sanitize_key( (string) $slug );
			if ( preg_match( '/^rated-([1-5])$/', $slug, $matches ) ) {
				$rating_value  = (int) $matches[1];
				$rating_bucket = null === $rating_bucket ? $rating_value : max( $rating_bucket, $rating_value );
				continue;
			}
			$visibility_slugs[] = $slug;
		}

		$visibility_slugs = array_values( array_unique( array_filter( $visibility_slugs ) ) );

		if ( isset( $record['taxonomies']['taxonomies_slugs']['product_visibility'] ) ) {
			$record['taxonomies']['taxonomies_slugs']['product_visibility'] = $visibility_slugs;
		}

		if ( isset( $record['taxonomies']['taxonomies_names']['product_visibility'] ) && is_array( $record['taxonomies']['taxonomies_names']['product_visibility'] ) ) {
			$record['taxonomies']['taxonomies_names']['product_visibility'] = array_values(
				array_filter(
					array_map( 'sanitize_text_field', $record['taxonomies']['taxonomies_names']['product_visibility'] ),
					static function ( $name ) {
						return ! preg_match( '/^rated-[1-5]$/', sanitize_key( (string) $name ) );
					}
				)
			);
		}

		if ( isset( $record['taxonomies']['taxonomies']['product_visibility'] ) && is_array( $record['taxonomies']['taxonomies']['product_visibility'] ) ) {
			$record['taxonomies']['taxonomies']['product_visibility'] = array_values(
				array_filter(
					$record['taxonomies']['taxonomies']['product_visibility'],
					static function ( $term ) {
						$slug = '';
						if ( is_array( $term ) ) {
							$slug = isset( $term['slug'] ) ? (string) $term['slug'] : (string) ( $term['name'] ?? '' );
						}
						return ! preg_match( '/^rated-[1-5]$/', sanitize_key( $slug ) );
					}
				)
			);
		}

		if ( null !== $rating_bucket ) {
			$record['rating']        = $rating_bucket;
			$record['rating_bucket'] = (string) $rating_bucket;
		}

		return $record;
	}

	/**
	 * Add numeric mirror fields for WooCommerce prices while keeping original string values.
	 *
	 * @param array $record The record payload.
	 *
	 * @return array
	 */
	private function wlc_add_price_numeric_fields( array $record ): array {
		$price_fields = array( '_price', '_regular_price', '_sale_price' );
		foreach ( $price_fields as $price_field ) {
			if ( ! isset( $record[ $price_field ] ) ) {
				continue;
			}

			$raw = (string) $record[ $price_field ];
			if ( '' === trim( $raw ) ) {
				continue;
			}

			$normalized = str_replace( ',', '.', preg_replace( '/[^0-9.,-]/', '', $raw ) );
			if ( ! is_numeric( $normalized ) ) {
				continue;
			}

			$record[ $price_field . '_num' ] = (float) $normalized;
		}

		return $record;
	}

	/**
	 * Handle attributes type display.
	 *
	 * @param string $type The type of attribute/field.
	 * @param string $field Post field/attribute.
	 *
	 * @return mixed
	 */
	private function wlc_handle_cpt_type( string $type, string $field ): mixed { //phpcs:ignore
		$output = '';
		switch ( $type ) {
			case 'integer':
				$output = (int) $field;
				break;
			case 'float':
				$output = (float) $field;
				break;
			case 'array':
				$output = (array) $field;
				break;
			case 'string':
				$output = $field;
				break;
		}

		return $output;
	}

	/**
	 * Prepare appropriate taxonomies for post.
	 *
	 * @param \WP_Post $post WP Post.
	 *
	 * @return array
	 */
	private function wlc_prepare_taxonomies( \WP_Post $post ): array {
		$taxonomies = get_object_taxonomies( $post->post_type, 'objects' );

		return $this->wlc_prepare_taxonomies_attributes_for_indexing( $taxonomies, $post );
	}

	/**
	 * Prepare Attributes from taxonomies.
	 *
	 * @param array    $taxonomies Array of taxonomies.
	 * @param \WP_Post $post WP Post.
	 *
	 * @return array
	 */
	private function wlc_prepare_taxonomies_attributes_for_indexing( array $taxonomies, \WP_Post $post ): array {
		$attributes = array();
		foreach ( $taxonomies as $taxonomy ) {
			$terms = wp_get_object_terms( $post->ID, $taxonomy->name );
			if ( ! empty( $terms ) ) {
				// Merge taxonomy attributes across multiple taxonomies (do not overwrite).
				$attributes = array_merge_recursive(
					$attributes,
					$this->wlc_prepare_taxonomies_attributes( $terms, $taxonomy )
				);
			}
		}

		return $attributes;
	}

	/**
	 * Prepare Attributes from taxonomies.
	 *
	 * @param array        $terms Array of terms.
	 * @param \WP_Taxonomy $taxonomy WP Taxonomy.
	 *
	 * @return array
	 */
	private function wlc_prepare_taxonomies_attributes( array $terms, \WP_Taxonomy $taxonomy ): array {
		$attributes = array();
		foreach ( $terms as $term ) {
			$attributes['taxonomies'][ $taxonomy->name ][] = array(
				'ID'   => esc_attr( $term->term_id ),
				'name' => esc_attr( $term->name ),
				'slug' => esc_attr( $term->slug ),
				'url'  => esc_url( home_url() . '/' . esc_attr( $term->name ) . '/' . esc_attr( $term->slug ) ),
			);
			// Flat arrays for Algolia faceting + simpler filters UI.
			$attributes['taxonomies_slugs'][ $taxonomy->name ][] = esc_attr( $term->slug );
			$attributes['taxonomies_names'][ $taxonomy->name ][] = esc_attr( $term->name );
		}

		return $attributes;
	}

	/**
	 * Indexes a batch of posts into the specified Algolia index.
	 *
	 * @param array  $data_batch     The batch of data to be indexed.
	 * @param string $index_name     The name of the Algolia index.
	 * @param int    $batch_number   The current batch number.
	 * @param int    $total_batches  The total number of batches.
	 * @param string $cpt_section    Optional. The custom post type section.
	 *
	 * @return array  An array containing the status of the indexing operation and relevant information.
	 */
	public function index_post_batch( array $data_batch, string $index_name, int $batch_number, int $total_batches, string $cpt_section = '' ): array {
		foreach ( $data_batch as $i => $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}
			$pid = $row['objectID'] ?? $row['post_id'] ?? null;
			if ( null !== $pid && '' !== $pid && is_numeric( $pid ) ) {
				$canonical                    = (string) (int) $pid;
				$data_batch[ $i ]['objectID'] = $canonical;
				$data_batch[ $i ]['post_id']  = $canonical;
			}
		}

		$client = $this->create_client();
		if ( null === $client ) {
			return array(
				'status'  => 'failed',
				'message' => esc_html__( 'There was an error during batch indexing.', 'wlc-search-integration-with-algolia' ),
				'posts'   => array(),
			);
		}

		$response     = $client->saveObjects( $index_name, $data_batch );
		$progress     = ( $batch_number / $total_batches ) * 100;
		$progress_key = 'wlc_algolia_index_progress';
		if ( '' !== $cpt_section ) {
			$progress_key .= '_' . sanitize_key( $cpt_section );
		}
		update_option( $progress_key, $progress );

		$indexed_posts = array_map(
			function ( $post ) {
				return array(
					'post_id'    => $post['objectID'],
					'post_title' => $post['title'],
				);
			},
			$data_batch
		);

		if ( $response ) {
			// Save facetes settings on first batch to ensure they're available immediately.
			// Algolia's setSettings is idempotent, so calling it multiple times is safe.
			// This ensures settings are available even if indexing is interrupted.
			if ( $cpt_section && ( 1 === $batch_number || $batch_number === $total_batches ) ) {
				$this->wlc_save_facetes( $client, $index_name, $cpt_section );
			}
			return array(
				'status'        => 'success',
				'message'       => esc_html__( 'Batch index completed successfully', 'wlc-search-integration-with-algolia' ),
				'indexed_posts' => $indexed_posts,
				'total_posts'   => count( $data_batch ),
			);
		} else {
			return array(
				'status'  => 'failed',
				'message' => esc_html__( 'There was an error during batch indexing.', 'wlc-search-integration-with-algolia' ),
				'posts'   => $indexed_posts,
			);
		}
	}
}
