<?php

namespace CelerSearch\Indices;

defined( 'ABSPATH' ) || exit;

use CelerSearch\Builders\IndexSettingsBuilder;
use CelerSearch\DataTransfer\IndexCandidate;
use CelerSearch\DataTransfer\IndexSettings;
use CelerSearch\DataTransfer\IndexablePost;
use CelerSearch\Interfaces\IIndexableObject;
use CelerSearch\Utilities\PostUtilities;
use RuntimeException;

class ProductsIndex extends BasePostIndex {

	/**
	 * The allowed post types
	 * @var array
	 */
	protected array $post_types;

	/**
	 * Whether to exclude sold out products
	 * @var bool
	 */
	protected bool $exclude_sold_out = false;

	/**
	 * Whether to exclude products with restricted catalog visibility
	 * @var bool
	 */
	protected bool $exclude_catalog_visibility = false;

	/**
	 * Performs additional initialization (if needed)
	 * @return void
	 */
	protected function init(): void {
		$config           = $this->details->getConfig();
		$this->post_types = isset( $config->post_types ) ? $config->post_types : array( 'product' );

		// If post_types is an object (due to array to object conversion), convert back to array
		if ( is_object( $this->post_types ) ) {
			$this->post_types = (array) $this->post_types;
		}

		// Get filtering options
		$this->exclude_sold_out            = isset( $config->exclude_sold_out ) ? (bool) $config->exclude_sold_out : false;
		$this->exclude_catalog_visibility = isset( $config->exclude_catalog_visibility ) ? (bool) $config->exclude_catalog_visibility : false;
	}

	/**
	 * @inheritDoc
	 */
	public function get_sort_field_map(): array {
		return [ 'date' => 'post_date', 'title' => 'post_title' ];
	}

	/**
	 * Check whether an item is supported.
	 *
	 * @param IndexCandidate $item
	 *
	 * @return bool
	 */
	public function is_supported( IndexCandidate $item ): bool {
		return $item && is_a( $item->item, \WP_Post::class ) && in_array( $item->item->post_type, $this->post_types, true );
	}

