<?php
/**
 * Lead Form Generator — LLM-assisted Branching Router
 *
 * Given a user's free-text / choice answer and a list of candidate next
 * question ids, ask the active frontend LLM to pick the best next question.
 *
 * Failure modes are all silent: if auth, network, model, or parsing fails,
 * the router returns null and the caller falls back to document-order flow.
 *
 * @package Niwa_AI
 * @since   0.12.0
 */

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

class Niwa_AI_Lead_Form_LLM_Router {

	/**
	 * @var Niwa_AI_Model_Router|null
	 */
	private $model_router;

	/**
	 * @var Niwa_AI_Auth_Manager|null
	 */
	private $auth_manager;

	/**
	 * @var Niwa_AI_OpenAI_Client|null
	 */
	private $client;

	/**
	 * Constructor.
	 *
	 * Any nullable dependency → router degrades to null responses.
	 *
	 * @param Niwa_AI_Model_Router|null $model_router Frontend model router.
	 * @param Niwa_AI_Auth_Manager|null $auth_manager Frontend-lane auth manager.
	 * @param Niwa_AI_OpenAI_Client|null $client      Frontend-lane OpenAI client.
	 */
	public function __construct( $model_router, $auth_manager, $client ) {
		$this->model_router = $model_router;
		$this->auth_manager = $auth_manager;
		$this->client       = $client;
	}

	/**
	 * Choose the next question id from a candidate list, given the last answer.
	 *
	 * @param array  $question          Current question schema array (must have id, label).
	 * @param mixed  $answer            User's answer (string or array of strings for multi-choice).
	 * @param array  $candidates        List of candidate question ids (strings).
	 * @param string $admin_instruction Optional admin guidance text (any language).
	 * @param array  $schema            Full schema (for candidate label lookup).
	 * @return string|null Chosen qid, Schema::COMPLETE_SENTINEL, or null on failure/no-match.
	 */
	public function choose_next( array $question, $answer, array $candidates, $admin_instruction, array $schema ) {
		// Graceful degradation — if any dep is missing, punt.
		if ( ! $this->model_router || ! $this->auth_manager || ! $this->client ) {
			return null;
		}
		if ( empty( $candidates ) ) {
			return null;
		}

		// Sanity: candidate ids must be non-empty strings.
		$candidates = array_values( array_filter( array_map( 'strval', $candidates ), 'strlen' ) );
		if ( empty( $candidates ) ) {
			return null;
		}

		try {
			$model = (string) $this->model_router->route_for_frontend_chat();
			if ( '' === $model ) {
				return null;
			}

			$auth_mode = $this->auth_manager->get_mode();
			$creds     = $this->auth_manager->get_credentials();

			$messages = $this->build_messages( $question, $answer, $candidates, $admin_instruction, $schema );

			$response = $this->client->chat_complete( $model, $messages, $auth_mode, $creds, 12, null, null );
			if ( ! is_array( $response ) || empty( $response['success'] ) ) {
				return null;
			}

			$raw = isset( $response['answer'] ) ? (string) $response['answer'] : '';
			if ( '' === $raw && isset( $response['text'] ) ) {
				$raw = (string) $response['text'];
			}

			return $this->parse_choice( $raw, $candidates );
		} catch ( \Throwable $e ) {
			return null;
		}
	}

	/**
	 * Build the chat-completion messages array.
	 *
	 * @param array  $question          Current question.
	 * @param mixed  $answer            Answer.
	 * @param array  $candidates        Candidate qids.
	 * @param string $admin_instruction Admin instruction.
	 * @param array  $schema            Schema for label lookup.
	 * @return array
	 */
	private function build_messages( array $question, $answer, array $candidates, $admin_instruction, array $schema ) {
		$index = Niwa_AI_Lead_Form_Schema::question_index( $schema );

		$lines = array();
		foreach ( $candidates as $qid ) {
			$label = '';
			if ( isset( $index[ $qid ]['question']['label'] ) ) {
				$label = (string) $index[ $qid ]['question']['label'];
			}
			$lines[] = '- ' . $qid . ( '' !== $label ? ' :: ' . $label : '' );
		}
		$candidate_block = implode( "\n", $lines );

		$answer_text = is_array( $answer )
			? implode( ', ', array_map( 'strval', $answer ) )
			: (string) $answer;

		$system = 'You are a branching router for a lead-capture form. '
			. 'Given the user\'s answer to a question, you MUST choose exactly one question id from the supplied candidate list as the next question to ask, '
			. 'OR return the literal string "' . Niwa_AI_Lead_Form_Schema::COMPLETE_SENTINEL . '" if no further question should be asked and the flow should end. '
			. 'Respond with ONLY the chosen id (or the sentinel). No explanation, no punctuation, no extra words. '
			. 'If none of the candidates clearly fits, return the first candidate.';

		$admin_instruction = trim( (string) $admin_instruction );
		if ( '' !== $admin_instruction ) {
			$system .= "\n\nAdditional admin directive (follow verbatim, may be in any language):\n" . $admin_instruction;
		}

		$user = "Current question (id=" . (string) ( isset( $question['id'] ) ? $question['id'] : '' ) . "): "
			. (string) ( isset( $question['label'] ) ? $question['label'] : '' ) . "\n"
			. "User's answer: " . $answer_text . "\n\n"
			. "Candidate next questions (id :: label):\n" . $candidate_block . "\n\n"
			. 'Reply with exactly one candidate id or "' . Niwa_AI_Lead_Form_Schema::COMPLETE_SENTINEL . '".';

		return array(
			array( 'role' => 'system', 'content' => $system ),
			array( 'role' => 'user',   'content' => $user ),
		);
	}

	/**
	 * Parse the model output into a valid candidate id.
	 *
	 * @param string $raw        Raw model output.
	 * @param array  $candidates Allowed ids.
	 * @return string|null
	 */
	private function parse_choice( $raw, array $candidates ) {
		$raw = trim( (string) $raw );
		if ( '' === $raw ) {
			return null;
		}

		// Strip surrounding quotes/backticks/punctuation.
		$raw = trim( $raw, "\"'` \t\n\r\0\x0B.," );

		// Collapse to first token — model may have added stray words despite instructions.
		$tokens = preg_split( '/\s+/', $raw );
		if ( ! empty( $tokens[0] ) ) {
			$first = trim( (string) $tokens[0], "\"'` \t\n\r\0\x0B.," );
			if ( '' !== $first ) {
				$raw = $first;
			}
		}

		$lower = strtolower( $raw );

		if ( Niwa_AI_Lead_Form_Schema::COMPLETE_SENTINEL === $lower ) {
			return Niwa_AI_Lead_Form_Schema::COMPLETE_SENTINEL;
		}

		foreach ( $candidates as $qid ) {
			if ( strtolower( (string) $qid ) === $lower ) {
				return (string) $qid;
			}
		}

		return null;
	}
}
