<?php
/**
 * API for managing the taxonomies.
 *
 * @link       https://bootstrapped.ventures
 * @since      5.0.0
 *
 * @package    WP_Recipe_Maker
 * @subpackage WP_Recipe_Maker/includes/public/api
 */

/**
 * API for managing the taxonomies.
 *
 * @since      5.0.0
 * @package    WP_Recipe_Maker
 * @subpackage WP_Recipe_Maker/includes/public/api
 * @author     Brecht Vandersmissen <brecht@bootstrapped.ventures>
 */
class WPRM_Api_Manage_Taxonomies {

	/**
	 * Register actions and filters.
	 *
	 * @since    5.0.0
	 */
	public static function init() {
		add_action( 'rest_api_init', array( __CLASS__, 'api_register_data' ) );
	}

	/**
	 * Register data for the REST API.
	 *
	 * @since    5.0.0
	 */
	public static function api_register_data() {
		if ( function_exists( 'register_rest_field' ) ) {
			register_rest_route( 'wp-recipe-maker/v1', '/manage/taxonomy', array(
				'callback' => array( __CLASS__, 'api_manage_taxonomies' ),
				'methods' => 'POST',
				'permission_callback' => array( __CLASS__, 'api_required_permissions' ),
			) );
			register_rest_route( 'wp-recipe-maker/v1', '/manage/taxonomy/merge', array(
				'callback' => array( __CLASS__, 'api_manage_taxonomies_merge' ),
				'methods' => 'POST',
				'permission_callback' => array( __CLASS__, 'api_required_permissions' ),
			) );
			register_rest_route( 'wp-recipe-maker/v1', '/manage/taxonomy/clone', array(
				'callback' => array( __CLASS__, 'api_manage_taxonomies_clone' ),
				'methods' => 'POST',
				'permission_callback' => array( __CLASS__, 'api_required_permissions' ),
			) );
			register_rest_route( 'wp-recipe-maker/v1', '/manage/taxonomy/label', array(
				'callback' => array( __CLASS__, 'api_manage_taxonomies_label' ),
				'methods' => 'POST',
				'permission_callback' => array( __CLASS__, 'api_required_permissions' ),
			) );
			register_rest_route( 'wp-recipe-maker/v1', '/manage/taxonomy/language', array(
				'callback' => array( __CLASS__, 'api_manage_taxonomies_language' ),
				'methods' => 'POST',
				'permission_callback' => array( __CLASS__, 'api_required_permissions' ),
			) );
			register_rest_route( 'wp-recipe-maker/v1', '/manage/taxonomy/bulk', array(
				'callback' => array( __CLASS__, 'api_manage_taxonomies_bulk_edit' ),
				'methods' => 'POST',
				'permission_callback' => array( __CLASS__, 'api_required_permissions' ),
			) );
			register_rest_route( 'wp-recipe-maker/v1', '/manage/taxonomy/term-id', array(
				'callback' => array( __CLASS__, 'api_manage_taxonomies_term_id' ),
				'methods' => 'POST',
				'permission_callback' => array( __CLASS__, 'api_required_permissions' ),
			) );
			register_rest_route( 'wp-recipe-maker/v1', '/manage/taxonomy/shopping-groups', array(
				'callback' => array( __CLASS__, 'api_manage_taxonomies_shopping_groups' ),
				'methods' => 'POST',
				'permission_callback' => array( __CLASS__, 'api_required_permissions' ),
			) );
		}
	}

	/**
	 * Validate ID in API call.
	 *
	 * @since    5.0.0
	 * @param    mixed           $param Parameter to validate.
	 * @param    WP_REST_Request $request Current request.
	 * @param    mixed           $key Key.
	 */
	public static function api_validate_numeric( $param, $request, $key ) {
		return is_numeric( $param );
	}

	/**
	 * Required permissions for the API.
	 *
	 * @since    5.0.0
	 */
	public static function api_required_permissions() {
		return current_user_can( WPRM_Settings::get( 'features_manage_access' ) );
	}

