<?php
/**
 * Product availability helper.
 *
 * @package FoodOrd
 */

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

/**
 * Class Foodord_Product_Availability
 */
class Foodord_Product_Availability {
	/**
	 * Cached category order map.
	 *
	 * @var array<string,int>
	 */
	private static $category_order_map = null;

	/**
	 * Get category order map keyed by term ID.
	 *
	 * @return array<int,int>
	 */
	private static function get_category_order_map(): array {
		if ( null !== self::$category_order_map ) {
			return self::$category_order_map;
		}

		$map = array();
		$all = get_terms(
			array(
				'taxonomy'   => 'product_cat',
				'hide_empty' => false,
				'orderby'    => 'menu_order',
				'order'      => 'ASC',
			)
		);

		if ( ! is_wp_error( $all ) ) {
			foreach ( (array) $all as $index => $term ) {
				if ( $term instanceof WP_Term ) {
					$map[ (int) $term->term_id ] = (int) $index;
				}
			}
		}

		self::$category_order_map = $map;
		return $map;
	}
	/**
	 * Meta key used to persist availability flag.
	 */
	private const META_KEY = '_foodord_product_available';

	/**
	 * Determine if product is available on FoodOrd surfaces.
	 *
	 * @param int $product_id Product ID.
	 * @return bool
	 */
	public static function is_available( int $product_id ): bool {
		if ( $product_id <= 0 ) {
			return true;
		}

		if ( self::is_hidden_by_timed_products( $product_id ) ) {
			return false;
		}

		return self::is_manually_available( $product_id );
	}

	/**
	 * Determine if product is manually available, without web-only timed rules.
	 *
	 * @param int $product_id Product ID.
	 * @return bool
	 */
	public static function is_manually_available( int $product_id ): bool {
		if ( $product_id <= 0 ) {
			return true;
		}

		$stored = get_post_meta( $product_id, self::META_KEY, true );

		if ( '' === $stored ) {
			return true;
		}

		return self::normalize_flag( $stored );
	}

	/**
	 * Determine if product should be hidden by Timed Products.
	 *
	 * @param int $product_id Product ID.
	 */
	public static function is_hidden_by_timed_products( int $product_id ): bool {
		if ( $product_id <= 0 ) {
			return false;
		}

		return class_exists( 'Foodord_Timed_Products' )
			&& method_exists( 'Foodord_Timed_Products', 'evaluate_product_action' )
			&& 'hide' === Foodord_Timed_Products::evaluate_product_action( $product_id );
	}

	/**
	 * Determine if product should be hidden from FoodOrd product listings.
	 *
	 * @param int $product_id Product ID.
	 */
	public static function is_hidden_from_listing( int $product_id ): bool {
		if ( $product_id <= 0 ) {
			return false;
		}

		if ( ! function_exists( 'wc_get_product' ) ) {
			return self::is_hidden_by_timed_products( $product_id );
		}

		$product = wc_get_product( $product_id );
		if ( $product instanceof WC_Product && self::is_product_hidden_from_listing( $product ) ) {
			return true;
		}

		return self::is_hidden_by_timed_products( $product_id );
	}

	/**
	 * Determine if product object should be hidden from FoodOrd product listings.
	 *
	 * @param WC_Product $product Product instance.
	 */
	public static function is_product_hidden_from_listing( WC_Product $product ): bool {
		return 'outofstock' === $product->get_stock_status() || self::is_hidden_by_timed_products( $product->get_id() );
	}

	/**
	 * Determine if product is available for a specific order mode.
	 *
	 * @param int    $product_id Product ID.
	 * @param string $order_mode Order mode.
	 */
	public static function is_available_for_order_mode( int $product_id, string $order_mode ): bool {
		if ( $product_id <= 0 ) {
			return true;
		}

		$order_mode = self::normalize_order_mode( $order_mode );

		if (
			class_exists( 'Foodord_Timed_Products' )
			&& method_exists( 'Foodord_Timed_Products', 'product_is_allowed_for_order_mode' )
			&& ! Foodord_Timed_Products::product_is_allowed_for_order_mode( $product_id, $order_mode )
		) {
			return false;
		}

		$stored = get_post_meta( $product_id, self::META_KEY, true );

		if ( '' === $stored ) {
			return true;
		}

		return self::normalize_flag( $stored );
	}

