<?php

/**
 * WP Taxonomies Class.
 *
 * @package WP Product Feed Manager/Data/Classes
 * @version 5.2.0
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( ! class_exists( 'WPPFM_Taxonomies' ) ) :

	/**
	 * Taxonomies Class
	 */
	class WPPFM_Taxonomies {

		/**
		 * Generates a string of shop taxonomies (like a category string) in the correct order
		 *
		 * @param string $product_id
		 * @param string $tax
		 * @param string $separator
		 *
		 * @since 2.31.0 Update the function to include a primary category selection.
		 * @since 2.31.1 Changed the make_shop_taxonomy_string() methode, so it returns the correct value for non-category related attributes.
		 * @since 2.33.0 Added a result test after the get_term_by_id call, to prevent an error message.
		 *
		 * @return string
		 */
		public static function make_shop_taxonomies_string( $product_id, $tax = 'product_cat', $separator = ' > ' ) {
			// Only check the category if the attribute is a category attribute.
			$primary_category = 'category' === $tax || 'product_cat' === $tax ? self::get_primary_cat( $product_id) : false;

			if ( $primary_category ) {
				$cats = $primary_category;
			} else {
				$args = array(
					'taxonomy' => $tax,
					'orderby'  => 'parent',
					'order'    => 'DESC',
				);

				// get the post-term ordered with the last child cat first
				$cats = wp_get_post_terms( $product_id, $tax, $args );
			}

			$result = array();

			if ( count( $cats ) === 0 ) {
				return '';
			}

			// anonymous function to get the correct taxonomy string
			$cat_string = function ( $id ) use ( &$result, &$cat_string, $tax ) {
				// get the first term
				$term = get_term_by( 'id', $id, $tax, 'ARRAY_A' );

				if ( ! $term ) { return; }

				// check if the term has a parent
				if ( $term['parent'] ) {
					// start the anonymous function again with the parent id
					$cat_string( $term['parent'] );
				}

				// add the term name to the result
				$result[] = $term['name'];
			};

			// activate the anonymous function with the term_id
			foreach( $cats as $cat ) {
				$cat_string( $cat->term_id );
			}

			return implode( $separator, $result );
		}

		/**
		 * Single category path: Yoast/Rank Math primary breadcrumb, or the first assigned term's breadcrumb when no primary is set.
		 *
		 * Unlike {@see self::make_shop_taxonomies_string()}, this never merges paths from multiple categories.
		 *
		 * @param int|string $product_id Post ID.
		 * @param string     $tax        Taxonomy slug.
		 * @param string     $separator  Hierarchy separator.
		 *
		 * @since 3.22.0
		 *
		 * @return string
		 */
		public static function make_main_product_category_string( $product_id, $tax = 'product_cat', $separator = ' > ' ) {
			$product_id = absint( $product_id );
			if ( ! $product_id ) {
				return '';
			}

			$term_id = 0;

			if ( 'category' === $tax || 'product_cat' === $tax ) {
				$primary = self::get_primary_cat( $product_id );
				if ( $primary && isset( $primary[0]->term_id ) ) {
					$term_id = absint( $primary[0]->term_id );
				}
			}

			if ( ! $term_id ) {
				$terms = wp_get_post_terms(
					$product_id,
					$tax,
					array(
						'orderby' => 'term_id',
						'order'   => 'ASC',
					)
				);
				if ( is_wp_error( $terms ) || empty( $terms ) ) {
					return '';
				}
				$term_id = absint( $terms[0]->term_id );
			}

			return self::get_single_term_taxonomy_breadcrumb_string( $term_id, $tax, $separator );
		}

		/**
		 * Generates a string with all selected categories
		 *
		 * @param string $post_id
		 * @param string $separator
		 *
		 * @return string
		 */
		public static function get_shop_categories( $post_id, $separator = ', ' ) {
			$return_string = '';

			$args = array(
				'taxonomy' => 'product_cat',
				'orderby'  => 'term_id',
			);

			$cats = wp_get_post_terms( $post_id, 'product_cat', $args );

			foreach ( $cats as $cat ) {
				$return_string .= $cat->name . $separator;
			}

			return rtrim( $return_string, $separator );
		}

		/**
		 * Returns the product category that is selected as primary (only when Yoast SEO or RankMath plugin is installed)
		 *
		 * @param string $product_id
		 *
		 * @return array|boolean
		 */
		public static function get_primary_cat( $product_id ) {
			$primary_cat_id = '';

			if ( is_plugin_active( 'wordpress-seo/wp-seo.php' ) || is_plugin_active_for_network( 'wordpress-seo/wp-seo.php' )
			|| is_plugin_active( 'wordpress-seo-premium/wp-seo-premium.php' ) || is_plugin_active_for_network( 'wordpress-seo-premium/wp-seo-premium.php' ) ) {
				$primary_cat_id = get_post_meta( $product_id, '_yoast_wpseo_primary_product_cat', true );
			}

			if ( is_plugin_active( 'seo-by-rank-math/rank-math.php' ) || is_plugin_active_for_network( 'seo-by-rank-math/rank-math.php' )
			|| is_plugin_active( 'seo-by-rank-math-pro/rank-math-pro.php' ) || is_plugin_active_for_network( 'seo-by-rank-math-pro/rank-math-pro.php' ) ) {
				$primary_cat_id = get_post_meta( $product_id, 'rank_math_primary_product_cat', true );
			}

			if ( $primary_cat_id ) {
				$product_cat[0] = get_term( $primary_cat_id, 'product_cat' );
				if ( isset( $product_cat[0]->term_id ) ) {
					return $product_cat;
				}
			} else {
				return false;
			}

			return false;
		}

		public static function get_shop_categories_list() {
			$args = array(
				'hide_empty'   => 0,
				'taxonomy'     => 'product_cat',
				'hierarchical' => 1,
				'orderby'      => 'name',
				'order'        => 'ASC',
				// phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- The exclude parameter is necessary to filter out specific categories from category mapping. This is a user-configurable feature and essential functionality.
				'exclude'      => apply_filters( 'wppfm_category_mapping_exclude', array() ),
				'exclude_tree' => apply_filters( 'wppfm_category_mapping_exclude_tree', array() ),
				'number'       => absint( apply_filters( 'wppfm_category_mapping_max_categories', 0 ) ),
				'child_of'     => 0,
			);

			// see https://developer.wordpress.org/reference/classes/wp_term_query/__construct/ for valid args
			$args = apply_filters( 'wppfm_category_mapping_args', $args );

			return self::get_cat_hierarchy( 0, $args );
		}

		/**
		 * Builds root-to-leaf names for a single taxonomy term (same ordering as make_shop_taxonomies_string per term).
		 *
		 * @param int    $term_id   Term ID.
		 * @param string $tax       Taxonomy slug.
		 * @param string $separator String used between segments.
		 *
		 * @return string
		 */
		private static function get_single_term_taxonomy_breadcrumb_string( $term_id, $tax, $separator ) {
			$result     = array();
			$term_id    = absint( $term_id );
			$cat_string = function ( $id ) use ( &$result, &$cat_string, $tax ) {
				$term = get_term_by( 'id', absint( $id ), $tax, 'ARRAY_A' );
				if ( ! $term ) {
					return;
				}
				if ( ! empty( $term['parent'] ) ) {
					$cat_string( $term['parent'] );
				}
				$result[] = $term['name'];
			};
			$cat_string( $term_id );

			return implode( $separator, $result );
		}

		private static function get_cat_hierarchy( $parent, $args ) {
			$cats = get_categories( $args );
			$ret  = new stdClass;

			foreach ( $cats as $cat ) {
				if ( $cat->parent == $parent ) {
					$id                 = $cat->cat_ID;
					$ret->$id           = $cat;
					$ret->$id->children = self::get_cat_hierarchy( $id, $args );
				}
			}

			return $ret;
		}
	}


	// end of WPPFM_Taxonomies_Class

endif;
