<?php

namespace Limb_Chatbot\Includes\Utilities;

use Limb_Chatbot\Includes\Data_Objects\Setting;
use Limb_Chatbot\Includes\AI_Providers\AI_Provider;
use Limb_Chatbot\Includes\AI_Providers\AI_Providers;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Services\Helper;
use Limb_Chatbot\Includes\Traits\Json_Serializable_Trait;
use ReflectionClass;

/**
 * Class Utility
 *
 * Base utility class that provides AI provider integration and generic utility functions.
 *
 * @package Limb_Chatbot\Includes\Utilities
 *
 * @since 1.0.0
 */
class Utility implements \JsonSerializable {

	use Json_Serializable_Trait;

	/**
	 * Static utility name.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public static string $name = '';

	/**
	 * Get the name of this utility.
	 *
	 * @return string Utility name.
	 *
	 * @since 1.0.0
	 */
	public static function get_name(): string {
		return static::$name;
	}

	/**
	 * Get the AI provider instance associated with this utility.
	 *
	 * @return AI_Provider|null AI provider instance or null.
	 *
	 * @throws Exception Throws if AI provider ID is not set.
	 *
	 * @since 1.0.0
	 */
	public function get_ai_provider(): ?AI_Provider {
		if ( $ai_provider_id = $this->get_ai_provider_id() ) {
			return AI_Providers::instance()->get_ai_provider( $ai_provider_id );
		}
		throw ( new Exception( Error_Codes::AI_PROVIDER_NOT_SET, __( 'Ai Provider isn\'t setup.', 'limb-chatbot' ) ) )->with_link( $this, null, null, 'chatbot', 'ai-settings', 'config_id' );
	}

	/**
	 * Get the AI provider's utility instance related to this utility.
	 *
	 * @return mixed AI provider utility instance.
	 *
	 * @throws Exception Throws if AI provider utility is missing or AI provider is unknown.
	 *
	 * @since 1.0.0
	 */
	public function get_ai_provider_utility() {
		$ai_provider = $this->get_ai_provider();
		if ( ! ( $ai_provider instanceof AI_Provider ) ) {
			throw ( new Exception( Error_Codes::AI_PROVIDER_NOT_SET, sprintf( __( 'Unknown Ai Provider.', 'limb-chatbot' ), Helper::get_class_shortname( static::class ) ) ) )->with_link( $this, null, null, 'chatbot',
				'ai-settings', 'config_id' );
		}
		if ( $utility = $ai_provider->get_ai_providers_utility( $this ) ) {
			return $utility;
		}
		// translators: %1$s is the AI provider name, %2$s is the utility name or class shortname.
		throw ( new Exception( Error_Codes::AI_PROVIDER_MISSING_UTILITY,
			sprintf( __( '%s doesn\'t support the %s utility.', 'limb-chatbot' ), $ai_provider->get_name(), Helper::get_class_shortname( static::class ) ) ) )->with_link( $this, null, null, 'chatbot', 'ai-settings',
			'config_id' );
	}

	/**
	 * Generate data using the AI provider utility.
	 *
	 * @return mixed
	 *
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function generate() {
		return $this->get_ai_provider_utility()->generate();
	}

	/**
	 * Create data using the AI provider utility.
	 *
	 * @return mixed
	 *
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function create() {
		return $this->get_ai_provider_utility()->create();
	}

	/**
	 * Run process using the AI provider utility.
	 *
	 * @return mixed
	 *
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function run() {
		return $this->get_ai_provider_utility()->run();
	}

	/**
	 * Retrieve data using the AI provider utility.
	 *
	 * @return mixed
	 *
	 * @since 1.0.0
	 */
	public function retrieve() {
		return $this->get_ai_provider_utility()->retrieve();
	}

	/**
	 * Magic setter for properties.
	 *
	 * @param  string  $name  Property name.
	 * @param  mixed  $value  Property value.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function __set( string $name, $value ): void {
		if ( ! property_exists( $this, $name ) ) {
			$this->{$name} = $value;
		}
	}

	/**
	 * Populate class properties from settings, matching by property name prefixed with setting prefix.
	 *
	 * @param  string  $setting_prefix  Prefix to prepend to property names when looking up settings.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function populate_properties( $setting_prefix ): void {
		$reflection = new ReflectionClass( $this );
		$properties = $reflection->getProperties();
		foreach ( $properties as $property ) {
			if ( $property->getDeclaringClass()->getName() !== $reflection->getName() ) {
				// If not own property, pass it.
				continue;
			}
			if ( ! $property->getType() || ! $property->getType()->isBuiltin() ) {
				// If not built in property type, pass it.
				continue;
			}
			$value = Setting::find( $setting_prefix . $property->getName() )->get_value();
			if ( ! empty( $value ) ) {
				$property->setValue( $this, $value );
			}
		}
	}
}