<?php
/**
 * Fallback product resolver.
 *
 * When a search returns no matches above the similarity threshold, we still
 * want to show shoppers *something* — dead-end results pages kill conversions.
 *
 * Resolution priority:
 *   1. Admin-curated list  (settings → fallback_product_ids, comma-separated)
 *   2. WooCommerce "Featured" products
 *   3. Best-sellers (by _total_sales meta)
 *   4. Most recent published products
 *
 * Returned products are NOT mixed into the real-match results; the front-end
 * widget renders them under a "Popular products" header so shoppers can tell
 * these aren't direct matches.
 *
 * @package FindAstra
 */

defined( 'ABSPATH' ) || exit;

final class FindAstra_Fallback {

	/**
	 * Returns up to $limit fallback product IDs.
	 *
	 * @param string|null $language Optional Polylang/WPML language tag to scope
	 *                              the fallback to (e.g. 'fr'). null = auto-
	 *                              detect from the current request; '' = no
	 *                              filter (return products from any language).
	 *                              On a /fr/ archive the fallback would
	 *                              otherwise return mostly English products
	 *                              that Polylang then strips out at render
	 *                              time, leaving the shopper with 0–1
	 *                              fallback items.
	 * @return int[]
	 */
	public function ids( int $limit = 6, ?string $language = null ): array {
		if ( null === $language ) {
			$language = FindAstra_Assets::current_request_language();
		}

		$ids = $this->admin_curated( $language );
		if ( count( $ids ) >= $limit ) {
			return array_slice( $ids, 0, $limit );
		}

		$ids = array_merge( $ids, $this->featured( $limit - count( $ids ), $language ) );
		$ids = array_values( array_unique( $ids ) );
		if ( count( $ids ) >= $limit ) {
			return array_slice( $ids, 0, $limit );
		}

		$ids = array_merge( $ids, $this->bestsellers( $limit - count( $ids ), $ids, $language ) );
		$ids = array_values( array_unique( $ids ) );
		if ( count( $ids ) >= $limit ) {
			return array_slice( $ids, 0, $limit );
		}

		$ids = array_merge( $ids, $this->recent( $limit - count( $ids ), $ids, $language ) );
		return array_slice( array_values( array_unique( $ids ) ), 0, $limit );
	}

	/**
	 * Add a tax_query restricting the wc_get_products() result set to the
	 * given language when WPML/Polylang's `language` taxonomy exists. Empty
	 * language → no filter applied (mono-lingual or "any language" path).
	 *
	 * @param array<string,mixed> $args
	 * @return array<string,mixed>
	 */
	private function apply_language_arg( array $args, string $language ): array {
		if ( '' === $language ) {
			return $args;
		}
		if ( ! taxonomy_exists( 'language' ) ) {
			return $args;
		}
		$tax_query = $args['tax_query'] ?? array();
		if ( ! is_array( $tax_query ) ) {
			$tax_query = array();
		}
		$tax_query[]        = array(
			'taxonomy' => 'language',
			'field'    => 'slug',
			'terms'    => array( $language ),
		);
		$args['tax_query']  = $tax_query;
		// Polylang also looks at the `lang` arg directly — pass it too so the
		// query gets language-scoped through the documented Polylang API on
		// sites where the plugin's pre_get_posts filter has been disabled.
		$args['lang']       = $language;
		return $args;
	}

	/**
	 * Filter admin-curated IDs down to the current language. Buyers list
	 * product IDs in one big comma-separated field regardless of language;
	 * we scope per request so a French archive's fallback shows the French
	 * translations of those products (not their English originals).
	 *
	 * @return int[]
	 */
	private function admin_curated( string $language ): array {
		$settings = get_option( FindAstra_Admin::SETTINGS_OPTION, array() );
		$raw      = is_array( $settings ) && isset( $settings['fallback_product_ids'] ) ? (string) $settings['fallback_product_ids'] : '';
		if ( '' === $raw ) {
			return array();
		}
		$ids = array_filter( array_map( 'absint', explode( ',', $raw ) ) );
		$ids = $this->keep_published( array_values( $ids ) );
		if ( '' === $language ) {
			return $ids;
		}
		return array_values( array_filter( $ids, function ( $id ) use ( $language ) {
			$post_lang = FindAstra_Indexer::detect_post_language( (int) $id );
			return '' === $post_lang || $post_lang === $language;
		} ) );
	}

	/**
	 * @return int[]
	 */
	private function featured( int $limit, string $language ): array {
		if ( $limit <= 0 || ! function_exists( 'wc_get_products' ) ) {
			return array();
		}
		$args = array(
			'limit'    => $limit,
			'status'   => 'publish',
			'featured' => true,
			'orderby'  => 'date',
			'order'    => 'DESC',
			'return'   => 'ids',
		);
		$args     = $this->apply_language_arg( $args, $language );
		$products = wc_get_products( $args );
		return is_array( $products ) ? array_map( 'intval', $products ) : array();
	}

	/**
	 * @param int[] $exclude
	 * @return int[]
	 */
	private function bestsellers( int $limit, array $exclude = array(), string $language = '' ): array {
		if ( $limit <= 0 || ! function_exists( 'wc_get_products' ) ) {
			return array();
		}
		$args = array(
			'limit'    => $limit,
			'status'   => 'publish',
			'orderby'  => 'meta_value_num',
			'meta_key' => 'total_sales',
			'order'    => 'DESC',
			'exclude'  => $exclude,
			'return'   => 'ids',
		);
		$args     = $this->apply_language_arg( $args, $language );
		$products = wc_get_products( $args );
		return is_array( $products ) ? array_map( 'intval', $products ) : array();
	}

	/**
	 * @param int[] $exclude
	 * @return int[]
	 */
	private function recent( int $limit, array $exclude = array(), string $language = '' ): array {
		if ( $limit <= 0 || ! function_exists( 'wc_get_products' ) ) {
			return array();
		}
		$args = array(
			'limit'   => $limit,
			'status'  => 'publish',
			'orderby' => 'date',
			'order'   => 'DESC',
			'exclude' => $exclude,
			'return'  => 'ids',
		);
		$args     = $this->apply_language_arg( $args, $language );
		$products = wc_get_products( $args );
		return is_array( $products ) ? array_map( 'intval', $products ) : array();
	}

	/**
	 * Keep only product IDs that actually exist and are published.
	 *
	 * @param int[] $ids
	 * @return int[]
	 */
	private function keep_published( array $ids ): array {
		if ( empty( $ids ) ) {
			return array();
		}
		$out = array();
		foreach ( $ids as $id ) {
			$post = get_post( $id );
			if ( $post instanceof WP_Post && 'product' === $post->post_type && 'publish' === $post->post_status ) {
				$out[] = (int) $id;
			}
		}
		return $out;
	}
}
