<?php
/**
 * Provider Manager.
 *
 * Manages the creation and configuration of LLM provider instances.
 * Handles provider registration, retrieval, and configuration management.
 *
 * @package   Agenistrator
 * @since     1.0.0
 * @author    Alex Ruban <alex@maximum.guru>
 * @author    Maximum Software Inc <contact@maximum.software>
 * @license   GPL-3.0-only
 * @link      https://agenistrator.com/
 *
 * SPDX-FileCopyrightText: 2025-2026 Alex Ruban <alex@maximum.guru>
 * SPDX-FileCopyrightText: 2025-2026 Maximum Software Inc <contact@maximum.software>
 * SPDX-License-Identifier: GPL-3.0-only
 */

namespace Agenistrator;

// Direct access prevention
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Provider_Manager class.
 */
class Provider_Manager extends Manager {
	/**
	 * List of registered provider types
	 *
	 * IMPORTANT: Provider type keys MUST follow the lowercase kebab-case convention
	 * documented on Provider_Type::get_type_id() (e.g., 'openai', 'ollama-cloud').
	 * Lowercase is required because WordPress sanitize_key() lowercases input in
	 * AJAX handlers.
	 *
	 * @var        array
	 */
	private static $provider_types = array(
		'openai'       => Provider_OpenAI::class,
		'anthropic'    => Provider_Anthropic::class,
		'openrouter'   => Provider_OpenRouter::class,
		'ollama-cloud' => Provider_Ollama_Cloud::class,
	);

	/**
	 * Provider instances cache
	 * @var        array
	 */
	private $instances = array();

	/**
	 * Constructor - initializes the manager
	 * @since     1.0.0
	 */
	protected function __construct() {
		// Call parent constructor with option suffix
		parent::__construct( 'providers' );
	}

	/**
	 * Get default configuration
	 *
	 * No default providers are created on activation.
	 * Users will be guided to set up providers through the UI.
	 *
	 * @return array Default configuration with empty providers array
	 * @since     1.0.0
	 */
	protected function get_default_config() {
		// Return configuration with empty providers array
		return array(
			'providers' => array(),
		);
	}

	/**
	 * Uninstall plugin
	 *
	 * Clears provider model cache transients and removes configuration.
	 * @since     1.0.0
	 */
	public function uninstall_plugin() {
		// Clear model cache transients for all providers before config is deleted
		foreach ( $this->get_all_providers() as $provider ) {
			$provider->clear_models_cache();
		}

		// Call parent to delete option
		parent::uninstall_plugin();
	}

	/**
	 * Get all registered provider types
	 * @return     array Array of provider types
	 * @since     1.0.0
	 */
	public function get_provider_types() {
		return array_keys( self::$provider_types );
	}

	/**
	 * Get all registered provider types with their human-readable display names
	 *
	 * Returns a map of type ID to localized display name for use in UI surfaces
	 * such as the provider type dropdown on the Add New Provider form.
	 *
	 * @return     array Associative array of type_id => display_name
	 * @since     1.0.1
	 */
	public function get_provider_type_display_names() {
		$names = array();
		foreach ( $this->get_provider_types() as $type_id ) {
			$provider          = $this->create_temp_provider( $type_id );
			$names[ $type_id ] = $provider->get_type_display_name();
		}
		return $names;
	}

	/**
	 * Check if a provider is enabled
	 * @param      string $provider_id The provider ID
	 * @return     bool True if enabled, false otherwise
	 * @since     1.0.0
	 */
	public function is_provider_enabled( $provider_id ) {
		return $this->get_config( array( 'providers', $provider_id, 'enabled' ), false );
	}

	/**
	 * Enable a provider
	 * @param      string $provider_id The provider ID
	 * @return     bool Success or failure
	 * @since     1.0.0
	 */
	public function enable_provider( $provider_id ) {
		// Check if provider exists
		if ( ! $this->get_config( array( 'providers', $provider_id ) ) ) {
			return false;
		}

		// Check if provider is already enabled
		if ( ! $this->get_config( array( 'providers', $provider_id, 'enabled' ), false ) ) {
			// Update the config and save
			return $this->set_config_and_save( array( 'providers', $provider_id, 'enabled' ), true );
		}

		return true;
	}

