<?php
declare( strict_types=1 );

namespace FiboFilters\Integrations\Multilingual;

use FiboFilters\Config\Env;
use FiboFilters\Filters\StoreFilters;
use FiboFilters\Indexer\SourceQuery;
use FiboFilters\Util\Helpers;
use FiboFilters\Util\Localization;

/**
 * Class WPML
 */
class WPML implements ProviderInterface {
	/** @var Localization */
	private $localization;

	/** @var StoreFilters */
	private $store_filters;

	public function __construct( Localization $localization, StoreFilters $store_filters ) {
		$this->localization  = $localization;
		$this->store_filters = $store_filters;
	}

	public function init(): void {
		add_filter( 'fibofilters/filters/prepared_sources', [ $this, 'adjust_sources' ], 10, 1 );

		add_filter( 'fibofilters/request/single_products/override_wp_the_query', '__return_true' );

		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
		if ( intval( apply_filters( 'wpml_sub_setting', false, 'custom_posts_sync_option', 'product' ) ) === 2 ) {
			add_filter(
				'fibofilters/indexer/filters_source_query/data',
				[
					$this,
					'add_fallback_data_for_products_without_translations',
				],
				15,
				3
			);
		}
	}

	public function is_active(): bool {
		return class_exists( 'SitePress' );
	}

	public function get_name(): string {
		return 'WPML';
	}

	public function get_languages( bool $include_invalid ): array {
		$include_hidden = apply_filters( 'fibofilters/multilingual/languages/include_hidden', false );

		$languages = [];

		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
		$wpml_languages = apply_filters( 'wpml_active_languages', null, [ 'skip_missing' => 0 ] );

		if ( is_array( $wpml_languages ) ) {
			foreach ( $wpml_languages as $language_code => $details ) {
				if ( Helpers::is_language_code( $language_code ) || $include_invalid ) {
					$languages[] = strtolower( $language_code );
				}
			}
		}

		if ( ! $include_hidden ) {
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
			$hidden_languages = apply_filters( 'wpml_setting', [], 'hidden_languages' );
			if ( ! empty( $hidden_languages ) && is_array( $hidden_languages ) ) {
				foreach ( $hidden_languages as $hidden_language ) {
					if ( ! Helpers::is_language_code( $hidden_language ) && $include_hidden ) {
						continue;
					}
					$languages = array_diff( $languages, [ $hidden_language ] );
				}
			}
		}

		return $languages;
	}

	public function get_current_language(): string {
		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
		return apply_filters( 'wpml_current_language', null );
	}

	public function get_default_language(): string {
		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
		$result = (string) apply_filters( 'wpml_default_language', null );

		return Helpers::is_language_code( $result ) ? $result : 'en';
	}

	public function get_post_language( int $post_id, string $post_type = 'product' ): string {
		global $wpdb;

		$language  = '';
		$post_type = 'post_' . $post_type;

		$sql = $wpdb->prepare(
			"SELECT language_code
                   FROM {$wpdb->prefix}icl_translations
                   WHERE element_type=%s
                   AND element_id=%d",
			sanitize_key( $post_type ),
			$post_id
		);
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
		$result = (string) $wpdb->get_var( $sql );

		if ( Helpers::is_language_code( $result ) ) {
			$language = $result;
		}

		return $language;
	}