	/**
	 * Handle manage taxonomies call to the REST API.
	 *
	 * @since    5.0.0
	 * @param    WP_REST_Request $request Current request.
	 */
	public static function api_manage_taxonomies( $request ) {
		// Parameters.
		$params = $request->get_params();

		$type = isset( $params['type'] ) ? sanitize_key( $params['type'] ) : '';
		$taxonomy = 'wprm_' . $type;

		if ( ! taxonomy_exists( $taxonomy ) ) {
			return false;
		}

		// Different type for links.
		if ( in_array( $type, array( 'ingredient', 'equipment' ) ) ) {
			$link_type = $type;
		} else {
			$link_type = 'term';
		}

		$page = isset( $params['page'] ) ? intval( $params['page'] ) : 0;
		$page_size = isset( $params['pageSize'] ) ? intval( $params['pageSize'] ) : 25;
		$sorted = isset( $params['sorted'] ) ? $params['sorted'] : array( array( 'id' => 'id', 'desc' => true ) );
		$filtered = isset( $params['filtered'] ) ? $params['filtered'] : array();
		$filtered = is_array( $filtered ) ? $filtered : array();
		$fixed_filter = isset( $params['filter'] ) && is_array( $params['filter'] ) && 2 === count( $params['filter'] ) ? $params['filter'] : false;

		if ( $fixed_filter ) {
			$filtered[] = array(
				'id' => sanitize_key( $fixed_filter[0] ),
				'value' => sanitize_text_field( $fixed_filter[1] ),
			);
		}

		// Starting query args.
		$args = array(
			'taxonomy' => $taxonomy,
			'hide_empty' => false,
			'number' => $page_size,
			'offset' => $page * $page_size,
			'count' => true,
		);

		// Order.
		$args['order'] = $sorted[0]['desc'] ? 'DESC' : 'ASC';
		switch( $sorted[0]['id'] ) {
			case 'name':
				$args['orderby'] = 'title';
				break;
			case 'slug':
				$args['orderby'] = 'slug';
				break;
			case 'count':
				$args['orderby'] = 'count';
				break;
			case 'plural':
				$args['orderby'] = 'meta_value';
				$args['meta_key'] = 'wprm_' . $type . '_plural';
				break;
			case 'group':
				$args['orderby'] = 'meta_value';
				$args['meta_key'] = 'wprmp_ingredient_group';
				break;
			case 'eafl':
				$args['orderby'] = 'meta_value_num';
				$args['meta_key'] = 'wprmp_' . $link_type . '_eafl';
				break;
			case 'link':
				$args['orderby'] = 'meta_value';
				$args['meta_key'] = 'wprmp_' . $link_type . '_link';
				break;
			case 'link_nofollow':
				$args['orderby'] = 'meta_value';
				$args['meta_key'] = 'wprmp_' . $link_type . '_link_nofollow';
				break;
			case 'amazon_asin':
				$args['orderby'] = 'meta_value';
				$args['meta_key'] = 'wprmp_amazon_asin';
				break;
			case 'amazon_name':
				$args['orderby'] = 'meta_value';
				$args['meta_key'] = 'wprmp_amazon_name';
				break;
			case 'amazon_updated':
				$args['orderby'] = 'meta_value_num';
				$args['meta_key'] = 'wprmp_amazon_updated';
				break;
			case 'amazon_status':
				$args['orderby'] = 'meta_value';
				$args['meta_key'] = 'wprmp_amazon_status';
				break;
			case 'wpupg_custom_link':
				$args['orderby'] = 'meta_value';
				$args['meta_key'] = 'wpupg_custom_link';
				break;
			case 'wpupg_custom_image':
				$args['orderby'] = 'meta_value';
				$args['meta_key'] = 'wpupg_custom_image';
				break;
			case 'tooltip':
				$args['orderby'] = 'description';
				break;
			default:
			 	$args['orderby'] = 'ID';
		}

		// Filter.
		if ( $filtered ) {
			foreach ( $filtered as $filter ) {
				$value = $filter['value'];
				switch( $filter['id'] ) {
					case 'id':
						$args['wprm_search_id'] = $value;
						break;
					case 'name':
					case 'slug':
						// Special characters are stored encoded in the database because of the rich text editor.
						$value = htmlspecialchars( $value, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 );

						$args['search'] = $value;
						break;
					case 'plural':
						if ( '' !== $value ) {
							$args['meta_query'][] = array(
								'key' => 'wprm_' . $type . '_plural',
								'compare' => 'LIKE',
								'value' => $value,
							);
						}
						break;
					case 'group':
						if ( '' !== $value ) {
							$args['meta_query'][] = array(
								'key' => 'wprmp_ingredient_group',
								'compare' => 'LIKE',
								'value' => $value,
							);
						}
						break;
					case 'eafl':
						if ( '' !== $value ) {
							$args['meta_query'][] = array(
								'key' => 'wprmp_' . $link_type . '_eafl',
								'compare' => 'LIKE',
								'value' => $value,
							);
						}
						break;
					case 'link':
						if ( '' !== $value ) {
							$args['meta_query'][] = array(
								'key' => 'wprmp_' . $link_type . '_link',
								'compare' => 'LIKE',
								'value' => $value,
							);
						}
						break;
					case 'link_nofollow':
						if ( 'all' !== $value ) {
							$args['meta_query'][] = array(
								'key' => 'wprmp_' . $link_type . '_link_nofollow',
								'compare' => '=',
								'value' => $value,
							);

							// Only when link exists.
							$args['meta_query'][] = array(
								'key' => 'wprmp_' . $type . '_link',
								'compare' => '!=',
								'value' => '',
							);
						}
						break;
					case 'amazon_asin':
						if ( 'all' !== $value ) {
							$compare = 'yes' === $value ? 'EXISTS' : 'NOT EXISTS';

							$args['meta_query'][] = array(
								'key' => 'wprmp_amazon_asin',
								'compare' => $compare,
							);
						}
						break;
					case 'amazon_name':
						if ( '' !== $value ) {
							$args['meta_query'][] = array(
								'key' => 'wprmp_amazon_name',
								'compare' => 'LIKE',
								'value' => $value,
							);
						}
						break;
					case 'amazon_status':
						if ( 'all' !== $value ) {
							if ( 'empty' === $value ) {
								// Filter for items with no ASIN.
								$args['meta_query'][] = array(
									'key' => 'wprmp_amazon_asin',
									'compare' => 'NOT EXISTS',
								);
							} else if ( 'not_in_stock' === $value ) {
								// Filter for any status that is NOT "IN_STOCK".
								// Use custom WHERE clause to exclude items where status is "IN_STOCK" (plain or JSON).
								$args['wprm_filter_not_in_stock'] = true;
								// Also ensure ASIN exists (status only makes sense if ASIN is set).
								$args['meta_query'][] = array(
									'key' => 'wprmp_amazon_asin',
									'compare' => 'EXISTS',
								);
								// Ensure status exists.
								$args['meta_query'][] = array(
									'key' => 'wprmp_amazon_status',
									'compare' => 'EXISTS',
								);
							} else if ( 'notification_statuses' === $value ) {
								$notification_statuses = class_exists( 'WPRMP_Amazon_Status_Notifications' ) ? WPRMP_Amazon_Status_Notifications::get_notification_statuses() : array();

								if ( ! empty( $notification_statuses ) ) {
									$args['meta_query'][] = array(
										'key' => 'wprmp_amazon_status',
										'compare' => 'IN',
										'value' => $notification_statuses,
									);
									// Also ensure ASIN exists (status only makes sense if ASIN is set).
									$args['meta_query'][] = array(
										'key' => 'wprmp_amazon_asin',
										'compare' => 'EXISTS',
									);
									$args['meta_query'][] = array(
										'key' => 'wprmp_amazon_asin',
										'compare' => '!=',
										'value' => '',
									);
								} else {
									$args['meta_query'][] = array(
										'key' => 'wprmp_amazon_status',
										'compare' => '=',
										'value' => '__wprm_no_notification_status__',
									);
								}
							} else {
								// Filter by status type (stored as plain string).
								$args['meta_query'][] = array(
									'key' => 'wprmp_amazon_status',
									'compare' => '=',
									'value' => $value,
								);
								// Also ensure ASIN exists (status only makes sense if ASIN is set).
								$args['meta_query'][] = array(
									'key' => 'wprmp_amazon_asin',
									'compare' => 'EXISTS',
								);
							}
						}
						break;
					case 'image_id':
						if ( 'all' !== $value ) {
							$compare = 'yes' === $value ? 'EXISTS' : 'NOT EXISTS';

							$image_field = 'equipment' === $type || 'ingredient' === $type ? $type : 'term';

							$args['meta_query'][] = array(
								'key' => 'wprmp_' . $image_field . '_image_id',
								'compare' => $compare,
							);
						}
						break;
					case 'product':
						if ( 'all' !== $value && in_array( $type, array( 'ingredient', 'equipment' ), true ) ) {
							$args['meta_query'][] = array(
								'key' => 'wprmp_product',
								'compare' => 'yes' === $value ? 'EXISTS' : 'NOT EXISTS',
							);
						}
						break;
					case 'wpupg_custom_link':
						if ( '' !== $value ) {
							$args['meta_query'][] = array(
								'key' => 'wpupg_custom_link',
								'compare' => 'LIKE',
								'value' => $value,
							);
						}
						break;
					case 'wpupg_custom_image':
						if ( 'all' !== $value ) {
							if ( 'yes' === $value ) {
								$args['meta_query'][] = array(
									'key' => 'wpupg_custom_image',
									'compare' => 'EXISTS',
								);
							} else {
								$args['meta_query'][] = array(
									'key' => 'wpupg_custom_image',
									'compare' => 'NOT EXISTS',
								);
							}
						}
						break;
					case 'tooltip':
						if ( '' !== $value ) {
							$args['description__like'] = $value;
						}
						break;
				}
			}
		}

		add_filter( 'terms_clauses', array( __CLASS__, 'api_manage_taxonomies_query' ), 10, 3 );
		$query = new WP_Term_Query( $args );

		unset( $args['number'] );
		unset( $args['offset'] );
		$filtered_terms = wp_count_terms( $args );
		remove_filter( 'terms_clauses', array( __CLASS__, 'api_manage_taxonomies_query' ), 10, 3 );

		$total_terms = wp_count_terms( array( 'taxonomy' => $taxonomy, 'hide_empty' => false ) );
		$rows = $query->terms ? array_values( $query->terms ) : array();

		self::prime_taxonomy_manage_caches( $rows, $type );

		// Get total counts (all post statuses) for returned terms
		if ( ! empty( $rows ) ) {
			global $wpdb;
			$term_ids = wp_list_pluck( $rows, 'term_taxonomy_id' );
			$term_ids_placeholder = implode( ',', array_fill( 0, count( $term_ids ), '%d') );
			
			$total_counts = $wpdb->get_results( $wpdb->prepare("
				SELECT tt.term_taxonomy_id, COUNT(p.ID) as total_count
				FROM {$wpdb->term_taxonomy} tt
				LEFT JOIN {$wpdb->term_relationships} tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
				LEFT JOIN {$wpdb->posts} p ON tr.object_id = p.ID
				WHERE tt.term_taxonomy_id IN ({$term_ids_placeholder})
				AND p.post_status != 'trash'
				GROUP BY tt.term_taxonomy_id
			", $term_ids ), OBJECT_K);
			
			// Update counts in returned terms
			foreach ( $rows as $row ) {
				if ( isset( $total_counts[ $row->term_taxonomy_id ] ) ) {
					$row->total_count = $total_counts[$row->term_taxonomy_id]->total_count;
				} else {
					$row->total_count = 0;
				}
			}
		}

		// Extra information needed.
		if ( 'nutrition_ingredient' === $type ) {
			// Nutrition ingredient is a special case.
			foreach( $rows as $index => $row ) {
				$nutrition = WPRMPN_Ingredient_Manager::get_nutrition( $row->term_id );

				if ( $nutrition ) {
					$row->amount = $nutrition['amount'];
					$row->unit = $nutrition['unit'];
					$row->facts = $nutrition['nutrients'];
				} else {
					$row->amount = '';
					$row->unit = '';
					$row->facts = array();
				}
			}
		} else {
			// Only get permalinks if has archive pages.
			$has_archive_pages = WPRM_Taxonomies::has_archive_pages( $taxonomy );

			$default_suitablefordiet_terms = WPRM_Taxonomies::get_diet_taxonomy_terms();

			// Extra information needed for most other taxonomies.
			foreach( $rows as $index => $row ) {
				// Permalink if archive is enabled.
				$row->permalink = false;
				if ( $has_archive_pages ) {
					$permalink = get_term_link( $row->term_id, $taxonomy );
					$row->permalink = is_wp_error( $permalink ) ? false : $permalink;
				}

				$row->language = WPRM_Compatibility::get_term_language( $row->term_id, $taxonomy );

				// Special meta key naming for ingredient and equipment.
				$meta_key = 'term';
				if ( 'ingredient' === $type ) { $meta_key = 'ingredient'; }
				if ( 'equipment' === $type ) { $meta_key = 'equipment'; }

				// Term image.
				$row->image_id = get_term_meta( $row->term_id, 'wprmp_' . $meta_key . '_image_id', true );
				$row->image_url = '';

				if ( $row->image_id ) {
					$thumb = wp_get_attachment_image_src( $row->image_id, array( 150, 999 ) );

					if ( $thumb && isset( $thumb[0] ) ) {
						$row->image_url = $thumb[0];
					}
				}

				// EAFL integration.
				$row->eafl = get_term_meta( $row->term_id, 'wprmp_' . $meta_key . '_eafl', true );

				if ( $row->eafl && class_exists( 'EAFL_Link_Manager' ) ) {
					$link = EAFL_Link_Manager::get_link( $row->eafl );

					if ( $link ) {
						$row->eafl_details = $link->get_data_manage();	
					}
				}

				// Term link.
				$row->link = get_term_meta( $row->term_id, 'wprmp_' . $meta_key . '_link', true );
				$row->link_nofollow = '';
				if ( $row->link ) {
					$link_nofollow = get_term_meta( $row->term_id, 'wprmp_' . $meta_key . '_link_nofollow', true );
					$row->link_nofollow = in_array( $link_nofollow, array( 'default', 'nofollow', 'follow', 'sponsored' ) ) ? $link_nofollow : 'default';
				}

				// Grid integration.
				$row->wpupg_custom_link = get_term_meta( $row->term_id, 'wpupg_custom_link', true );
				$row->wpupg_custom_image = get_term_meta( $row->term_id, 'wpupg_custom_image', true );
				$row->wpupg_custom_image_url = '';

				if ( $row->wpupg_custom_image ) {
					$thumb = wp_get_attachment_image_src( $row->wpupg_custom_image, array( 150, 999 ) );

					if ( $thumb && isset( $thumb[0] ) ) {
						$row->wpupg_custom_image_url = $thumb[0];
					}
				}

				// Type specific fields.
				switch ( $type ) {
					case 'ingredient':
						$row->plural = get_term_meta( $row->term_id, 'wprm_ingredient_plural', true );
						$row->unit_system_names = array(
							1 => array(
								'singular' => get_term_meta( $row->term_id, 'wprm_ingredient_unit_system_1_singular', true ),
								'plural' => get_term_meta( $row->term_id, 'wprm_ingredient_unit_system_1_plural', true ),
							),
							2 => array(
								'singular' => get_term_meta( $row->term_id, 'wprm_ingredient_unit_system_2_singular', true ),
								'plural' => get_term_meta( $row->term_id, 'wprm_ingredient_unit_system_2_plural', true ),
							),
						);
						$row->group = get_term_meta( $row->term_id, 'wprmp_ingredient_group', true );
						$row->product = class_exists( 'WPRMPP_Meta' ) ? WPRMPP_Meta::get_product_from_term_id( $row->term_id ) : false;
						break;
						case 'ingredient_unit':
							$row->plural = get_term_meta( $row->term_id, 'wprm_ingredient_unit_plural', true );
							$row->connector = get_term_meta( $row->term_id, 'wprm_ingredient_unit_connector', true );
							$row->connector_spacing = WPRM_Ingredient_Display::sanitize_connector_spacing( get_term_meta( $row->term_id, 'wprm_ingredient_unit_connector_spacing', true ) );
							$row->connector_pluralizes_ingredient = '1' === get_term_meta( $row->term_id, 'wprm_ingredient_unit_connector_pluralizes_ingredient', true );
							break;
					case 'equipment':
						$row->affiliate_html = get_term_meta( $row->term_id, 'wprmp_equipment_affiliate_html', true );
						$row->amazon_asin = get_term_meta( $row->term_id, 'wprmp_amazon_asin', true );
						$row->amazon_name = get_term_meta( $row->term_id, 'wprmp_amazon_name', true );
						$row->amazon_image = get_term_meta( $row->term_id, 'wprmp_amazon_image', true );
						$row->amazon_image_width = get_term_meta( $row->term_id, 'wprmp_amazon_image_width', true );
						$row->amazon_image_height = get_term_meta( $row->term_id, 'wprmp_amazon_image_height', true );
						$row->amazon_updated = get_term_meta( $row->term_id, 'wprmp_amazon_updated', true );
						$row->amazon_status = get_term_meta( $row->term_id, 'wprmp_amazon_status', true );
						// Get non-affiliate product URL for linking.
						if ( $row->amazon_asin && class_exists( 'WPRMP_Amazon' ) ) {
							$row->amazon_product_url = WPRMP_Amazon::get_product_url( $row->amazon_asin );
						} else {
							$row->amazon_product_url = '';
						}
						$row->product = class_exists( 'WPRMPP_Meta' ) ? WPRMPP_Meta::get_product_from_term_id( $row->term_id ) : false;
						break;
					case 'suitablefordiet':
						$row->label = get_term_meta( $row->term_id, 'wprm_term_label', true );
						if ( isset( $row->actual_name ) ) {
							$row->name = $row->actual_name;
						}
						$row->is_default = in_array( $row->name, array_keys( $default_suitablefordiet_terms ) );

						break;
					case 'glossary_term':
						$row->tooltip = $row->description;
						break;
				}
			}
		}

		$data = array(
			'rows' => $rows,
			'total' => intval( $total_terms ),
			'filtered' => intval( $filtered_terms ),
			'pages' => ceil( $filtered_terms / $page_size ),
		);

		return rest_ensure_response( $data );
	}

	/**
	 * Prime caches for related objects used while hydrating manage rows.
	 *
	 * @since    10.0.0
	 * @param    array  $rows Term rows returned for the current manage page.
	 * @param    string $type Manage taxonomy type.
	 */
	private static function prime_taxonomy_manage_caches( $rows, $type ) {
		if ( empty( $rows ) ) {
			return;
		}

		$term_ids = wp_list_pluck( $rows, 'term_id' );
		$term_ids = array_values( array_unique( array_filter( array_map( 'intval', $term_ids ) ) ) );

		if ( empty( $term_ids ) ) {
			return;
		}

		update_meta_cache( 'term', $term_ids );

		$meta_key = 'term';
		if ( 'ingredient' === $type ) {
			$meta_key = 'ingredient';
		} elseif ( 'equipment' === $type ) {
			$meta_key = 'equipment';
		}

		$attachment_ids = array();
		foreach ( $term_ids as $term_id ) {
			$image_id = intval( get_term_meta( $term_id, 'wprmp_' . $meta_key . '_image_id', true ) );
			if ( $image_id ) {
				$attachment_ids[] = $image_id;
			}

			$wpupg_custom_image = intval( get_term_meta( $term_id, 'wpupg_custom_image', true ) );
			if ( $wpupg_custom_image ) {
				$attachment_ids[] = $wpupg_custom_image;
			}
		}

		$attachment_ids = array_values( array_unique( array_filter( array_map( 'intval', $attachment_ids ) ) ) );
		if ( ! empty( $attachment_ids ) ) {
			if ( function_exists( '_prime_post_caches' ) ) {
				_prime_post_caches( $attachment_ids, false, true );
			} else {
				update_meta_cache( 'post', $attachment_ids );
				foreach ( $attachment_ids as $attachment_id ) {
					get_post( $attachment_id );
				}
			}
		}
	}

	/**
	 * Filter the where taxonomies query.
	 *
	 * @since	5.0.0
	 */
	public static function api_manage_taxonomies_query( $pieces, $taxonomies, $args ) {
		global $wpdb;
		
		$id_search = isset( $args['wprm_search_id'] ) ? $args['wprm_search_id'] : false;
		if ( $id_search ) {
			$pieces['where'] .= ' AND t.term_id LIKE \'%' . esc_sql( $wpdb->esc_like( $id_search ) ) . '%\'';
		}

		// Filter for "not_in_stock" - exclude items where status is "IN_STOCK".
		$filter_not_in_stock = isset( $args['wprm_filter_not_in_stock'] ) ? $args['wprm_filter_not_in_stock'] : false;
		if ( $filter_not_in_stock ) {
			// Exclude terms where amazon_status is exactly "IN_STOCK".
			$pieces['where'] .= " AND NOT EXISTS (
				SELECT 1 FROM {$wpdb->termmeta} tm
				WHERE tm.term_id = t.term_id
				AND tm.meta_key = 'wprmp_amazon_status'
				AND tm.meta_value = 'IN_STOCK'
			)";
		}

		return $pieces;
	}

	/**
	 * Handle taxonomies merge call to the REST API.
	 *
	 * @since    5.0.0
	 * @param    WP_REST_Request $request Current request.
	 */
	public static function api_manage_taxonomies_merge( $request ) {
		// Parameters.
		$params = $request->get_params();

		$type = isset( $params['type'] ) ? sanitize_key( $params['type'] ) : '';
		$taxonomy = 'wprm_' . $type;

		if ( ! taxonomy_exists( $taxonomy ) ) {
			return false;
		}

		$old_id = isset( $params['oldId'] ) ? intval( $params['oldId'] ) : false;
		$new_id = isset( $params['newId'] ) ? intval( $params['newId'] ) : false;

		if ( $old_id && $new_id ) {
			$old_term = get_term( $old_id, $taxonomy );
			$new_term = get_term( $new_id, $taxonomy );

			if ( $old_term && ! is_wp_error( $old_term ) && $new_term && ! is_wp_error( $new_term ) ) {
				// Add new term ID to recipes using the old term ID.
				$args = array(
					'post_type' => WPRM_POST_TYPE,
					'post_status' => 'any',
					'nopaging' => true,
					'tax_query' => array(
						array(
							'taxonomy' => $old_term->taxonomy,
							'field' => 'id',
							'terms' => $old_term->term_id,
						),
					)
				);
		
				$query = new WP_Query( $args );
				$posts = $query->posts;
				foreach ( $posts as $post ) {
					if ( 'wprm_ingredient' === $old_term->taxonomy ) {
						$recipe = WPRM_Recipe_Manager::get_recipe( $post );
		
						$new_ingredients = array();
						$new_ingredient_ids = array();
						foreach ( $recipe->ingredients() as $ingredient_group ) {
							$new_ingredient_group = $ingredient_group;
							$new_ingredient_group['ingredients'] = array();
		
							foreach ( $ingredient_group['ingredients'] as $ingredient ) {
								if ( intval( $ingredient['id'] ) === $old_term->term_id ) {
									$ingredient['id'] = $new_term->term_id;
									$new_name = $new_term->name;

									// Check if we need to use plural.
									if ( '' === $ingredient['unit'] && 1.0 !== floatval( $ingredient['amount'] ) ) {
										$plural = get_term_meta( $new_term->term_id, 'wprm_ingredient_plural', true );

										if ( $plural ) {
											$new_name = $plural;
										}
									}

									$ingredient['name'] = $new_name;
								}
								$new_ingredient_ids[] = intval( $ingredient['id'] );
								$new_ingredient_group['ingredients'][] = $ingredient;
							}
		
							$new_ingredients[] = $new_ingredient_group;
						}
		
						$new_ingredient_ids = array_unique( $new_ingredient_ids );
						wp_set_object_terms( $recipe->id(), $new_ingredient_ids, 'wprm_ingredient', false );
		
						update_post_meta( $recipe->id(), 'wprm_ingredients', $new_ingredients );
					} else if ( 'wprm_ingredient_unit' === $old_term->taxonomy ) {
						$recipe = WPRM_Recipe_Manager::get_recipe( $post );
		
						$new_ingredients = array();
						$new_ingredient_unit_ids = array();
						foreach ( $recipe->ingredients() as $ingredient_group ) {
							$new_ingredient_group = $ingredient_group;
							$new_ingredient_group['ingredients'] = array();
		
							foreach ( $ingredient_group['ingredients'] as $ingredient ) {
								if ( isset( $ingredient['unit_id'] ) && intval( $ingredient['unit_id'] ) === $old_term->term_id ) {
									$ingredient['unit_id'] = $new_term->term_id;
									$new_name = $new_term->name;

									// Check if we need to use plural.
									if ( 1.0 !== floatval( $ingredient['amount'] ) ) {
										$plural = get_term_meta( $new_term->term_id, 'wprm_ingredient_unit_plural', true );

										if ( $plural ) {
											$new_name = $plural;
										}
									}
									$ingredient['unit'] = $new_name;

									$new_ingredient_unit_ids[] = intval( $ingredient['unit_id'] );
								}

								// Check converted as well.
								if ( isset( $ingredient['converted'] ) ) {
									foreach ( $ingredient['converted'] as $system => $conversion ) {
										if ( isset( $ingredient['converted'][ $system ]['unit_id'] ) && intval( $ingredient['converted'][ $system ]['unit_id'] ) === $old_term->term_id ) {
											$ingredient['converted'][ $system ]['unit_id'] = $new_term->term_id;

											$new_name = $new_term->name;

											// Check if we need to use plural.
											if ( 1.0 !== floatval( $ingredient['converted'][ $system ]['amount'] ) ) {
												$plural = get_term_meta( $new_term->term_id, 'wprm_ingredient_unit_plural', true );

												if ( $plural ) {
													$new_name = $plural;
												}
											}
											$ingredient['converted'][ $system ]['unit'] = $new_name;

											$new_ingredient_unit_ids[] = intval( $ingredient['converted'][ $system ]['unit_id'] );
										}
									}
								}

								$new_ingredient_group['ingredients'][] = $ingredient;
							}
		
							$new_ingredients[] = $new_ingredient_group;
						}
		
						$new_ingredient_unit_ids = array_unique( $new_ingredient_unit_ids );
						wp_set_object_terms( $recipe->id(), $new_ingredient_unit_ids, 'wprm_ingredient_unit', false );
		
						update_post_meta( $recipe->id(), 'wprm_ingredients', $new_ingredients );
					} else if ( 'wprm_equipment' === $old_term->taxonomy ) {
						$recipe = WPRM_Recipe_Manager::get_recipe( $post );
		
						$new_equipment = array();
						$new_equipment_ids = array();
						foreach ( $recipe->equipment() as $equipment ) {
							if ( intval( $equipment['id'] ) === $old_term->term_id ) {
								$equipment['id'] = $new_term->term_id;
								$equipment['name'] = $new_term->name;
							}
							$new_equipment_ids[] = intval( $equipment['id'] );
							$new_equipment[] = $equipment;
						}
		
						$new_equipment_ids = array_unique( $new_equipment_ids );
						wp_set_object_terms( $recipe->id(), $new_equipment_ids, 'wprm_equipment', false );
		
						update_post_meta( $recipe->id(), 'wprm_equipment', $new_equipment );
					} else {
						// Append new term.
						wp_set_object_terms( $post->ID, $new_term->term_id, $new_term->taxonomy, true );
					}
				}

				// Delete old term.
				wp_delete_term( $old_term->term_id, $taxonomy );
				return rest_ensure_response( true );
			}
		}

		return rest_ensure_response( false );
	}


	/**
	 * Handle taxonomies clone call to the REST API.
	 *
	 * @since    9.1.0
	 * @param    WP_REST_Request $request Current request.
	 */
	public static function api_manage_taxonomies_clone( $request ) {
		// Parameters.
		$params = $request->get_params();

		$type = isset( $params['type'] ) ? sanitize_key( $params['type'] ) : '';
		$taxonomy = 'wprm_' . $type;

		if ( ! taxonomy_exists( $taxonomy ) ) {
			return false;
		}

		$existing_id = isset( $params['id'] ) ? intval( $params['id'] ) : false;
		$new_name = isset( $params['name'] ) ? WPRM_Recipe_Sanitizer::sanitize_html( $params['name'] ) : '';

		if ( $existing_id && $new_name ) {
			$existing_term = get_term( $existing_id, $taxonomy );

			if ( $existing_term && ! is_wp_error( $existing_term ) ) {
				$existing_meta = get_term_meta( $existing_id );

				$new_term = wp_insert_term( $new_name, $taxonomy, array(
					'description' => $existing_term->description,
					'parent'      => $existing_term->parent,
				) );

				if ( ! is_wp_error( $new_term ) && $new_term['term_id'] ) {
					// Copy all term metadata through Database.
					global $wpdb;

					$wpdb->query( $wpdb->prepare( sprintf( "INSERT INTO %s (`term_id`, `meta_key`, `meta_value`) SELECT %%d, `meta_key`, `meta_value` FROM %s WHERE `term_id` = %%d", $wpdb->termmeta, $wpdb->termmeta ), $new_term['term_id'], $existing_id ) );

					return rest_ensure_response( true );
				}
			}
		}

		return rest_ensure_response( false );
	}

	/**
	 * Handle taxonomies label call to the REST API.
	 *
	 * @since    5.9.0
	 * @param    WP_REST_Request $request Current request.
	 */
	public static function api_manage_taxonomies_label( $request ) {
		// Parameters.
		$params = $request->get_params();

		$type = isset( $params['type'] ) ? sanitize_key( $params['type'] ) : '';
		$taxonomy = 'wprm_' . $type;

		if ( ! taxonomy_exists( $taxonomy ) ) {
			return false;
		}

		$id = isset( $params['id'] ) ? intval( $params['id'] ) : false;
		$label = isset( $params['label'] ) ? sanitize_text_field( $params['label'] ) : false;

		if ( $id && $label ) {
			update_term_meta( $id, 'wprm_term_label', $label );
			return rest_ensure_response( true );
		}

		return rest_ensure_response( false );
	}

	/**
	 * Handle taxonomy language change calls to the REST API.
	 *
	 * @since	10.8.0
	 * @param	WP_REST_Request $request Current request.
	 */
	public static function api_manage_taxonomies_language( $request ) {
		$params = $request->get_params();

		$type     = isset( $params['type'] ) ? sanitize_key( $params['type'] ) : '';
		$taxonomy = 'wprm_' . $type;

		if ( ! taxonomy_exists( $taxonomy ) ) {
			return rest_ensure_response( false );
		}

		$id       = isset( $params['id'] ) ? intval( $params['id'] ) : 0;
		$language = isset( $params['language'] ) && $params['language'] ? sanitize_key( $params['language'] ) : false;

		if ( $id && $language ) {
			$updated = WPRM_Compatibility::set_term_language( $id, $taxonomy, $language );
			return rest_ensure_response( (bool) $updated );
		}

		return rest_ensure_response( false );
	}

	/**
	 * Handle shopping list groups call to the REST API.
	 *
	 * @since    10.0.0
	 * @param    WP_REST_Request $request Current request.
	 */
	public static function api_manage_taxonomies_shopping_groups( $request ) {
		global $wpdb;

		$params = $request->get_params();

		$search = isset( $params['search'] ) ? sanitize_text_field( $params['search'] ) : '';
		$search = trim( $search );

		// Limit search string length to prevent abuse.
		if ( strlen( $search ) > 100 ) {
			$search = substr( $search, 0, 100 );
		}

		$limit = isset( $params['limit'] ) ? intval( $params['limit'] ) : 50;
		$limit = min( max( 1, $limit ), 100 );

		if ( '' !== $search ) {
			$query = $wpdb->prepare(
				"
					SELECT tm.meta_value as name, COUNT(*) as count
					FROM {$wpdb->termmeta} tm
					WHERE tm.meta_key = %s
					AND tm.meta_value != ''
					AND tm.meta_value LIKE %s
					GROUP BY tm.meta_value
					ORDER BY count DESC, tm.meta_value ASC
				",
				'wprmp_ingredient_group',
				'%' . $wpdb->esc_like( $search ) . '%'
			);
		} else {
			$query = $wpdb->prepare(
				"
					SELECT tm.meta_value as name, COUNT(*) as count
					FROM {$wpdb->termmeta} tm
					WHERE tm.meta_key = %s
					AND tm.meta_value != ''
					GROUP BY tm.meta_value
					ORDER BY count DESC, tm.meta_value ASC
				",
				'wprmp_ingredient_group'
			);
		}

		$raw_groups = $wpdb->get_results( $query, ARRAY_A );

		$groups_map = array();

		foreach ( $raw_groups as $raw_group ) {
			$name = isset( $raw_group['name'] ) ? trim( $raw_group['name'] ) : '';
			$count = isset( $raw_group['count'] ) ? intval( $raw_group['count'] ) : 0;

			if ( '' === $name ) {
				continue;
			}

			$key = strtolower( $name );

			if ( ! isset( $groups_map[ $key ] ) ) {
				$groups_map[ $key ] = array(
					'name' => $name,
					'count' => $count,
				);
			} else {
				$groups_map[ $key ]['count'] += $count;
			}
		}

		$groups = array_values( $groups_map );

		usort( $groups, function( $a, $b ) {
			if ( $a['count'] !== $b['count'] ) {
				return $a['count'] > $b['count'] ? -1 : 1;
			}

			return strcasecmp( $a['name'], $b['name'] );
		});

		$groups = array_slice( $groups, 0, $limit );

		return rest_ensure_response( array(
			'groups' => $groups,
		) );
	}

	/**
	 * Handle taxonomies bulk edit call to the REST API.
	 *
	 * @since    5.0.0
	 * @param    WP_REST_Request $request Current request.
	 */
	public static function api_manage_taxonomies_bulk_edit( $request ) {
		// Parameters.
		$params = $request->get_params();

		$type = isset( $params['type'] ) ? sanitize_key( $params['type'] ) : '';
		$taxonomy = 'wprm_' . $type;

		if ( ! taxonomy_exists( $taxonomy ) ) {
			return false;
		}

		$ids = isset( $params['ids'] ) ? array_map( 'intval', $params['ids'] ) : array();
		$action = isset( $params['action'] ) ? $params['action'] : false;

		if ( $ids && $action && $action['type'] ) {
			// Do once.
			if ( 'export' === $action['type'] ) {
				if ( class_exists( 'WPRMP_Export_Taxonomies' ) ) {
					return WPRMP_Export_Taxonomies::bulk_edit_export( $ids );
				} else {
					return false;
				}
			}

			// Do per term.
			$args = array(
				'taxonomy' => $taxonomy,
				'hide_empty' => false,
				'include' => $ids,
			);

			$query = new WP_Term_Query( $args );
			$terms = $query->terms ? array_values( $query->terms ) : array();

			foreach ( $terms as $term ) {
				switch ( $action['type'] ) {
					case 'change-group':
						if ( 'wprm_ingredient' === $taxonomy ) {
							$group = sanitize_text_field( $action['options'] );
							update_term_meta( $term->term_id, 'wprmp_ingredient_group', $group );
						}
						break;
					case 'change-link':
						if ( 'wprm_ingredient' === $taxonomy ) {
							$link = trim( $action['options'] );
							update_term_meta( $term->term_id, 'wprmp_ingredient_link', $link );
						}
						if ( 'wprm_equipment' === $taxonomy ) {
							$link = trim( $action['options'] );
							update_term_meta( $term->term_id, 'wprmp_equipment_link', $link );
						}
						break;
					case 'change-nofollow':
						if ( 'wprm_ingredient' === $taxonomy ) {
							$nofollow = in_array( $action['options'], array( 'default', 'nofollow', 'follow', 'sponsored' ), true ) ? $action['options'] : 'default';
							update_term_meta( $term->term_id, 'wprmp_ingredient_link_nofollow', $nofollow );
						}
						if ( 'wprm_equipment' === $taxonomy ) {
							$nofollow = in_array( $action['options'], array( 'default', 'nofollow', 'follow', 'sponsored' ), true ) ? $action['options'] : 'default';
							update_term_meta( $term->term_id, 'wprmp_equipment_link_nofollow', $nofollow );
						}
						break;
					case 'change-html':
						if ( 'wprm_equipment' === $taxonomy ) {
							$html = trim( $action['options'] );
							update_term_meta( $term->term_id, 'wprmp_equipment_affiliate_html', $html );
						}
						break;
					case 'create-nutrition':
						// Sanitize name before lookup.
						$name = WPRM_Recipe_Sanitizer::sanitize_html( $term->name );

						// Find or create term.
						$nutrition_term = term_exists( $name, 'wprm_nutrition_ingredient' );

						if ( 0 === $nutrition_term || null === $nutrition_term ) {
							wp_insert_term( $name, 'wprm_nutrition_ingredient' );
						}
						break;
					case 'delete':
						wp_delete_term( $term->term_id, $taxonomy );
						break;
				}
			}

			return rest_ensure_response( true );
		}

		return rest_ensure_response( false );
	}

	/**
	 * Handle taxonomies term ID call to the REST API.
	 *
	 * @since    5.0.0
	 * @param    WP_REST_Request $request Current request.
	 */
	public static function api_manage_taxonomies_term_id( $request ) {
		// Parameters.
		$params = $request->get_params();

		$taxonomy = isset( $params['taxonomy'] ) ? sanitize_key( $params['taxonomy'] ) : '';
		$name = isset( $params['name'] ) ? sanitize_text_field( $params['name'] ) : '';

		if ( ! $taxonomy || ! $name ) {
			return rest_ensure_response( false );
		}

		// Get or create term ID using the sanitizer function.
		$term_id = WPRM_Recipe_Sanitizer::get_term_id_by_name( $taxonomy, $name );

		return rest_ensure_response( array(
			'term_id' => $term_id,
		) );
	}
}

WPRM_Api_Manage_Taxonomies::init();