	/**
	 * Disable a provider
	 * @param      string $provider_id The provider ID
	 * @return     bool Success or failure
	 * @since     1.0.0
	 */
	public function disable_provider( $provider_id ) {
		// Check if provider exists
		if ( ! $this->get_config( array( 'providers', $provider_id ) ) ) {
			return false;
		}

		// Check if provider is already disabled
		if ( $this->get_config( array( 'providers', $provider_id, 'enabled' ), false ) ) {
			// Update the config and save
			return $this->set_config_and_save( array( 'providers', $provider_id, 'enabled' ), false );
		}

		return true;
	}

	/**
	 * Get a provider by ID
	 * @param      string $provider_id The provider ID
	 * @return     Provider_Type Provider instance
	 * @since     1.0.0
	 */
	public function get_provider( $provider_id ) {
		// Check if the provider exists in configuration
		$provider_config = $this->get_config( array( 'providers', $provider_id ) );
		if ( ! $provider_config ) {
			// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output
			throw new \Exception(
				String_Utils::replace_tags(
					__( 'Unknown provider: {provider_id}', 'agenistrator' ),
					array( 'provider_id' => $provider_id )
				)
			);
			// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
		}

		// Return from cache if available
		if ( isset( $this->instances[ $provider_id ] ) ) {
			return $this->instances[ $provider_id ];
		}

		// Get provider type
		$type = $provider_config['type'];

		// Check if the provider type exists
		if ( ! isset( self::$provider_types[ $type ] ) ) {
			// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output
			throw new \Exception(
				String_Utils::replace_tags(
					__( 'Unknown provider type: {type}', 'agenistrator' ),
					array( 'type' => $type )
				)
			);
			// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
		}

		$provider_class = self::$provider_types[ $type ];

		// Check if the class exists
		if ( ! class_exists( $provider_class ) ) {
			// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output
			throw new \Exception(
				String_Utils::replace_tags(
					__( 'Provider class not found: {provider_class}', 'agenistrator' ),
					array( 'provider_class' => $provider_class )
				)
			);
			// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
		}

		// Create provider instance with configuration
		$provider = new $provider_class(
			$provider_id,
			$provider_config['display_name'],
			$provider_config['parameters']
		);

		// Cache the instance
		$this->instances[ $provider_id ] = $provider;

		return $provider;
	}

	/**
	 * Create a new provider
	 *
	 * @param      string $type The provider type (must be lowercase)
	 * @param      string $display_name The display name
	 * @param      array $parameters The parameters
	 * @return     string The new provider ID (lowercase)
	 * @since     1.0.0
	 */
	public function create_provider( $type, $display_name, $parameters = array() ) {
		// Normalize type to lowercase for consistent lookups (sanitize_key() lowercases input)
		$type = strtolower( $type );

		// Check if the provider type exists
		if ( ! isset( self::$provider_types[ $type ] ) ) {
			// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output
			throw new \Exception(
				String_Utils::replace_tags(
					__( 'Unknown provider type: {type}', 'agenistrator' ),
					array( 'type' => $type )
				)
			);
			// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
		}

		// Generate a unique provider ID (lowercase for consistent lookups)
		$provider_id = $type . '_' . uniqid();

		// Add to configurations and save
		$config = array(
			'type'         => $type,
			'display_name' => $display_name,
			'parameters'   => $parameters,
			'enabled'      => true, // New providers are enabled by default
		);

		$this->set_config_and_save( array( 'providers', $provider_id ), $config );

		return $provider_id;
	}

