<?php
declare( strict_types=1 );

namespace FiboFilters\Filters;

use FiboFilters\Config\Env;
use FiboFilters\Filters\SourceTypes\SourceType;
use FiboFilters\Filters\SourceTypes\SourceTypeInterface;
use FiboFilters\Index\Sources;
use FiboFilters\Sorting\Sorting;
use FiboFilters\Struct\QueryContext;
use FiboFilters\WooCommerce\Helper as WooCommerceHelper;
use FiboFilters\WP\Helper as WordPressHelper;

/**
 * Class FilterSources
 */
class FilterSources {

	/**
	 * Sources for filters
	 *
	 * @var null|SourceTypes\SourceTypeInterface[]
	 */
	private $sources = null;

	/**
	 * Indexes of sources in the sources array.
	 *
	 * @var null|string[]
	 */
	private $sources_indexes = null;

	/** @var Sources */
	private Sources $index_sources;

	/** @var WooCommerceHelper */
	private $wc_helper;

	/** @var WordPressHelper */
	private $wp_helper;

	/** @var Sorting */
	private $sorting;

	public function __construct( WooCommerceHelper $woocommerce_helper, WordPressHelper $wp_helper, Sources $index_sources, Sorting $sorting ) {
		$this->wc_helper     = $woocommerce_helper;
		$this->wp_helper     = $wp_helper;
		$this->index_sources = $index_sources;
		$this->sorting       = $sorting;
	}

	public function get_source( string $source, bool $with_analysis_data = false ): SourceTypeInterface {
		$sources = $this->get_sources_raw();
		if ( isset( $this->sources_indexes[ $source ] ) && isset( $sources[ $this->sources_indexes[ $source ] ] ) ) {
			if ( $with_analysis_data ) {
				$source_analysis_data = get_transient( Env::$plugin_prefix . 'sources_analyse_data' );
				if ( isset( $source_analysis_data[ $sources[ $this->sources_indexes[ $source ] ]->get_data_attr( 'value' ) ] ) ) {
					$sources[ $this->sources_indexes[ $source ] ]->set_value_analyse_data( $source_analysis_data[ $sources[ $this->sources_indexes[ $source ] ]->get_data_attr( 'value' ) ] );
				}
			}

			return $sources[ $this->sources_indexes[ $source ] ];
		}

		return new SourceTypes\SourceType( $this->wc_helper, [], false, false );
	}

	/**
	 * @return SourceTypeInterface[]
	 */
	public function get_sources(): array {
		return $this->get_sources_raw();
	}

	public function get_sources_data(): array {
		return array_map(
			function ( SourceTypeInterface $source ): array {
				return $source->get_data();
			},
			$this->get_sources()
		);
	}

	/**
	 * Get localized sources data
	 *
	 * Some of the data passed to the plugin settings contains translations and if the user's language
	 * is different from the website language, we must change the language, load the translation files
	 * again and then get this data.
	 *
	 * Thanks to this, these phrases are in the language of the website and not the user.
	 *
	 * @param bool $with_analysis_data With analysis data.
	 *
	 * @return array
	 */
	public function get_sources_data_localized( bool $with_analysis_data = false ): array {
		$restore_needed = $this->wp_helper->maybe_switch_to_locale();

		if ( $restore_needed ) {
			$this->reset_sources();
			$this->prepare_sources();
		}

		if ( $with_analysis_data ) {
			$this->add_cached_analysis_data();
		}

		$data = $this->get_sources_data();

		if ( $restore_needed ) {
			$this->wp_helper->restore_previous_locale();
		}

		return $data;
	}

	private function get_sources_raw(): array {
		if ( is_array( $this->sources ) ) {
			return $this->sources;
		}

		$this->prepare_sources();

		return (array) $this->sources;
	}

	public function reset_sources() {
		$this->sources = null;
	}

	public function get_sources_without_analysis_data(): array {
		$sources = [];

		foreach ( $this->get_sources() as $source ) {
			if ( empty( $source->get_data_attr( 'analyseData' ) ) ) {
				$sources[] = $source->get_data_attr( 'value' );
			}
		}

		return $sources;
	}

	private function add_cached_analysis_data(): void {
		$source_analysis_data = get_transient( Env::$plugin_prefix . 'sources_analyse_data' );
		if ( $source_analysis_data === false ) {
			return;
		}

		foreach ( $this->get_sources() as $source ) {
			if ( isset( $source_analysis_data[ $source->get_data_attr( 'value' ) ] ) ) {
				$source->set_value_analyse_data( $source_analysis_data[ $source->get_data_attr( 'value' ) ] );
			}
		}
	}