	/**
	 * Get the badge label for an unavailable product.
	 *
	 * @param int $product_id Product ID.
	 */
	public static function get_unavailable_label( int $product_id ): string {
		if ( self::is_available( $product_id ) ) {
			return '';
		}

		$available_mode = self::get_mode_limited_availability( $product_id );
		if ( 'pickup' === $available_mode ) {
			return __( 'Only Pickup', 'foodord' );
		}

		if ( 'delivery' === $available_mode ) {
			return __( 'Only Delivery', 'foodord' );
		}

		return __( 'Not available', 'foodord' );
	}

	/**
	 * Get the toast message for an unavailable product.
	 *
	 * @param int $product_id Product ID.
	 */
	public static function get_unavailable_message( int $product_id ): string {
		if ( self::is_available( $product_id ) ) {
			return '';
		}

		$available_mode = self::get_mode_limited_availability( $product_id );
		if ( 'pickup' === $available_mode ) {
			return __( 'This product is only available for pickup.', 'foodord' );
		}

		if ( 'delivery' === $available_mode ) {
			return __( 'This product is only available for delivery.', 'foodord' );
		}

		return __( 'This product is not available right now.', 'foodord' );
	}

	/**
	 * Persist availability flag for a product.
	 *
	 * @param int  $product_id Product ID.
	 * @param bool $available  Availability state.
	 * @return bool Updated availability state.
	 */
	public static function set_available( int $product_id, bool $available ): bool {
		if ( $product_id <= 0 ) {
			return true;
		}

		update_post_meta( $product_id, self::META_KEY, $available ? 'yes' : 'no' );

		// Bust caches attached to the product object.
		wc_delete_product_transients( $product_id );

		return self::is_available( $product_id );
	}

	/**
	 * Return string status representation.
	 *
	 * @param int $product_id Product ID.
	 * @return string
	 */
	public static function get_status( int $product_id ): string {
		return self::is_manually_available( $product_id ) ? 'available' : 'unavailable';
	}

	/**
	 * Get current order mode.
	 */
	private static function get_current_order_mode(): string {
		$order_mode = 'delivery';

		if ( function_exists( 'foodord_get_order_mode' ) ) {
			$order_mode = foodord_get_order_mode();
		} elseif ( function_exists( 'WC' ) && WC()->session ) {
			$order_mode = (string) WC()->session->get( 'foodord_order_mode', 'delivery' );
		}

		return self::normalize_order_mode( $order_mode );
	}

	/**
	 * Return the opposite mode if the product is unavailable only for the current mode.
	 *
	 * @param int $product_id Product ID.
	 */
	private static function get_mode_limited_availability( int $product_id ): string {
		$current_mode  = self::get_current_order_mode();
		$opposite_mode = 'delivery' === $current_mode ? 'pickup' : 'delivery';

		return self::is_available_for_order_mode( $product_id, $opposite_mode ) ? $opposite_mode : '';
	}

	/**
	 * Normalize order mode.
	 *
	 * @param string $order_mode Raw order mode.
	 */
	private static function normalize_order_mode( string $order_mode ): string {
		$order_mode = strtolower( trim( $order_mode ) );

		return in_array( $order_mode, array( 'delivery', 'pickup' ), true ) ? $order_mode : 'delivery';
	}

