<?php
namespace Pay4All\Pro\Domain;

use Pay4All\Pro\PostTypes;

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

final class Category {

	public const META_RULE_TYPE   = 'p4all_rule_type';
	public const META_MULTIPLE    = 'p4all_qty_multiple';
	public const META_MIN_TOTAL   = 'p4all_min_total';
	public const META_MAX_TOTAL   = 'p4all_max_total';
	public const META_EXACT_TOTAL = 'p4all_exact_total';
	public const META_ERROR_MSG   = 'p4all_error_message';

	/**
	 * Term meta storing per-language display names for a category, shape
	 *   [ 'fr' => 'Vin', 'de' => 'Wein', 'it' => 'Vino', 'en' => 'Wine' ]
	 * Empty entries are omitted. The canonical `$term->name` is kept as a
	 * fallback for the primary activated language.
	 */
	public const META_NAMES       = 'p4all_category_name_translations';

	/**
	 * Per-language "Tous les X" label used by the public filter widget when
	 * this category (or one of its ancestors) becomes a filter dropdown.
	 *   [ 'fr' => 'Tous les cépages', 'de' => 'Alle Rebsorten', … ]
	 */
	public const META_ALL_LABELS  = 'p4all_category_all_label_translations';

	/**
	 * Integer sort key set by the drag-and-drop reorder UI on the taxonomy
	 * edit screen. Consumed by the public filter widget so the merchant's
	 * ordering flows through to the customer's dropdowns. Defaults to 0
	 * (terms with no explicit order stay alphabetical among themselves).
	 */
	public const META_ORDER       = 'p4all_category_order';

	public const RULES = [ 'none', 'min', 'max', 'multiple', 'min_multiple', 'exact' ];

	public static function order( int $term_id ) : int {
		return (int) get_term_meta( $term_id, self::META_ORDER, true );
	}

	public static function rule_type( int $term_id ) : string {
		$v = (string) get_term_meta( $term_id, self::META_RULE_TYPE, true );
		return in_array( $v, self::RULES, true ) ? $v : 'none';
	}

	public static function multiple( int $term_id ) : int {
		return max( 1, (int) get_term_meta( $term_id, self::META_MULTIPLE, true ) );
	}

	public static function min_total( int $term_id ) : int {
		return max( 0, (int) get_term_meta( $term_id, self::META_MIN_TOTAL, true ) );
	}

	public static function max_total( int $term_id ) : int {
		return max( 0, (int) get_term_meta( $term_id, self::META_MAX_TOTAL, true ) );
	}

	public static function exact_total( int $term_id ) : int {
		return max( 0, (int) get_term_meta( $term_id, self::META_EXACT_TOTAL, true ) );
	}

	/**
	 * Returns the localised error message for the current site language.
	 * Falls back to the first activated language, then any non-empty entry,
	 * then an empty string. Legacy string-stored values are transparently
	 * treated as the message for the first activated language.
	 */
	public static function error_message( int $term_id ) : string {
		return self::error_message_for( $term_id, self::current_language() );
	}

	/**
	 * @return array<string,string> keyed by 2-char language code (fr/de/it/en)
	 */
	public static function error_messages( int $term_id ) : array {
		$raw = get_term_meta( $term_id, self::META_ERROR_MSG, true );

		// Legacy: a plain string from before multi-language support → migrate
		// in-memory to the array shape so the rest of the code doesn't care.
		if ( is_string( $raw ) ) {
			$raw = trim( $raw ) === '' ? [] : [ self::primary_language() => $raw ];
		}
		if ( ! is_array( $raw ) ) { return []; }

		$clean = [];
		foreach ( $raw as $lang => $msg ) {
			$lang = strtolower( (string) $lang );
			if ( in_array( $lang, \Pay4All\Form\Admin\Settings\Schema::SUPPORTED_LANGUAGES, true ) ) {
				$clean[ $lang ] = (string) $msg;
			}
		}
		return $clean;
	}