	private function prepare_sources(): void {
		$this->sources = [];

		if ( taxonomy_exists( 'product_cat' ) ) {
			$this->sources[] = new SourceTypes\Taxonomy(
				$this->wc_helper,
				[
					'label'                 => _x( 'Categories', '[admin]', 'fibofilters' ),
					'value'                 => 'tax_product_cat',
					'sourceType'            => 'taxonomy',
					'rawValue'              => 'product_cat',
					'allowedTypes'          => [
						'category',
						'category-hierarchical',
						'checkboxes',
						'select',
					],
					'overwrittenAttributes' => [
						'label' => _x( 'Categories', '[front]', 'fibofilters' ),
					],
					'nonEditableAttributes' => [],
					'orderByTermOrder'      => $this->wc_helper->is_taxonomy_sortable( 'product_cat' ),
					'userInputType'         => 'default',
				],
				true,
				false
			);
		}

		if ( taxonomy_exists( 'product_tag' ) ) {
			$this->sources[] = new SourceTypes\Taxonomy(
				$this->wc_helper,
				[
					'label'                 => _x( 'Tag', '[admin]', 'fibofilters' ),
					'value'                 => 'tax_product_tag',
					'sourceType'            => 'taxonomy',
					'rawValue'              => 'product_tag',
					'allowedTypes'          => [
						'checkboxes',
						'buttons',
						'radio',
						'select',
					],
					'overwrittenAttributes' => [
						'label' => _x( 'Tag', '[front]', 'fibofilters' ),
					],
					'nonEditableAttributes' => [],
					'orderByTermOrder'      => $this->wc_helper->is_taxonomy_sortable( 'product_tag' ),
					'userInputType'         => 'default',
				],
				true,
				false
			);
		}

		foreach ( $this->wc_helper->get_attribute_taxonomies() as $attribute ) {
			$this->sources[] = new SourceTypes\Taxonomy(
				$this->wc_helper,
				[
					/* translators: %s: attribute label */
					'label'                 => sprintf( _x( 'Attribute: %s', '[admin]', 'fibofilters' ), $attribute->attribute_label ),
					'value'                 => 'tax_pa_' . $attribute->attribute_name,
					'sourceType'            => 'taxonomy',
					'rawValue'              => 'pa_' . $attribute->attribute_name,
					'allowedTypes'          => [
						'checkboxes',
						'buttons',
						'radio',
						'select',
						'color',
					],
					'overwrittenAttributes' => [
						'label' => $attribute->attribute_label,
					],
					'nonEditableAttributes' => [
						'multivalue_range_logic',
					],
					'orderByTermOrder'      => true,
					'userInputType'         => 'default',
				],
				true,
				true
			);
		}

		$this->sources[] = new SourceTypes\Price(
			$this->wc_helper,
			[
				'label'                 => _x( 'Price', '[admin]', 'fibofilters' ),
				'value'                 => 'price',
				'sourceType'            => 'price',
				'rawValue'              => '_price',
				'allowedTypes'          => [
					'range',
				],
				'overwrittenAttributes' => [
					'label'        => _x( 'Price', '[front]', 'fibofilters' ),
					'when_to_show' => 'always',
				],
				'nonEditableAttributes' => [
					'when_to_show',
					'use_global_number_format',
					'multivalue_range_logic',
				],
				'orderByTermOrder'      => false,
				'userInputType'         => 'price_range',
			],
			true,
			false
		);

		$this->sources[] = new SourceTypes\StockStatus(
			$this->wc_helper,
			[
				'label'                 => _x( 'Stock status', '[admin]', 'fibofilters' ),
				'value'                 => 'cf__stock_status',
				'sourceType'            => 'custom_field',
				'rawValue'              => '_stock_status',
				'allowedTypes'          => [
					'radio',
					'select',
					'toggle',
					'checkboxes',
				],
				'overwrittenAttributes' => [
					'label' => _x( 'Stock status', '[front]', 'fibofilters' ),
				],
				'nonEditableAttributes' => [
					'logic',
				],
				'orderByTermOrder'      => false,
				'userInputType'         => 'default',
			],
			true,
			false
		);

		$this->sources[] = new SourceTypes\OnSale(
			$this->wc_helper,
			[
				'label'                 => _x( 'On sale', '[admin]', 'fibofilters' ),
				'value'                 => 'on_sale',
				'sourceType'            => 'on_sale',
				'rawValue'              => '',
				'allowedTypes'          => [
					'toggle',
					'checkboxes',
				],
				'overwrittenAttributes' => [
					'label'        => _x( 'On sale', '[front]', 'fibofilters' ),
					'when_to_show' => 'always',
				],
				'nonEditableAttributes' => [
					'logic',
					'label_in_applied_filters',
					'when_to_show',
					'orderby',
				],
				'orderByTermOrder'      => false,
				'userInputType'         => 'default',
			],
			true,
			false
		);

		$this->sources[] = new SourceTypes\Search(
			$this->wc_helper,
			[
				'label'                 => _x( 'Search', '[admin]', 'fibofilters' ),
				'value'                 => 'search',
				'sourceType'            => 'search',
				'rawValue'              => '',
				'allowedTypes'          => [
					'search',
				],
				'overwrittenAttributes' => [
					'label'        => _x( 'Search', '[front]', 'fibofilters' ),
					'when_to_show' => 'always',
				],
				// If FiboSearch Pro is active, allow to change logic, because it is used as engine selector.
				'nonEditableAttributes' => did_action( 'fibofilters/integrations/plugin/late_init/fibosearch_pro' ) > 0 ? [] : [ 'logic' ],
				'orderByTermOrder'      => false,
				'userInputType'         => 'search',
				'hidden'                => true,
				'sourceAsType'          => 'search',
			],
			false,
			false
		);

		if ( wc_review_ratings_enabled() ) {
			$this->sources[] = new SourceTypes\CustomField(
				$this->wc_helper,
				[
					'label'                 => _x( 'Average rating', '[admin]', 'fibofilters' ),
					'value'                 => 'cf__wc_average_rating',
					'sourceType'            => 'custom_field',
					'rawValue'              => '_wc_average_rating',
					'allowedTypes'          => [
						'range',
					],
					'overwrittenAttributes' => [
						'label' => _x( 'Average rating', '[front]', 'fibofilters' ),
					],
					'nonEditableAttributes' => [
						'multivalue_range_logic',
					],
					'orderByTermOrder'      => false,
					'userInputType'         => 'default',
					'hidden'                => true,
				],
				true,
				false
			);
		}

		$this->sources[] = new SourceTypes\Featured(
			$this->wc_helper,
			[
				'label'                 => _x( 'Featured', '[admin]', 'fibofilters' ),
				'value'                 => 'featured',
				'sourceType'            => 'featured',
				'rawValue'              => '',
				'allowedTypes'          => [
					'toggle',
					'checkboxes',
				],
				'overwrittenAttributes' => [
					'label'        => _x( 'Featured', '[front]', 'fibofilters' ),
					'when_to_show' => 'always',
				],
				'nonEditableAttributes' => [
					'logic',
					'label_in_applied_filters',
					'when_to_show',
					'orderby',
				],
				'orderByTermOrder'      => false,
				'userInputType'         => 'default',
			],
			true,
			false
		);

		$this->register_custom_sources();

		$this->sources         = apply_filters( 'fibofilters/filters/prepared_sources', $this->sources, $this->wc_helper );
		$this->sources_indexes = array_flip(
			array_map(
				function ( $source ) {
					return $source->get_data_attr( 'value' );
				},
				$this->sources
			)
		);
	}