	/**
	 * Build REST payload for a WooCommerce product.
	 *
	 * @param WC_Product $product Product instance.
	 * @return array<string,mixed>
	 */
	public static function build_rest_payload( WC_Product $product ): array {
		$product_id = $product->get_id();
		$available  = self::is_manually_available( $product_id );

		$image_url = '';
		if ( has_post_thumbnail( $product_id ) ) {
			$thumb = get_the_post_thumbnail_url( $product_id, 'woocommerce_thumbnail' );
			if ( is_string( $thumb ) ) {
				$image_url = $thumb;
			}
		}

		$currency = get_woocommerce_currency();

		$is_variable        = $product->is_type( 'variable' );
		$variations_payload = array();
		$default_attributes = array();

		if ( $is_variable && class_exists( 'WC_Product_Variation' ) ) {
			$default_attributes = method_exists( $product, 'get_default_attributes' )
				? (array) $product->get_default_attributes()
				: array();
			$variations_payload = self::build_variations_payload( $product );
		}

		$category_ids       = $product->get_category_ids();
		$categories         = array();
		$category_order_map = self::get_category_order_map();

		if ( ! empty( $category_ids ) ) {
			$terms = get_terms(
				array(
					'taxonomy'   => 'product_cat',
					'hide_empty' => false,
					'include'    => array_map( 'intval', $category_ids ),
					'orderby'    => 'include',
				)
			);

			if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
				usort(
					$terms,
					function ( WP_Term $a, WP_Term $b ) use ( $category_order_map ) {
						$index_a = $category_order_map[ (int) $a->term_id ] ?? PHP_INT_MAX;
						$index_b = $category_order_map[ (int) $b->term_id ] ?? PHP_INT_MAX;
							return $index_a <=> $index_b;
					}
				);

				foreach ( $terms as $term ) {
					$order        = $category_order_map[ (int) $term->term_id ] ?? PHP_INT_MAX;
					$categories[] = array(
						'id'         => (int) $term->term_id,
						'name'       => $term->name,
						'slug'       => $term->slug,
						'menu_order' => $order,
					);
				}
			}
		}

		$primary_category       = count( $categories ) > 0
			? $categories[0]['name']
			: __( 'Uncategorized', 'foodord' );
		$primary_category_slug  = count( $categories ) > 0
			? $categories[0]['slug']
			: 'uncategorized';
		$primary_category_order = count( $categories ) > 0
			? ( $categories[0]['menu_order'] ?? PHP_INT_MAX )
			: PHP_INT_MAX;