	/**
	 * Returns the per-language display names bag, keyed by 2-char code.
	 * @return array<string,string>
	 */
	public static function names( int $term_id ) : array {
		$raw = get_term_meta( $term_id, self::META_NAMES, true );
		if ( ! is_array( $raw ) ) { return []; }
		$out = [];
		foreach ( $raw as $lang => $name ) {
			$lang = strtolower( (string) $lang );
			if ( in_array( $lang, \Pay4All\Form\Admin\Settings\Schema::SUPPORTED_LANGUAGES, true ) ) {
				$name = trim( (string) $name );
				if ( $name !== '' ) { $out[ $lang ] = $name; }
			}
		}
		return $out;
	}

	/**
	 * Localised display name for the current request language. Fallback
	 * chain: current lang → primary activated lang → any non-empty entry
	 * → `$term->name` (the canonical WordPress name).
	 */
	public static function display_name( int $term_id ) : string {
		return self::display_name_for( $term_id, self::current_language() );
	}

	public static function display_name_for( int $term_id, string $lang ) : string {
		$names = self::names( $term_id );
		if ( isset( $names[ $lang ] ) && $names[ $lang ] !== '' ) { return $names[ $lang ]; }
		$primary = self::primary_language();
		if ( isset( $names[ $primary ] ) && $names[ $primary ] !== '' ) { return $names[ $primary ]; }
		foreach ( $names as $n ) {
			if ( trim( (string) $n ) !== '' ) { return (string) $n; }
		}
		$term = get_term( $term_id );
		return ( $term && ! is_wp_error( $term ) ) ? (string) $term->name : '';
	}

	/**
	 * Per-language "Tous les X" labels bag.
	 * @return array<string,string>
	 */
	public static function all_labels( int $term_id ) : array {
		$raw = get_term_meta( $term_id, self::META_ALL_LABELS, true );
		if ( ! is_array( $raw ) ) { return []; }
		$out = [];
		foreach ( $raw as $lang => $label ) {
			$lang = strtolower( (string) $lang );
			if ( in_array( $lang, \Pay4All\Form\Admin\Settings\Schema::SUPPORTED_LANGUAGES, true ) ) {
				$label = trim( (string) $label );
				if ( $label !== '' ) { $out[ $lang ] = $label; }
			}
		}
		return $out;
	}

	/** Localised "Tous les X" label for the current request language. */
	public static function display_all_label( int $term_id ) : string {
		return self::display_all_label_for( $term_id, self::current_language() );
	}

	public static function display_all_label_for( int $term_id, string $lang ) : string {
		$labels = self::all_labels( $term_id );
		if ( isset( $labels[ $lang ] ) && $labels[ $lang ] !== '' ) { return $labels[ $lang ]; }
		$primary = self::primary_language();
		if ( isset( $labels[ $primary ] ) && $labels[ $primary ] !== '' ) { return $labels[ $primary ]; }
		foreach ( $labels as $l ) {
			if ( trim( (string) $l ) !== '' ) { return (string) $l; }
		}
		// Generic fallback ("Tous") — merchant hasn't set the label yet. Not
		// as good as a bespoke phrasing but keeps the picker usable.
		return __( 'Tous', 'pay4all-pro' );
	}

	public static function error_message_for( int $term_id, string $lang ) : string {
		$all = self::error_messages( $term_id );
		if ( isset( $all[ $lang ] ) && $all[ $lang ] !== '' ) { return $all[ $lang ]; }
		// Fallback chain: primary activated language → first non-empty entry.
		$primary = self::primary_language();
		if ( isset( $all[ $primary ] ) && $all[ $primary ] !== '' ) { return $all[ $primary ]; }
		foreach ( $all as $msg ) {
			if ( trim( (string) $msg ) !== '' ) { return (string) $msg; }
		}
		return '';
	}

	/**
	 * Detects the current request language. Honours WPML and Polylang when
	 * present so admins keep a single source of truth for the active locale,
	 * then falls back to the WP locale's 2-char prefix, then to French.
	 */
	public static function current_language() : string {
		// WPML — apply_filters returns null when WPML is missing.
		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML-owned filter, we consume it.
		if ( has_filter( 'wpml_current_language' ) ) {
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML-owned filter.
			$lang = apply_filters( 'wpml_current_language', null );
			if ( is_string( $lang ) && $lang !== '' ) {
				return strtolower( substr( $lang, 0, 2 ) );
			}
		}
		// Polylang.
		if ( function_exists( 'pll_current_language' ) ) {
			$lang = pll_current_language( 'slug' );
			if ( is_string( $lang ) && $lang !== '' ) {
				return strtolower( substr( $lang, 0, 2 ) );
			}
		}
		$wp = strtolower( substr( (string) get_locale(), 0, 2 ) );
		return in_array( $wp, \Pay4All\Form\Admin\Settings\Schema::SUPPORTED_LANGUAGES, true ) ? $wp : self::primary_language();
	}