	public function get_term_language( int $term_id, string $taxonomy ): string {
		global $wpdb;

		$language     = '';
		$element_type = 'tax_' . sanitize_key( $taxonomy );

		$query = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT language_code
                FROM {$wpdb->prefix}icl_translations
                WHERE element_type = %s
                AND element_id = %d",
				$element_type,
				$term_id
			)
		);

		if ( is_string( $query ) && Helpers::is_language_code( $query ) ) {
			$language = $query;
		}

		return $language;
	}

	public function get_permalink( string $post_id, string $permalink = '', string $language = '', string $default_language = '' ): string {
		if ( $default_language === $language ) {
			return $permalink;
		}

		/**
		 *  1 if the option is *Different languages in directories*
		 *  2 if the option is *A different domain per language*
		 *  3 if the option is *Language name added as a parameter*.
		 */
		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
		$url_type = apply_filters( 'wpml_setting', 0, 'language_negotiation_type' );
		if ( intval( $url_type ) === 3 ) {
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
			$permalink = apply_filters( 'wpml_permalink', $permalink, $language );
		} else {
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
			$permalink = apply_filters( 'wpml_permalink', $permalink, $language, true );
		}

		return $permalink;
	}

	public function get_terms_in_all_languages( string $taxonomy, array $languages, string $current_language ): array {
		$used_ids = [];
		$terms    = [];

		foreach ( $languages as $language ) {
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
			do_action( 'wpml_switch_language', $language );
			$args = [
				'taxonomy'         => $taxonomy,
				'hide_empty'       => true,
				'suppress_filters' => false,
			];
			// TODO Ten hook może trzeba zmienic na inny niż '/search/' ale w FiboSearch jest tez uzywany w innych miejscach.
			$terms_in_lang = get_terms( apply_filters( 'fibofilters/search/' . $taxonomy . '/args', $args ) );

			if ( empty( $terms_in_lang ) || ! is_array( $terms_in_lang ) ) {
				continue;
			}

			foreach ( $terms_in_lang as $term_in_lang ) {
				if ( isset( $term_in_lang->term_id ) && ! in_array( $term_in_lang->term_id, $used_ids, true ) ) {
					$terms[]    = $term_in_lang;
					$used_ids[] = $term_in_lang->term_id;
				}
			}
		}

		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
		do_action( 'wpml_switch_language', $current_language );

		return $terms;
	}

	public function get_terms_in_language( array $args = [], string $language = '', string $current_language = '' ): array {
		$used_ids = [];
		$terms    = [];

		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
		do_action( 'wpml_switch_language', $language );
		$args = wp_parse_args(
			$args,
			[
				'taxonomy'         => '',
				'hide_empty'       => true,
				'suppress_filters' => false,
			]
		);

		// TODO Ten filtr moze trzeba zmienic, tj. '/search/' ale jest tez uzywany w innych miejscach.
		$taxonomy      = $args['taxonomy'];
		$terms_in_lang = get_terms( apply_filters( "fibofilters/search/{$taxonomy}/args", $args ) );

		if ( ! empty( $terms_in_lang ) && is_array( $terms_in_lang ) ) {
			foreach ( $terms_in_lang as $term_in_lang ) {
				if ( isset( $term_in_lang->term_id ) && ! in_array( $term_in_lang->term_id, $used_ids, true ) ) {
					$terms[]    = $term_in_lang;
					$used_ids[] = $term_in_lang->term_id;
				}
			}
		}

		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
		do_action( 'wpml_switch_language', $current_language );

		return $terms;
	}

	public function get_term( int $term_id, string $taxonomy, string $language, string $current_language = '' ): ?\WP_Term {
		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
		do_action( 'wpml_switch_language', $language );

		$term = get_term( $term_id, $taxonomy );

		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
		do_action( 'wpml_switch_language', $current_language );

		return is_object( $term ) && is_a( $term, 'WP_Term' ) ? $term : null;
	}

	public function switch_language( string $language = '' ): void {
		if ( ! empty( $language ) ) {
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
			do_action( 'wpml_switch_language', $language );
		}
	}

	public function register_strings_for_translation(): void {
		$filters = $this->store_filters->get_all_filters();

		foreach ( $filters as $filter ) {
			// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
			do_action( 'wpml_register_single_string', Env::$plugin_slug, $filter['label'], $filter['label'] );
			if ( $filter['type'] === 'category' ) {
				do_action( 'wpml_register_single_string', Env::$plugin_slug, $filter['label_category'], $filter['label_category'] );
				do_action( 'wpml_register_single_string', Env::$plugin_slug, $filter['label_subcategory'], $filter['label_subcategory'] );
			}
			if ( $filter['type'] === 'radio' && ! empty( $filter['any_label'] ) ) {
				do_action( 'wpml_register_single_string', Env::$plugin_slug, $filter['any_label'], $filter['any_label'] );
			}
			// phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
		}

		// Register string from JSON file with frontend texts.
		$file_path    = sprintf( '%s/fibofilters-%s-%s.json', Env::$languages_path, 'en_US', md5( 'build/front-pro/front.js' ) );
		$translations = $this->localization->get_translations_from_file( $file_path );
		if ( ! empty( $translations ) ) {
			foreach ( $translations as $key => $translation ) {
				if ( empty( $translation ) ) {
					continue;
				}
				$original = $key;
				$chunks   = explode( "\x04", $original, 2 );
				if ( isset( $chunks[1] ) ) {
					$context  = $chunks[0];
					$original = $chunks[1];
				} else {
					$context = '';
				}

				do_action(
					'wpml_register_single_string', // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
					empty( $context ) ? Env::$plugin_slug : [
						'domain'  => Env::$plugin_slug,
						'context' => $context,
					],
					$original,
					$original
				);
			}
		}
	}

	public function perform_frontend_actions(): void {
		$this->translate_strings();
		$this->adjust_config();
	}

	private function translate_strings(): void {
		add_filter(
			'fibofilters/filter/get_data',
			function ( $data, $context ) {
				if ( $context === 'frontend-filtering' ) {
					// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
					$data['label'] = apply_filters( 'wpml_translate_single_string', $data['label'], Env::$plugin_slug, $data['label'] );
					if ( $data['type'] === 'category' ) {
						$data['label_category']    = apply_filters( 'wpml_translate_single_string', $data['label_category'], Env::$plugin_slug, $data['label_category'] );
						$data['label_subcategory'] = apply_filters( 'wpml_translate_single_string', $data['label_subcategory'], Env::$plugin_slug, $data['label_subcategory'] );
					}
					// phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
				}
				return $data;
			},
			10,
			2
		);

		// Translate the phrase from the filter settings for "Any" value.
		add_filter(
			'fibofilters/filter/radio/any_label',
			function ( $any_label ) {
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
				return apply_filters( 'wpml_translate_single_string', $any_label, Env::$plugin_slug, $any_label );
			}
		);

		add_filter(
			'fibofilters/i18n/front_translations',
			function ( $translations ) {
				foreach ( $translations as $key => $translation ) {
					if ( empty( $key ) || ! is_array( $translation ) ) {
						continue;
					}
					// Skip _n() translations.
					if ( count( $translation ) > 1 ) {
						continue;
					}

					$original = $key;
					$chunks   = explode( "\x04", $original, 2 );

					if ( isset( $chunks[1] ) ) {
						$context  = $chunks[0];
						$original = $chunks[1];
					} else {
						$context = '';
					}

					$wpml_translation = apply_filters(
						'wpml_translate_single_string', // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
						$original,
						[
							'domain'  => Env::$plugin_slug,
							'context' => $context,
						],
						$original
					);

					// Override only if translation from WPML is different.
					if ( $wpml_translation !== $translation[0] ) {
						$translations[ $key ] = [ $wpml_translation ];
					}
				}

				return $translations;
			},
			5
		);
	}

	private function adjust_config() {
		global $sitepress;

		/**
		 * Fix the URL of the descriptor JSON file to be in a subdomain of the current language.
		 */
		if ( is_callable( [ $sitepress, 'convert_url' ] ) && (int) $sitepress->get_setting( 'language_negotiation_type' ) === 2 ) {
			add_filter(
				'fibofilters/config/descriptors_json_url',
				function ( $url ) use ( $sitepress ) {
					if ( strpos( $url, '.json' ) !== false ) {
						return $sitepress->convert_url( $url, $this->get_current_language() );
					}
					return $url;
				},
				20
			);
		}

		/**
		 * Add a language to the endpoints when it is added as a parameter.
		 *
		 * With the default language, the "lang" parameter is not added and if we currently have a different
		 * language in the Dashboard, WPML in the REST API query does not recognize the default one,
		 * only the other language. That's why we add "lang" even for the default language.
		 */
		if ( is_callable( [ $sitepress, 'convert_url' ] ) && (int) $sitepress->get_setting( 'language_negotiation_type' ) === 3 ) {
			add_filter(
				'fibofilters/config/descriptors_endpoint',
				function ( $url ) use ( $sitepress ) {
					if ( $this->get_current_language() === $this->get_default_language() ) {
						return add_query_arg( [ 'lang' => $this->get_current_language() ], $url );
					}
					return $sitepress->convert_url( $url, $this->get_current_language() );
				}
			);
			add_filter(
				'fibofilters/config/updated_descriptors_endpoint',
				function ( $url ) use ( $sitepress ) {
					if ( $this->get_current_language() === $this->get_default_language() ) {
						return add_query_arg( [ 'lang' => $this->get_current_language() ], $url );
					}
					return $sitepress->convert_url( $url, $this->get_current_language() );
				}
			);
		}
	}

	public function adjust_sources( $sources ) {
		/**
		 * If WooCommerce Multilingual & Multicurrency is active we need to adjust the source for the average rating,
		 * because values are stored with different meta keys.
		 */
		if ( class_exists( 'woocommerce_wpml' ) ) {
			foreach ( $sources as $source ) {
				if ( $source->get_data_attr( 'value' ) === 'cf__wc_average_rating' ) {
					$source->set_data_attr( 'rawValue', '_wcml_average_rating' );
				}
			}
		}

		return $sources;
	}

	/**
	 * Add fallback data for products without translations
	 *
	 * WPML >> Settings >> Post Types Translation >> Product: Translatable (use translation if available or fallback to default language)
	 *
	 * @param array       $rows Source query data.
	 * @param SourceQuery $source_query SourceQuery object.
	 * @param string      $select_type Type of select.
	 */
	public function add_fallback_data_for_products_without_translations( $rows, $source_query, $select_type ): array {
		if ( $select_type !== 'data' ) {
			return $rows;
		}

		$languages = $this->get_languages( false );
		$languages = array_diff( $languages, [ $this->get_default_language() ] );

		if ( empty( $languages ) ) {
			return $rows;
		}

		$new_rows = [];

		foreach ( $rows as $row ) {
			if ( $row['lang'] !== $this->get_default_language() ) {
				continue;
			}

			$type = empty( $row['parent_id'] ) ? 'product' : 'product_variation';

			foreach ( $languages as $language ) {
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
				$translated_object_id = apply_filters( 'wpml_object_id', $row['ID'], $type, false, $language );
				if ( ! is_null( $translated_object_id ) ) {
					continue;
				}

				$new_row         = $row;
				$new_row['lang'] = $language;
				foreach ( $new_row as $key => $values ) {
					if ( strpos( $key, 'tax_' ) !== 0 || empty( $values ) ) {
						continue;
					}

					foreach ( $values as $value_i => $value ) {
						$taxonomy = str_replace( 'tax_', '', $key );
						// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
						$translated_object_id = apply_filters( 'wpml_object_id', intval( $value['value'] ), $taxonomy, false, $language );
						if ( empty( $translated_object_id ) ) {
							continue;
						}

						$term = $this->get_term( $translated_object_id, $taxonomy, $language );
						if ( ! empty( $term ) ) {
							$new_row[ $key ][ $value_i ]['value']         = (string) $translated_object_id;
							$new_row[ $key ][ $value_i ]['label']         = $term->name;
							$new_row[ $key ][ $value_i ]['label_url']     = $term->slug;
							$new_row[ $key ][ $value_i ]['parent_value'] = (string) $term->parent;
						}
					}
				}

				$new_rows[] = $new_row;
			}
		}

		return array_merge( $rows, $new_rows );
	}
}