	public function get_sources_for_ordering( $with_source_id = false, $language = '' ): array {
		$sources_for_ordering = [
			'order_by_price'    => [ 'type' => 'range' ],
			'menu_order'        => [ 'type' => 'range' ],
			'order_by_date'     => [ 'type' => 'range' ],
			'order_by_modified' => [ 'type' => 'range' ],
			'order_by_sales'    => [ 'type' => 'range' ],
		];

		$allowed_catalog_orderby = apply_filters( 'fibofilters/allowed_catalog_orderby', [] );
		if (
			is_array( $allowed_catalog_orderby ) &&
			(
				in_array( 'title', $allowed_catalog_orderby, true ) ||
				in_array( 'title-desc', $allowed_catalog_orderby, true )
			)
		) {
			$sources_for_ordering['search'] = [ 'type' => 'search' ];
		}

		if ( wc_review_ratings_enabled() ) {
			$sources_for_ordering['cf__wc_average_rating'] = [ 'type' => 'range' ];
			$sources_for_ordering['ratings_count']         = [ 'type' => 'range' ];
		}

		// Custom sort options.
		foreach ( $this->sorting->get_custom_sort_options() as $sort_option ) {
			if ( $sort_option['source'] === 'postmeta' && $sort_option['type'] === 'numeric' ) {
				$sources_for_ordering[ 'cf__' . $sort_option['postmeta_key'] ] = [ 'type' => 'range' ];
			}
		}

		if ( $with_source_id ) {
			$ctx = new QueryContext( $language );
			foreach ( $sources_for_ordering as $key => $data ) {
				$sources_for_ordering[ $key ]['source_id'] = $this->index_sources->get_source_id_by_name( $key, $ctx );
			}
		}

		return $sources_for_ordering;
	}