	/**
	 * Check whether an item can be indexed.
	 *
	 * @param IndexCandidate $item
	 *
	 * @return bool
	 */
	public function should_index( IndexCandidate $item ): bool {
		if ( ! $this->is_supported( $item ) ) {
			return false;
		}

		// Check if WooCommerce is active
		if ( ! function_exists( 'wc_get_product' ) ) {
			return false;
		}

		$product = wc_get_product( $item->item );

		if ( ! $product ) {
			return false;
		}

		// Option 1: Exclude sold out products
		if ( $this->exclude_sold_out && ! $product->is_in_stock() ) {
			return false;
		}

		// Option 2: Exclude products with restricted catalog visibility
		if ( $this->exclude_catalog_visibility ) {
			$visibility = $product->get_catalog_visibility();
			if ( in_array( $visibility, [ 'catalog', 'hidden' ], true ) ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Returns items from the index
	 *
	 * @param int $page
	 * @param int $batch_size
	 *
	 * @return array<IndexCandidate>
	 */
	public function get_candidates( int $page, int $batch_size ): array {
		$args = [
			'post_type'              => $this->post_types,
			'posts_per_page'         => $batch_size,
			'post_status'            => 'publish',
			'order'                  => 'ASC',
			'orderby'                => 'ID',
			'paged'                  => $page,
			// phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- Required to prevent recursive search filters during indexing
			'suppress_filters'       => true,
			'cache_results'          => false,
			'lazy_load_term_meta'    => false,
			'update_post_term_cache' => false,
		];

		return array_map( function( \WP_Post $item ) {
			return new IndexCandidate( $item );
		}, get_posts( $args ) );
	}

	/**
	 * Returns specific items by IDs
	 *
	 * @param array $ids
	 *
	 * @return array<IndexCandidate>
	 */
	public function get_candidates_by_ids( array $ids ): array {
		$args             = [
			'post_type'              => $this->post_types,
			'posts_per_page'         => count( $ids ),
			'post_status'            => 'publish',
			'order'                  => 'ASC',
			'orderby'                => 'ID',
			'paged'                  => 1,
			// phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- Required to prevent recursive search filters during indexing
			'suppress_filters'       => true,
			'cache_results'          => false,
			'lazy_load_term_meta'    => false,
			'update_post_term_cache' => false,
		];
		$args['post__in'] = (array) $ids;

		return array_map( function( \WP_Post $item ) {
			return new IndexCandidate( $item );
		}, get_posts( $args ) );
	}

	/**
	 * Returns items from the index
	 *
	 * @return int
	 */
	public function get_candidate_count(): int {

		$query = new \WP_Query(
			array(
				'post_type'              => $this->post_types,
				'post_status'            => 'publish',
				// phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- Required to prevent recursive search filters during indexing
				'suppress_filters'       => true,
				'cache_results'          => false,
				'lazy_load_term_meta'    => false,
				'update_post_term_cache' => false,
			)
		);

		return (int) $query->found_posts;
	}

	/**
	 * Returns the item records
	 *
	 * @param IndexCandidate $item
	 *
	 * @return IIndexableObject[]
	 */
	public function get_candidate_objects( IndexCandidate $item ): array {
		return $this->create_records( $item->item );
	}

	/**
	 * Creates product attributes
	 *
	 * @param  \WP_Post  $post
	 *
	 * @return IndexablePost[]
	 */
	private function create_records( \WP_Post $post ) {

		$product = wc_get_product( $post );

		if ( ! $product ) {
			return array();
		}

		$attributes = $this->get_attributes( $post, $product );

		$removed = remove_filter( 'the_content', 'wptexturize', 10 );

		$post_content = apply_filters( 'celersearch_searchable_product_content', $post->post_content, $post );
		$post_content = apply_filters( 'the_content', $post_content ); // phpcs:ignore -- Legitimate use of Core hook.

		if ( true === $removed ) {
			add_filter( 'the_content', 'wptexturize', 10 );
		}

		$post_content = PostUtilities::prepare_content( $post_content );
		$parts        = PostUtilities::explode_content( $post_content );

		if ( defined( 'CELERSEARCH_SPLIT_POSTS' ) && false === CELERSEARCH_SPLIT_POSTS ) {
			$parts = array( array_shift( $parts ) );
		}

		$records = array();
		foreach ( $parts as $i => $part ) {
			$record                 = $attributes;
			$object_id              = PostUtilities::get_post_object_id( $post->ID, $i );
			$record['object_id']    = $object_id;
			$record['content']      = $part;
			$record['record_index'] = $i;
			$records[]              = new IndexablePost( $object_id, $record );
		}

		$records = (array) apply_filters( 'celersearch_searchable_product_records', $records, $post );
		$records = (array) apply_filters( 'celersearch_searchable_product_' . $post->post_type . '_records', $records, $post );

		return $records;

	}

	/**
	 * Get product shared attributes.
	 *
	 * @param  \WP_Post  $post     The post to get shared attributes for.
	 * @param  \WC_Product  $product  The WooCommerce product object.
	 *
	 * @return array
	 *
	 * @throws \RuntimeException If post type information unknown.
	 */
	private function get_attributes( \WP_Post $post, \WC_Product $product ) {

		$attributes              = [];
		$attributes['post_id']   = $post->ID;
		$attributes['post_type'] = $post->post_type;

		$post_type = get_post_type_object( $post->post_type );
		if ( null === $post_type ) {
			throw new RuntimeException( 'Unable to fetch the post type information.' );
		}
		$attributes['post_type_label']     = $post_type->labels->name;
		$attributes['post_title']          = $post->post_title;
		$attributes['post_excerpt']        = PostUtilities::prepare_content( apply_filters( 'the_excerpt', $post->post_excerpt ) ); // phpcs:ignore -- Legitimate use of Core hook.
		$attributes['post_date']           = get_post_time( 'U', false, $post );
		$attributes['post_date_formatted'] = get_the_date( '', $post );
		$attributes['post_modified']       = get_post_modified_time( 'U', false, $post );
		$attributes['comment_count']       = (int) $post->comment_count;
		$attributes['menu_order']          = (int) $post->menu_order;

		$author = get_userdata( $post->post_author );
		if ( $author ) {
			$attributes['post_author'] = array(
				'user_id'      => (int) $post->post_author,
				'display_name' => $author->display_name,
				'user_url'     => $author->user_url,
				'user_login'   => $author->user_login,
			);
		}

		$attributes['images'] = PostUtilities::get_post_images( $post->ID );

		$attributes['permalink']      = get_permalink( $post );
		$attributes['post_mime_type'] = $post->post_mime_type;
		$attributes['status']         = $post->post_status;

		// Push all taxonomies by default, including custom ones.
		$taxonomy_objects = get_object_taxonomies( $post->post_type, 'objects' );

		$attributes['taxonomies']              = array();
		$attributes['taxonomies_hierarchical'] = array();
		foreach ( $taxonomy_objects as $taxonomy ) {
			$terms = wp_get_object_terms( $post->ID, $taxonomy->name );
			$terms = is_array( $terms ) ? $terms : array();

			if ( $taxonomy->hierarchical ) {
				$hierarchical_taxonomy_values = PostUtilities::get_taxonomy_tree( $terms, $taxonomy->name );
				if ( ! empty( $hierarchical_taxonomy_values ) ) {
					$attributes['taxonomies_hierarchical'][ $taxonomy->name ] = $hierarchical_taxonomy_values;
				}
			}

			$taxonomy_values = array_map( 'html_entity_decode', wp_list_pluck( $terms, 'name' ) );
			if ( ! empty( $taxonomy_values ) ) {
				$attributes['taxonomies'][ $taxonomy->name ] = $taxonomy_values;
			}
		}

		// WooCommerce specific fields (all required)

		// 1. SKU
		$attributes['sku'] = $product->get_sku();

		// 2. Short description
		$attributes['short_description'] = $product->get_short_description();

		// 3. Prices
		if ( $product instanceof \WC_Product_Variable ) {
			// Variable product - get min/max prices
			$attributes['price']                   = (float) $product->get_variation_price( 'min', true );
			$attributes['regular_price']           = (float) $product->get_variation_regular_price( 'min', true );
			$attributes['regular_price_formatted'] = wc_price( $attributes['regular_price'] );
			$attributes['max_price']               = (float) $product->get_variation_price( 'max', true );
			$attributes['max_price_formatted']     = wc_price( $attributes['max_price'] );
			$attributes['sale_price']              = (float) $product->get_variation_sale_price( 'min', true );
			$attributes['variations_count']        = count( $product->get_available_variations() );
			$attributes['variations']              = $this->get_variation_details( $product );
		} else {
			// Simple product
			$attributes['price']         = (float) $product->get_regular_price();
			$attributes['sale_price']    = (float) $product->get_sale_price();
			$attributes['regular_price'] = $attributes['price'];
		}

		// Format prices
		$attributes['price_formatted']      = wc_price( $attributes['price'] );
		$attributes['sale_price_formatted'] = $attributes['sale_price'] ? wc_price( $attributes['sale_price'] ) : '';

		// Currency symbol
		$currency_symbol                      = get_woocommerce_currency_symbol();
		$attributes['currency_symbol'] = html_entity_decode( $currency_symbol );

		// 4. Total sales
		$attributes['total_sales'] = $product->get_total_sales();

		// 5. Ratings
		$attributes['ratings'] = array(
			'total_ratings'  => 0,
			'average_rating' => 0,
		);
		if ( wc_review_ratings_enabled() ) {
			$attributes['ratings']['total_ratings']  = $product->get_rating_count();
			$attributes['ratings']['average_rating'] = (int) floor( $product->get_average_rating() );
		}

		// 6. Dimensions and weight
		$attributes['physical_attributes'] = array(
			'weight'     => '',
			'dimensions' => '',
		);

		if ( $product->has_weight() ) {
			$attributes['physical_attributes']['weight'] = wc_format_weight( $product->get_weight() );
		}

		if ( $product->has_dimensions() ) {
			$attributes['physical_attributes']['dimensions'] = wc_format_dimensions( $product->get_dimensions( false ) );
		}

		// Additional useful WooCommerce fields
		$attributes['in_stock']           = $product->is_in_stock();
		$attributes['stock_quantity']     = $product->get_stock_quantity();
		$attributes['catalog_visibility'] = $product->get_catalog_visibility();

		$attributes = (array) apply_filters( 'celersearch_searchable_product_shared_attributes', $attributes, $post );

		return (array) apply_filters( 'celersearch_searchable_product_' . $post->post_type . '_shared_attributes', $attributes, $post );

	}

	/**
	 * Create product index settings
	 *
	 * @return IndexSettings
	 */
	public function get_settings(): IndexSettings {

		$settings = ( new IndexSettingsBuilder )
			->from_stored( $this->get_stored_settings() )
			->primary_key( 'object_id' )
			->distinct_attribute( 'post_id' )
			->searchable_attributes( [
				'post_title:unordered',
				'sku:unordered',
				'short_description:unordered',
				'taxonomies:unordered',
				'content:unordered'
			] )
			->filterable_attributes( [
				'taxonomies',
				'taxonomies_hierarchical',
				'post_author.display_name',
				'post_type_label',
				'price',
				'sale_price',
				'in_stock',
				'ratings.average_rating',
				'catalog_visibility',
				'status',
			] )
			->sortable_attributes( [
				'post_date:desc',
				'price:asc',
				'total_sales:desc',
				'ratings.average_rating:desc',
				'record_index:asc',
				'post_title:asc',
			] )
			->snippet_attributes( [
				'post_title:30',
				'short_description:55',
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Applying WordPress core filter
				'content:' . intval( apply_filters( 'excerpt_length', 55 ) ),
			] );


		$settings = apply_filters( 'celersearch_products_index_settings', $settings, $this );

		return $settings->build();

	}

	/**
	 * Get variation details for indexing
	 *
	 * @param \WC_Product_Variable $product
	 *
	 * @return array
	 */
	private function get_variation_details( \WC_Product_Variable $product ): array {
		$variations = [];

		foreach ( $product->get_available_variations() as $variation_data ) {
			$variation = wc_get_product( $variation_data['variation_id'] );

			if ( ! $variation || ! $variation->is_purchasable() ) {
				continue;
			}

			// Build readable attribute string (e.g., "Blue, Large")
			$attribute_values = [];
			foreach ( $variation->get_attributes() as $attr => $value ) {
				if ( ! empty( $value ) && is_string( $value ) ) {
					$attribute_values[] = ucfirst( $value );
				}
			}

			$variations[] = [
				'id'         => $variation->get_id(),
				'title'      => implode( ', ', $attribute_values ),
				'attributes' => $variation->get_attributes(),
				'sku'        => $variation->get_sku(),
				'price'      => (float) $variation->get_price(),
				'price_html' => $variation->get_price_html(),
				'permalink'  => $variation->get_permalink(),
				'in_stock'   => $variation->is_in_stock(),
				'image'      => wp_get_attachment_image_url( $variation->get_image_id(), 'thumbnail' ),
			];
		}

		return apply_filters( 'celersearch_product_variations', $variations, $product );
	}
}