	/**
	 * Update a provider configuration
	 * @param      string $provider_id The provider ID
	 * @param      string $display_name The display name
	 * @param      array $parameters The parameters
	 * @return     bool Success or failure
	 * @since     1.0.0
	 */
	public function update_provider( $provider_id, $display_name, $parameters = null ) {
		// Check if the provider exists
		if ( ! $this->get_config( array( 'providers', $provider_id ) ) ) {
			return false;
		}

		// Update display name
		$this->set_config( array( 'providers', $provider_id, 'display_name' ), $display_name );

		// Update parameters if provided
		if ( null !== $parameters ) {
			$this->set_config( array( 'providers', $provider_id, 'parameters' ), $parameters );
		}

		// Clear instance from cache
		if ( isset( $this->instances[ $provider_id ] ) ) {
			unset( $this->instances[ $provider_id ] );
		}

		// Save configurations
		$this->save_config();

		return true;
	}

	/**
	 * Delete a provider
	 * @param      string $provider_id The provider ID
	 * @return     bool Success or failure
	 * @since     1.0.0
	 */
	public function delete_provider( $provider_id ) {
		// Check if the provider exists
		if ( ! $this->get_config( array( 'providers', $provider_id ) ) ) {
			return false;
		}

		// Get a copy of the providers config
		$providers = $this->get_config( array( 'providers' ), array() );

		// Remove from configurations
		unset( $providers[ $provider_id ] );

		// Clear instance from cache
		if ( isset( $this->instances[ $provider_id ] ) ) {
			unset( $this->instances[ $provider_id ] );
		}

		// Update the providers config and save
		return $this->set_config_and_save( array( 'providers' ), $providers );
	}

	/**
	 * Get all providers that are enabled and properly configured
	 * @return     array Array of enabled and properly configured Provider_Type instances
	 * @since     1.0.0
	 */
	public function get_enabled_providers() {
		$providers         = $this->get_config( array( 'providers' ), array() );
		$enabled_providers = array();

		foreach ( $providers as $provider_id => $config ) {
			// Skip disabled providers
			if ( ! isset( $config['enabled'] ) || ! $config['enabled'] ) {
				continue;
			}

			try {
				$provider = $this->get_provider( $provider_id );
				// Check if the provider has all required parameters set
				if ( $provider->is_configured() ) {
					$enabled_providers[ $provider_id ] = $provider;
				}
			} catch ( \Throwable $e ) {
				// Skip providers with errors
				continue;
			}
		}

		return $enabled_providers;
	}

	/**
	 * Get all providers (including disabled ones)
	 * @return     array Array of all Provider_Type instances
	 * @since     1.0.0
	 */
	public function get_all_providers() {
		$providers_config = $this->get_config( array( 'providers' ), array() );
		$providers        = array();

		foreach ( array_keys( $providers_config ) as $provider_id ) {
			try {
				$providers[ $provider_id ] = $this->get_provider( $provider_id );
			} catch ( \Throwable $e ) {
				// Skip providers with errors
				continue;
			}
		}

		return $providers;
	}

	/**
	 * Create a temporary provider for testing
	 *
	 * @param      string $type The provider type (must be lowercase)
	 * @param      array $parameters The parameters to test
	 * @return     Provider_Type A temporary provider instance
	 * @since     1.0.0
	 */
	public function create_temp_provider( $type, $parameters = array() ) {
		// Normalize type to lowercase for consistent lookups (sanitize_key() lowercases input)
		$type = strtolower( $type );

		// Check if the provider type exists
		if ( ! isset( self::$provider_types[ $type ] ) ) {
			// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output
			throw new \Exception(
				String_Utils::replace_tags(
					__( 'Unknown provider type: {type}', 'agenistrator' ),
					array( 'type' => $type )
				)
			);
			// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
		}

		$provider_class = self::$provider_types[ $type ];

		// Check if the class exists
		if ( ! class_exists( $provider_class ) ) {
			// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output
			throw new \Exception(
				String_Utils::replace_tags(
					__( 'Provider class not found: {provider_class}', 'agenistrator' ),
					array( 'provider_class' => $provider_class )
				)
			);
			// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
		}

		// Create a temporary provider instance
		$provider = new $provider_class( 'temp_' . uniqid(), 'Temporary ' . $type, $parameters );

		return $provider;
	}
}