	/**
	 * Get map of orderby keys to source ids
	 *
	 * We need this map to match orderby keys and data in descriptors, witch are indexed by source id.
	 *
	 * @param string $language Language.
	 */
	public function get_orderby_sources_map( $language = '' ): array {
		$catalog_orderby        = $this->sorting->get_catalog_orderedby();
		$ctx                    = new QueryContext( $language );
		$sources_for_ordering   = $this->get_sources_for_ordering( true, $language );
		$orderby_source_ids_map = [];
		foreach ( $catalog_orderby as $orderby_key => $orderby_label ) {
			switch ( $orderby_key ) {
				case 'menu_order':
					$source             = 'menu_order';
					$order              = 'asc';
					$orderby_product_id = false;
					break;
				case 'popularity':
					$source             = 'order_by_sales';
					$order              = 'desc';
					$orderby_product_id = 'desc';
					break;
				case 'rating':
					$source             = 'cf__wc_average_rating';
					$order              = 'desc';
					$orderby_product_id = 'desc';
					break;
				case 'ratings_count':
					$source             = 'ratings_count';
					$order              = 'desc';
					$orderby_product_id = 'desc';
					break;
				case 'date':
					$source             = 'order_by_date';
					$order              = 'desc';
					$orderby_product_id = 'desc';
					break;
				case 'price':
					$source             = 'order_by_price';
					$order              = 'asc';
					$orderby_product_id = 'asc';
					break;
				case 'price-desc':
					$source             = 'order_by_price';
					$order              = 'desc';
					$orderby_product_id = 'desc';
					break;
				case 'modified-desc':
					$source             = 'order_by_modified';
					$order              = 'desc';
					$orderby_product_id = false;
					break;
				case 'title':
					$source             = 'search';
					$order              = 'asc';
					$orderby_product_id = false;
					break;
				case 'title-desc':
					$source             = 'search';
					$order              = 'desc';
					$orderby_product_id = false;
					break;
				default:
					$source             = '';
					$order              = '';
					$orderby_product_id = false;
			}

			// Custom sort options.
			if ( empty( $source ) && empty( $order ) ) {
				list( $source, $order, $orderby_product_id ) = $this->sorting->get_custom_orderby_source_data( $orderby_key );
			}

			$orderby_source_ids_map[ $orderby_key ] = isset( $sources_for_ordering[ $source ] ) ? [
				'source_id'          => $sources_for_ordering[ $source ]['source_id'],
				'order'              => $order,
				'orderby_product_id' => $orderby_product_id,
			] : false;
		}

		if ( apply_filters( 'fibofilters/request/product_order/featured_first', false ) ) {
			$orderby_source_ids_map['order_by_featured'] = [
				'source_id' => $this->index_sources->get_source_id_by_name( 'order_by_featured', $ctx ),
			];
		}
		if ( apply_filters( 'fibofilters/request/product_order/in_stock_first', false ) ) {
			$orderby_source_ids_map['order_by_stock_status'] = [
				'source_id' => $this->index_sources->get_source_id_by_name( 'order_by_stock_status', $ctx ),
			];
		}

		return $orderby_source_ids_map;
	}