	/**
	 * First activated language in the merchant's settings — used as the
	 * authoritative fallback when a category has no message for the current
	 * language.
	 */
	public static function primary_language() : string {
		$langs = (array) \Pay4All\Form\Admin\Settings\Schema::get( 'languages', [] );
		$langs = array_values( array_intersect( $langs, \Pay4All\Form\Admin\Settings\Schema::SUPPORTED_LANGUAGES ) );
		return $langs[0] ?? 'fr';
	}

	/**
	 * Validate a (category_id => total_qty) map. Returns array of errors.
	 *
	 * @param array<int,int> $totals
	 * @return array<int,string>
	 */
	public static function validate_totals( array $totals ) : array {
		$errors = [];
		foreach ( $totals as $term_id => $qty ) {
			$term_id = (int) $term_id;
			$qty     = (int) $qty;
			if ( $term_id <= 0 ) { continue; }
			$err = self::validate_category( $term_id, $qty );
			if ( $err !== '' ) { $errors[ $term_id ] = $err; }
		}
		return $errors;
	}

	public static function validate_category( int $term_id, int $qty ) : string {
		if ( $qty <= 0 ) { return ''; }
		$type = self::rule_type( $term_id );
		if ( $type === 'none' ) { return ''; }

		$multiple = self::multiple( $term_id );
		$min      = self::min_total( $term_id );
		$max      = self::max_total( $term_id );
		$exact    = self::exact_total( $term_id );
		$custom   = self::error_message( $term_id );
		$cat_name = self::display_name( $term_id );

		switch ( $type ) {
			case 'min':
				if ( $qty < $min ) {
					return $custom !== '' ? $custom : sprintf(
						/* translators: 1: category name 2: minimum qty 3: current qty */
						__( '« %1$s » : commande minimum %2$d. Vous avez choisi %3$d.', 'pay4all-pro' ),
						$cat_name, $min, $qty
					);
				}
				return '';
			case 'max':
				if ( $max > 0 && $qty > $max ) {
					return $custom !== '' ? $custom : sprintf(
						/* translators: 1: category name, 2: max allowed qty, 3: current qty */
						__( '« %1$s » : maximum %2$d autorisé. Vous avez choisi %3$d.', 'pay4all-pro' ),
						$cat_name, $max, $qty
					);
				}
				return '';
			case 'multiple':
				if ( $multiple > 1 && ( $qty % $multiple ) !== 0 ) {
					return $custom !== '' ? $custom : sprintf(
						/* translators: 1: category name, 2: required multiple */
						__( '« %1$s » : commande par multiple de %2$d.', 'pay4all-pro' ),
						$cat_name, $multiple
					);
				}
				return '';
			case 'min_multiple':
				if ( $qty < $min ) {
					return $custom !== '' ? $custom : sprintf(
						/* translators: 1: category name, 2: minimum qty, 3: required multiple */
						__( '« %1$s » : commande minimum %2$d (par multiple de %3$d).', 'pay4all-pro' ),
						$cat_name, $min, $multiple
					);
				}
				if ( $multiple > 1 && ( $qty % $multiple ) !== 0 ) {
					return $custom !== '' ? $custom : sprintf(
						/* translators: 1: category name, 2: required multiple */
						__( '« %1$s » : commande par multiple de %2$d.', 'pay4all-pro' ),
						$cat_name, $multiple
					);
				}
				return '';
			case 'exact':
				if ( $exact > 0 && $qty !== $exact ) {
					return $custom !== '' ? $custom : sprintf(
						/* translators: 1: category name, 2: exact required qty */
						__( '« %1$s » : commande de %2$d exactement.', 'pay4all-pro' ),
						$cat_name, $exact
					);
				}
				return '';
		}
		return '';
	}
}