		return array(
			'id'                           => $product_id,
			'name'                         => $product->get_name(),
			'title'                        => $product->get_name(),
			'product_title'                => $product->get_name(),
			'sku'                          => $product->get_sku(),
			'product_sku'                  => $product->get_sku(),
			'price'                        => $product->get_price(),
			'current_price'                => $product->get_price(),
			'prices'                       => array(
				'price'         => $product->get_price(),
				'regular_price' => $product->get_regular_price(),
				'sale_price'    => $product->get_sale_price(),
				'currency'      => $currency,
				'price_html'    => $product->get_price_html(),
			),
			'price_html'                   => $product->get_price_html(),
			'currency'                     => $currency,
			'type'                         => $product->get_type(),
			'product_type'                 => $product->get_type(),
			'is_variable'                  => $is_variable,
			'has_variations'               => $is_variable && count( $variations_payload ) > 0,
			'variations'                   => $variations_payload,
			'default_variation_attributes' => $default_attributes,
			'available'                    => $available,
			'is_available'                 => $available,
			'isAvailable'                  => $available,
			'availability'                 => self::get_status( $product_id ),
			'status'                       => $product->get_status(),
			'stock_status'                 => $product->get_stock_status(),
			'manage_stock'                 => $product->managing_stock(),
			'stock_quantity'               => $product->get_stock_quantity(),
			'backorders'                   => $product->get_backorders(),
			'backorders_allowed'           => $product->backorders_allowed(),
			'sold_individually'            => $product->is_sold_individually(),
			'tax_status'                   => $product->get_tax_status(),
			'tax_class'                    => $product->get_tax_class(),
			'image'                        => $image_url,
			'thumbnail'                    => $image_url,
			'image_urls'                   => array_filter( array( $image_url ) ),
			'category_ids'                 => array_map( 'intval', $category_ids ),
			'categories'                   => $categories,
			'primary_category'             => $primary_category,
			'primary_category_slug'        => $primary_category_slug,
			'primary_category_order'       => $primary_category_order,
		);
	}

	/**
	 * Build payload for variations of a variable product.
	 *
	 * @param WC_Product $product Product instance.
	 * @return array<int,array<string,mixed>>
	 */
	private static function build_variations_payload( WC_Product $product ): array {
		if ( ! class_exists( 'WC_Product_Variable' ) || ! class_exists( 'WC_Product_Variation' ) ) {
			return array();
		}

		if ( ! $product instanceof WC_Product_Variable ) {
			return array();
		}

		$children = $product->get_children();
		if ( empty( $children ) ) {
			return array();
		}

		$currency   = get_woocommerce_currency();
		$variations = array();

		foreach ( $children as $child_id ) {
			$variation = wc_get_product( (int) $child_id );
			if ( ! $variation instanceof WC_Product_Variation ) {
				continue;
			}

			$attributes_payload = self::build_variation_attributes_payload( $variation, $product );
			$price_payload      = array(
				'price'         => $variation->get_price(),
				'regular_price' => $variation->get_regular_price(),
				'sale_price'    => $variation->get_sale_price(),
				'currency'      => $currency,
				'price_html'    => $variation->get_price_html(),
			);

			$variations[] = array(
				'id'                 => $variation->get_id(),
				'variation_id'       => $variation->get_id(),
				'sku'                => $variation->get_sku(),
				'name'               => $variation->get_name(),
				'status'             => $variation->get_status(),
				'stock_status'       => $variation->get_stock_status(),
				'in_stock'           => $variation->is_in_stock(),
				'manage_stock'       => $variation->managing_stock(),
				'stock_quantity'     => $variation->get_stock_quantity(),
				'backorders'         => $variation->get_backorders(),
				'backorders_allowed' => $variation->backorders_allowed(),
				'tax_status'         => $variation->get_tax_status(),
				'tax_class'          => $variation->get_tax_class(),
				'attribute_summary'  => $attributes_payload['summary'],
				'attributes'         => $attributes_payload['attributes'],
				'prices'             => $price_payload,
				'price'              => $price_payload['price'],
				'regular_price'      => $price_payload['regular_price'],
				'sale_price'         => $price_payload['sale_price'],
			);
		}

		return $variations;
	}

	/**
	 * Build formatted attributes payload for a variation.
	 *
	 * @param WC_Product_Variation $variation      Variation instance.
	 * @param WC_Product           $parent_product Parent product instance.
	 * @return array{attributes: array<int,array<string,string>>, summary: string}
	 */
	private static function build_variation_attributes_payload( WC_Product_Variation $variation, WC_Product $parent_product ): array {
		$attributes     = array();
		$summary_parts  = array();
		$raw_attributes = $variation->get_attributes();

		foreach ( (array) $raw_attributes as $raw_key => $raw_value ) {
			if ( '' === $raw_key ) {
				continue;
			}

			$attribute_key = is_string( $raw_key ) ? $raw_key : '';
			$taxonomy      = 0 === strpos( $attribute_key, 'attribute_' ) ? substr( $attribute_key, 10 ) : $attribute_key;
			$label         = self::resolve_variation_attribute_label( $taxonomy, $parent_product );
			$value         = self::resolve_variation_attribute_value( $taxonomy, $raw_value );

			$attributes[] = array(
				'key'   => $taxonomy,
				'slug'  => $taxonomy,
				'label' => $label,
				'name'  => $label,
				'value' => $value,
				'raw'   => is_scalar( $raw_value ) ? (string) $raw_value : '',
			);

			if ( '' !== $value ) {
				$summary_parts[] = trim( sprintf( '%s: %s', $label, $value ) );
			}
		}

		return array(
			'attributes' => $attributes,
			'summary'    => implode( ', ', array_filter( $summary_parts ) ),
		);
	}

	/**
	 * Resolve a variation attribute label from the parent product before falling back to WooCommerce labels.
	 *
	 * @param string     $taxonomy       Attribute taxonomy or local key.
	 * @param WC_Product $parent_product Parent product instance.
	 * @return string
	 */
	private static function resolve_variation_attribute_label( string $taxonomy, WC_Product $parent_product ): string {
		$normalized_taxonomy = 0 === strpos( $taxonomy, 'attribute_' ) ? substr( $taxonomy, 10 ) : $taxonomy;
		$target_slug         = sanitize_title( $normalized_taxonomy );
		$fallback            = self::clean_variation_attribute_label( wc_attribute_label( $normalized_taxonomy ) );
		$parent_attributes   = $parent_product->get_attributes();

		if ( ! is_array( $parent_attributes ) || empty( $parent_attributes ) ) {
			return '' !== $fallback ? $fallback : $normalized_taxonomy;
		}

		foreach ( $parent_attributes as $attribute_key => $attribute ) {
			if ( ! is_object( $attribute ) || ! method_exists( $attribute, 'get_name' ) ) {
				continue;
			}

			$attribute_name = (string) $attribute->get_name();
			$key_name       = is_string( $attribute_key ) ? $attribute_key : '';
			$candidates     = array_filter( array( $attribute_name, $key_name ) );

			foreach ( $candidates as $candidate ) {
				$candidate_key = 0 === strpos( $candidate, 'attribute_' ) ? substr( $candidate, 10 ) : $candidate;
				if ( $candidate_key !== $normalized_taxonomy && sanitize_title( $candidate_key ) !== $target_slug ) {
					continue;
				}

				$label = method_exists( $attribute, 'is_taxonomy' ) && $attribute->is_taxonomy()
					? wc_attribute_label( $attribute_name )
					: $attribute_name;
				$label = self::clean_variation_attribute_label( $label );

				return '' !== $label ? $label : $fallback;
			}
		}

		return '' !== $fallback ? $fallback : $normalized_taxonomy;
	}

	/**
	 * Normalize an attribute label before it is used in variation summaries.
	 *
	 * @param string $label Raw label.
	 * @return string
	 */
	private static function clean_variation_attribute_label( string $label ): string {
		return rtrim( trim( $label ), ':' );
	}

	/**
	 * Resolve a variation attribute value into a human readable label.
	 *
	 * @param string $taxonomy Attribute taxonomy or key.
	 * @param mixed  $value    Raw value.
	 * @return string
	 */
	private static function resolve_variation_attribute_value( string $taxonomy, $value ): string {
		if ( ! is_scalar( $value ) ) {
			return '';
		}

		$clean_value = function_exists( 'wc_clean' ) ? wc_clean( (string) $value ) : sanitize_text_field( (string) $value );
		if ( '' === $clean_value ) {
			return '';
		}

		if ( taxonomy_exists( $taxonomy ) ) {
			$term = get_term_by( 'slug', $clean_value, $taxonomy );
			if ( $term instanceof WP_Term ) {
				return $term->name;
			}
		}

		return $clean_value;
	}

	/**
	 * Convert incoming flag to boolean.
	 *
	 * @param mixed $value Raw flag value.
	 * @return bool
	 */
	public static function normalize_flag( $value ): bool {
		if ( is_bool( $value ) ) {
			return $value;
		}

		$normalized = strtolower( trim( (string) $value ) );

		if ( '' === $normalized ) {
			return true;
		}

		if ( in_array( $normalized, array( '0', 'no', 'false', 'off' ), true ) ) {
			return false;
		}

		return true;
	}

	// Intentionally no camelCase alias: WordPress coding standards enforce snake_case.
}
