<?php
/**
 * Truebe intent: a small, deterministic read of what KIND of answer a question wants.
 *
 * Two questions can share almost every word but want different answers: "who admitted
 * the patient" and "when was the patient admitted" overlap heavily, but the leading word
 * flips the target from a person to a date. The matcher scores word overlap; on its own it
 * can pick the wrong one of a close pair. Intent is the tie-breaker: when two seeds score
 * within the near-tie margin, the seed whose intent matches the question's intent wins.
 *
 * v1 scope: who, what, when, where, why, how, and yes/no. Aggregates ("list all ...") and
 * comparisons ("higher than last time") are deferred. No model, no API. Just the question.
 */

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

if ( class_exists( 'Truebe_Intent' ) ) {
	return;
}

class Truebe_Intent {

	/** Leading auxiliaries that signal a yes/no question. */
	private static $yes_no_leads = array(
		'do','does','did','is','are','was','were','can','could','will','would',
		'should','has','have','had','am','may','might','must',
	);

	/**
	 * Detect the question's intent.
	 *
	 * @param string $question
	 * @return array { intent:string (who|what|when|where|why|how|yes_no|unknown), is_yes_no:bool }
	 */
	public static function detect( $question ) {
		$intent = self::intent_of( $question );
		return array(
			'intent'    => $intent,
			'is_yes_no' => ( 'yes_no' === $intent ),
		);
	}

	/**
	 * The single intent label for a question.
	 *
	 * @param string $question
	 * @return string
	 */
	public static function intent_of( $question ) {
		$q = strtolower( trim( (string) $question ) );
		if ( '' === $q ) {
			return 'unknown';
		}

		$words = preg_split( '/\s+/', preg_replace( '/[^a-z0-9\s]/', ' ', $q ), -1, PREG_SPLIT_NO_EMPTY );
		if ( empty( $words ) ) {
			return 'unknown';
		}
		$first  = $words[0];
		$second = isset( $words[1] ) ? $words[1] : '';

		// "what time" and "how long" read as time questions.
		if ( 'what' === $first && in_array( $second, array( 'time', 'hours', 'day', 'days', 'date' ), true ) ) {
			return 'when';
		}
		if ( 'how' === $first && in_array( $second, array( 'long', 'soon' ), true ) ) {
			return 'when';
		}

		switch ( $first ) {
			case 'who':
			case 'whom':
			case 'whose':
				return 'who';
			case 'what':
			case 'which':
				return 'what';
			case 'when':
				return 'when';
			case 'where':
				return 'where';
			case 'why':
				return 'why';
			case 'how':
				return 'how';
		}

		if ( in_array( $first, self::$yes_no_leads, true ) ) {
			return 'yes_no';
		}

		return 'unknown';
	}

	/**
	 * Break a near-tie by intent. Given the tied candidate seeds, return the one whose
	 * intent matches the question's intent, but only if exactly one matches. If none or
	 * more than one match, return null and let the caller stay ambiguous.
	 *
	 * @param string  $question
	 * @param array[] $seeds Tied candidate seeds (each may have an 'intent' field).
	 * @return array|null The winning seed, or null if intent cannot decide.
	 */
	public static function break_tie( $question, $seeds ) {
		$want = self::intent_of( $question );
		if ( 'unknown' === $want || 'yes_no' === $want ) {
			return null; // No seed-intent equivalent to match against.
		}
		if ( ! is_array( $seeds ) ) {
			return null;
		}

		$matches = array();
		foreach ( $seeds as $seed ) {
			$seed_intent = isset( $seed['intent'] ) ? strtolower( trim( (string) $seed['intent'] ) ) : '';
			if ( $seed_intent === $want ) {
				$matches[] = $seed;
			}
		}

		return ( 1 === count( $matches ) ) ? $matches[0] : null;
	}
}