<?php

namespace Limb_Chatbot\Factories;

use Exception;
use Limb_Chatbot\Includes\Data_Objects\Chatbot_Limit;
use Limb_Chatbot\Includes\Data_Objects\Guest_Limit;
use Limb_Chatbot\Includes\Data_Objects\Input_Limit;
use Limb_Chatbot\Includes\Data_Objects\Limit;
use Limb_Chatbot\Includes\Data_Objects\Role_Limit;
use Limb_Chatbot\Includes\Interfaces\Usage_Service_Interface;
use Limb_Chatbot\Includes\Services\Chatbot_Usage_Service;
use Limb_Chatbot\Includes\Services\Input_Usage_Service;
use Limb_Chatbot\Includes\Services\User_Roles;
use Limb_Chatbot\Includes\Services\User_Usage_Service;


/**
 * Factory class for creating Usage_Service_Interface instances based on Limit type.
 *
 * @since 1.0.0
 */
class Usage_Service_Factory {

	/**
	 * Create a usage service instance based on the given Limit type.
	 *
	 * Flow:
	 *  1. Guard  — return null when this limit does not apply to the current user.
	 *  2. Input_Limit — always maps to Input_Usage_Service (stateless word-count).
	 *  3. Guest / Role — delegate to unit-based resolution (shared logic).
	 *
	 * Authenticated WP users are handled exclusively by Role_Limit (per-role).
	 * Non-authenticated guests are handled by Guest_Limit.
	 *
	 * @param Limit $limit Limit object to determine the usage service.
	 *
	 * @return Usage_Service_Interface|null Returns an instance of Usage_Service_Interface or null if not applicable.
	 *
	 * @throws Exception Throws if the Limit type is unknown.
	 *
	 * @since 1.0.0
	 */
	public function make( Limit $limit ): ?Usage_Service_Interface {
		if ( $limit instanceof Chatbot_Limit ) {
			return new Chatbot_Usage_Service();
		}
		if ( ! $this->is_applicable( $limit ) ) {
			return null;
		}

		if ( $limit instanceof Guest_Limit || $limit instanceof Role_Limit ) {
			return $this->resolve_by_unit( $limit );
		}

		if ( $limit instanceof Input_Limit ) {
			return new Input_Usage_Service();
		}

		throw new Exception( 'Unknown limit' );
	}

	/**
	 * Determine whether the given limit applies to the current user context.
	 *
	 * - Guest_Limit  : only for non-authenticated visitors.
	 * - Role_Limit   : only when the user actually holds the required role.
	 * - Everything else (Input_Limit, …) is always applicable.
	 *
	 * @param Limit $limit
	 *
	 * @return bool
	 *
	 * @since 1.0.0
	 */
	private function is_applicable( Limit $limit ): bool {
		if ( $limit instanceof Guest_Limit ) {
			// Guest limit only applies to non-authenticated visitors.
			return empty( $limit->get_chatbot_user()->get_wp_user_id() );
		}

		if ( $limit instanceof Role_Limit ) {
			$chatbot_user = $limit->get_chatbot_user();
			if ( ! $chatbot_user ) {
				return false;
			}
			$user_roles = User_Roles::resolve_current_chatbot_user_roles( $chatbot_user );
			// Limit only applies when the user actually holds the required role.
			return in_array( $limit->get_role(), $user_roles, true );
		}

		// Input_Limit and any future types are always applicable.
		return true;
	}

	/**
	 * Map a limit's unit to the correct usage service.
	 *
	 * Word-count checks are stateless → Input_Usage_Service.
	 * Message-count checks are stateful  → User_Usage_Service.
	 *
	 * Shared by Guest_Limit and Role_Limit — no duplication.
	 *
	 * @param Limit $limit
	 *
	 * @return Usage_Service_Interface
	 *
	 * @since 1.0.0
	 */
	private function resolve_by_unit( Limit $limit ): Usage_Service_Interface {
		if ( $limit->get_unit() === Limit::UNIT_WORD ) {
			return new Input_Usage_Service();
		}

		return new User_Usage_Service();
	}
}
