<?php

namespace SugarCalendarBookings\Support\Helpers;

/**
 * Filter label resolution and text formatting.
 *
 * Resolves raw filter values into structured display labels
 * and formats them as HTML text for search result notices.
 *
 * @since 1.4.0
 */
class FilterLabels {

	/**
	 * Static cache for resolved filter labels.
	 *
	 * Keyed by md5 hash of filter values to avoid duplicate DB queries.
	 *
	 * @since 1.4.0
	 *
	 * @var array
	 */
	private static $cache = [];

	/**
	 * Resolve active filter values into structured display labels.
	 *
	 * Takes raw filter values and a label map, resolves model IDs to names,
	 * and returns an indexed array of structured entries for build_text().
	 *
	 * Results are cached by input hash to prevent duplicate DB queries
	 * within the same request.
	 *
	 * @since 1.4.0
	 *
	 * @param array $values    Associative array of filter key => raw value (string or array of IDs).
	 * @param array $label_map Filter label map. Each entry: 'filter_key' => [ 'label' => string, 'label_plural' => string (optional), 'model' => string (optional) ].
	 *
	 * @return array Indexed array of entries: [ [ 'label' => string, 'label_plural' => string, 'value' => string|array ], ... ].
	 */
	public static function resolve( $values, $label_map ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		$json      = wp_json_encode( $values );
		$cache_key = $json !== false ? md5( $json ) : null;

		if ( $cache_key !== null && isset( self::$cache[ $cache_key ] ) ) {
			return self::$cache[ $cache_key ];
		}

		$entries = [];

		foreach ( $values as $key => $value ) {

			if ( empty( $value ) || ! isset( $label_map[ $key ] ) ) {
				continue;
			}

			$entry = self::resolve_entry( $value, $label_map[ $key ] );

			if ( $entry !== null ) {
				$entries[] = $entry;
			}
		}

		if ( $cache_key !== null ) {
			self::$cache[ $cache_key ] = $entries;
		}

		return $entries;
	}

	/**
	 * Resolve a single filter value into a structured display entry.
	 *
	 * @since 1.4.0
	 *
	 * @param string|array $value  Raw filter value (string or array of IDs).
	 * @param array        $config Label map config: [ 'label' => string, 'label_plural' => string (optional), 'model' => string (optional) ].
	 *
	 * @return array|null Entry array or null if value cannot be resolved.
	 */
	private static function resolve_entry( $value, $config ) {

		if ( empty( $config['label'] ) ) {
			return null;
		}

		$label        = $config['label'];
		$label_plural = $config['label_plural'] ?? $label;

		if ( isset( $config['model'] ) ) {
			return self::resolve_model_entry( $value, $label, $label_plural, $config['model'] );
		}

		return [
			'label'        => $label,
			'label_plural' => $label_plural,
			'value'        => is_array( $value ) ? array_map( 'ucfirst', $value ) : ucfirst( $value ),
		];
	}

	/**
	 * Resolve a model-based filter value by querying IDs to names.
	 *
	 * @since 1.4.0
	 *
	 * @param string|array $value        Raw filter value (ID or array of IDs).
	 * @param string       $label        Singular label.
	 * @param string       $label_plural Plural label.
	 * @param string       $model_class  Model class name.
	 *
	 * @return array|null Entry array or null if no valid IDs or names found.
	 */
	private static function resolve_model_entry( $value, $label, $label_plural, $model_class ) {

		$ids = array_filter( array_map( 'absint', (array) $value ) );

		if ( empty( $ids ) ) {
			return null;
		}

		$models = $model_class::query()->select( [ 'id', 'name' ] )->where( 'id', 'IN', $ids, '%d' )->get();
		$names  = array_map( fn( $m ) => $m->name, $models );

		if ( empty( $names ) ) {
			return null;
		}

		return [
			'label'        => $label,
			'label_plural' => $label_plural,
			'value'        => count( $names ) === 1 ? $names[0] : $names,
		];
	}

	/**
	 * Build the filter text portion of the search results notice.
	 *
	 * @since 1.4.0
	 *
	 * @param array $filters Indexed array of filter entries. Each entry: [ 'label' => string, 'label_plural' => string, 'value' => string|array ].
	 *
	 * @return string HTML string for the filter portion, or empty string if no filters.
	 */
	public static function build_text( $filters ) {

		if ( empty( $filters ) ) {
			return '';
		}

		$parts = array_filter(
			array_map( [ __CLASS__, 'get_filter_text_for_entry' ], $filters )
		);

		if ( empty( $parts ) ) {
			return '';
		}

		return ' ' . esc_html__( 'where', 'sugar-calendar-bookings-scheduling-appointments-lite' ) . ' ' . implode( ' · ', $parts );
	}

	/**
	 * Process a single filter entry into an HTML segment.
	 *
	 * @since 1.4.0
	 *
	 * @param array $entry Filter entry with 'label', 'label_plural', and 'value' keys.
	 *
	 * @return string HTML string like '<strong>Label</strong> is "value"', or empty string.
	 */
	private static function get_filter_text_for_entry( $entry ) {

		$value = $entry['value'];

		if ( $value === '' || $value === null ) {
			return '';
		}

		$parts = is_array( $value )
			? self::get_filter_text_for_array_value( $entry )
			: self::get_filter_text_for_single_value( $entry );

		if ( $parts === null ) {
			return '';
		}

		return sprintf(
			'<strong>%s</strong> %s %s',
			esc_html( $parts['label'] ),
			$parts['verb'],
			$parts['value_text']
		);
	}

	/**
	 * Format a single scalar filter value.
	 *
	 * @since 1.4.0
	 *
	 * @param array $entry Filter entry with 'label' and 'value' keys.
	 *
	 * @return array{ label: string, verb: string, value_text: string }
	 */
	private static function get_filter_text_for_single_value( $entry ) {

		return [
			'label'      => $entry['label'],
			'verb'       => esc_html__( 'is', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			'value_text' => sprintf( '"%s"', esc_html( $entry['value'] ) ),
		];
	}

	/**
	 * Format an array of filter values with grammar handling.
	 *
	 * Returns null if all values are empty after filtering.
	 *
	 * @since 1.4.0
	 *
	 * @param array $entry Filter entry with 'label', 'label_plural', and 'value' (array) keys.
	 *
	 * @return array{ label: string, verb: string, value_text: string }|null
	 */
	private static function get_filter_text_for_array_value( $entry ) {

		$values = array_filter( $entry['value'], fn( $v ) => $v !== '' );

		if ( empty( $values ) ) {
			return null;
		}

		$escaped_values = array_map( 'esc_html', $values );
		$last           = array_pop( $escaped_values );

		// Single array value uses singular label and "is".
		if ( empty( $escaped_values ) ) {
			return [
				'label'      => $entry['label'],
				'verb'       => esc_html__( 'is', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
				'value_text' => sprintf( '"%s"', $last ),
			];
		}

		// Multiple values use plural label and "are".
		return [
			'label'      => ! empty( $entry['label_plural'] ) ? $entry['label_plural'] : $entry['label'],
			'verb'       => esc_html__( 'are', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			'value_text' => sprintf(
				/* translators: 1: comma-separated quoted filter values, 2: last quoted filter value. Example: "Red", "Blue" and "Green". */
				esc_html__( '"%1$s" and "%2$s"', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
				implode( '", "', $escaped_values ),
				$last
			),
		];
	}
}