	/**
	 * Allow to register custom sources in easier way.
	 *
	 * @return void
	 */
	private function register_custom_sources(): void {

		/**
		 * Taxonomies
		 */
		$cptax_taxonomies = apply_filters( 'fibofilters/filters/custom_sources/taxonomies', [] );

		foreach ( $cptax_taxonomies as $args ) {
			$taxonomy = $args['taxonomy'];

			if ( empty( $taxonomy ) || ! taxonomy_exists( $taxonomy ) ) {
				continue;
			}

			$taxonomy_obj = get_taxonomy( $taxonomy );

			if ( empty( $args['label'] ) ) {
				/* translators: %s: custom taxonomy name  */
				$args['label'] = sprintf( _x( 'Custom taxonomy: %s', '[admin]', 'fibofilters' ), $taxonomy_obj->labels->name );
			}

			if ( empty( $args['label_front'] ) ) {
				$args['label_front'] = $taxonomy_obj->labels->name;
			}

			$this->sources[] = new SourceTypes\Taxonomy(
				$this->wc_helper,
				[
					'label'                 => $args['label'],
					'value'                 => 'tax_' . $taxonomy,
					'sourceType'            => 'taxonomy',
					'rawValue'              => $taxonomy,
					'allowedTypes'          => [
						'checkboxes',
						'buttons',
						'radio',
						'select',
					],
					'overwrittenAttributes' => [
						'label' => $args['label_front'],
					],
					'nonEditableAttributes' => [
						'multivalue_range_logic',
					],
					'orderByTermOrder'      => true,
					'userInputType'         => 'default',
				],
				true,
				true
			);
		}

		/**
		 * Custom fields
		 */
		$custom_fields = apply_filters( 'fibofilters/filters/custom_sources/custom_fields', [] );

		foreach ( $custom_fields as $args ) {
			$args = wp_parse_args(
				$args,
				[
					'field_slug'  => '',
					'label'       => '',
					'label_front' => '',
				]
			);

			$args['field_slug'] = sanitize_key( $args['field_slug'] );

			if ( empty( $args['field_slug'] ) ) {
				continue;
			}

			if ( empty( $args['label'] ) ) {
				/* translators: %s: custom field slug  */
				$args['label'] = sprintf( _x( 'Custom field: %s', '[admin]', 'fibofilters' ), $args['field_slug'] );
			}

			if ( empty( $args['label_front'] ) ) {
				$args['label_front'] = ucfirst( rtrim( ltrim( $args['field_slug'], '_' ), '_' ) );
			}

			$this->sources[] = new SourceTypes\CustomField(
				$this->wc_helper,
				[
					'label'                 => $args['label'],
					'value'                 => 'cf_' . $args['field_slug'],
					'sourceType'            => 'custom_field',
					'rawValue'              => $args['field_slug'],
					'allowedTypes'          => [
						'checkboxes',
						'radio',
						'select',
						'color',
					],
					'overwrittenAttributes' => [
						'label' => $args['label_front'],
					],
					'nonEditableAttributes' => [
						'multivalue_range_logic',
					],
					'orderByTermOrder'      => false,
					'userInputType'         => 'default',
				],
				true,
				true
			);
		}

		/**
		 * Computed sources
		 */
		$computed_sources = apply_filters( 'fibofilters/filters/custom_sources/computed', [] );
		foreach ( $computed_sources as $args ) {
			$args = wp_parse_args(
				$args,
				[
					'computed'    => '',
					'label'       => '',
					'label_front' => '',
					'type'        => '',
					'callback'    => null,
				]
			);

			$args['computed'] = sanitize_key( $args['computed'] );

			if ( empty( $args['computed'] ) || ! is_callable( $args['callback'] ) ) {
				continue;
			}

			if ( empty( $args['type'] ) || ! in_array(
				$args['type'],
				[
					'checkboxes',
					'buttons',
					'radio',
					'range',
					'range-multivalue',
					'toggle',
					'select',
					'color',
				],
				true
			) ) {
				continue;
			}

			if ( empty( $args['label'] ) ) {
				/* translators: %s: key of computed field */
				$args['label'] = sprintf( _x( 'Computed: %s', '[admin]', 'fibofilters' ), $args['computed'] );
			}

			if ( empty( $args['label_front'] ) ) {
				$args['label_front'] = ucfirst( $args['computed'] );
			}

			$non_editable_attributes = [ 'multivalue_range_logic' ];
			$overwritten_meta        = [];

			// "range-multivalue" is not real type, so we need to convert it to range and change logic.
			if ( $args['type'] === 'range-multivalue' ) {
				$args['type']              = 'range';
				$non_editable_attributes   = array_diff( $non_editable_attributes, [ 'multivalue_range_logic' ] );
				$overwritten_meta['logic'] = 'range-continuous';
			}

			$this->sources[] = new SourceTypes\SourceType(
				$this->wc_helper,
				[
					'label'                 => $args['label'],
					'value'                 => 'cmp_' . $args['computed'],
					'sourceType'            => 'computed',
					'rawValue'              => '',
					'allowedTypes'          => [ $args['type'] ],
					'overwrittenAttributes' => [
						'label' => $args['label_front'],
					],
					'overwrittenMeta'       => $overwritten_meta,
					'nonEditableAttributes' => $non_editable_attributes,
					'orderByTermOrder'      => false,
					'userInputType'         => 'default',
					'callback'              => $args['callback'],
				],
				false,
				false,
			);
		}
	}
}
