<?php
/**
 * Ollama Cloud Provider.
 *
 * Implements the provider interface for Ollama Cloud (https://ollama.com/cloud).
 * Uses the orhanerday/open-ai package with Ollama's OpenAI-compatible endpoint
 * at https://ollama.com/v1. Embeddings are not currently offered on the cloud
 * tier, so the embedding factory throws.
 *
 * @package   Agenistrator
 * @since     1.0.1
 * @author    Alex Ruban <alex@maximum.guru>
 * @author    Maximum Software Inc <contact@maximum.software>
 * @license   GPL-3.0-only
 * @link      https://agenistrator.com/
 *
 * SPDX-FileCopyrightText: 2026 Alex Ruban <alex@maximum.guru>
 * SPDX-FileCopyrightText: 2026 Maximum Software Inc <contact@maximum.software>
 * SPDX-License-Identifier: GPL-3.0-only
 */

namespace Agenistrator;

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

/**
 * Provider_Ollama_Cloud class.
 */
class Provider_Ollama_Cloud extends Provider_OpenAI {

	/**
	 * Get the provider type ID
	 *
	 * @return     string Lowercase provider type identifier
	 * @since     1.0.1
	 */
	public function get_type_id() {
		return 'ollama-cloud';
	}

	/**
	 * Get the human-readable display name for this provider type
	 *
	 * @return     string Localized provider type display name
	 * @since     1.0.1
	 */
	public function get_type_display_name() {
		return __( 'Ollama Cloud', 'agenistrator' );
	}

	/**
	 * Get the parameter definitions for this provider
	 *
	 * @return     array Array of parameter definitions
	 * @since     1.0.1
	 */
	public function get_parameter_definitions() {
		return array(
			'api_key' => array(
				'name'        => __( 'API Key', 'agenistrator' ),
				'type'        => 'password',
				'description' => __( 'Your Ollama Cloud API key.', 'agenistrator' ),
				'required'    => true,
			),
		);
	}

	/**
	 * Get the URL to obtain an API key for this provider
	 *
	 * @return     string URL to the Ollama Cloud API keys page
	 * @since     1.0.1
	 */
	public function get_api_key_url() {
		return 'https://ollama.com/settings/keys';
	}

	/**
	 * Get the required parameters for this provider
	 *
	 * @return     array Array of required parameter names
	 * @since     1.0.1
	 */
	public function get_required_parameters() {
		return array( 'api_key' );
	}

	/**
	 * Get the max tokens per minute rate limit for this provider
	 *
	 * Ollama Cloud uses subscription-tier concurrency limits rather than TPM,
	 * so we disable the token-per-minute cap.
	 *
	 * @return     int|null TPM limit or null to disable rate limiting
	 * @since     1.0.1
	 */
	public function get_max_tokens_per_minute() {
		return null;
	}

	/**
	 * Get the models.dev API key for looking up model info
	 *
	 * @return     string The models.dev provider key
	 * @since     1.0.1
	 */
	public function get_models_dev_key() {
		return 'ollama-cloud';
	}

	/**
	 * Override base URL for Ollama Cloud's OpenAI-compatible endpoint
	 *
	 * The orhanerday/open-ai library appends /v1 to paths, so we pass the bare
	 * origin without /v1.
	 *
	 * @return     string|null
	 * @since     1.0.1
	 */
	protected function get_client_base_url() {
		return 'https://ollama.com';
	}

	/**
	 * Override headers to include the bearer token for Ollama Cloud
	 *
	 * @return     array
	 * @since     1.0.1
	 */
	protected function get_client_headers() {
		return array(
			'User-Agent: Agenistrator/' . AGENISTRATOR_VERSION,
			'Authorization: Bearer ' . String_Utils::sanitize_header_value( $this->get_param( 'api_key' ) ),
		);
	}

	/**
	 * Create a Chat model instance
	 *
	 * @param      string $model_id The model ID
	 * @return     LLM_Chat_Ollama_Cloud Chat model instance
	 * @since     1.0.1
	 */
	public function create_chat_model( string $model_id ) {
		return new LLM_Chat_Ollama_Cloud( $model_id, $this );
	}

	/**
	 * Create an Embedding model instance
	 *
	 * @param      string $model_id The model ID
	 * @return     LLM_Embedding Embedding model instance
	 * @throws     \Exception Ollama Cloud does not currently provide embedding models
	 * @since     1.0.1
	 */
	// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- required by abstract Provider_Type::create_embedding_model()
	public function create_embedding_model( string $model_id ) {
		// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output
		throw new \Exception( __( 'Ollama Cloud does not currently provide embedding models.', 'agenistrator' ) );
	}

	/**
	 * Get all available models from the Ollama Cloud API
	 *
	 * Ollama Cloud's /v1/models endpoint returns a flat list with no embedding
	 * models and no context/output token info; the latter is resolved through
	 * Model_Info's models.dev fallback (see get_models_dev_key()).
	 *
	 * @return     array Models categorized by type (chat only)
	 * @since     1.0.1
	 */
	public function get_available_models() {
		if ( ! $this->is_configured() ) {
			// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output
			throw new \Exception( __( 'Provider is not properly configured to get available models.', 'agenistrator' ) );
		}

		if ( null !== $this->available_models ) {
			return $this->available_models;
		}

		try {
			$client        = $this->get_client();
			$response_json = $client->listModels();

			try {
				$response = String_Utils::decode_json( $response_json );
			} catch ( \Throwable $e ) {
				throw new \Exception(
					String_Utils::replace_tags(
						__( 'Invalid API response: {error}', 'agenistrator' ),
						array( 'error' => $e->getMessage() )
					)
				);
			}

			if ( isset( $response['error']['message'] ) ) {
				// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output
				throw new \Exception( $response['error']['message'] );
			}

			$models = array(
				'chat'      => array(),
				'embedding' => array(),
			);

			if ( isset( $response['data'] ) && is_array( $response['data'] ) ) {
				foreach ( $response['data'] as $model ) {
					$model_id = $model['id'] ?? '';
					if ( '' === $model_id ) {
						continue;
					}
					$models['chat'][] = array(
						'id'   => $model_id,
						'name' => $this->build_model_display_name( $model_id ),
					);
				}
			}

			$this->available_models = $models;

			return $models;
		} catch ( \Throwable $e ) {
			// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output
			throw new \Exception(
				String_Utils::replace_tags(
					__( 'Failed to retrieve models from Ollama Cloud API: {error}', 'agenistrator' ),
					array( 'error' => $e->getMessage() ?: __( 'Unknown error', 'agenistrator' ) )
				)
			);
			// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
		}
	}

	/**
	 * Get the default chat model ID
	 *
	 * @return     string|null Default chat model ID or null if not available
	 * @since     1.0.1
	 */
	public function get_default_chat_model() {
		$default = 'gemma4:31b-cloud';
		return $this->has_model( $default ) ? $default : null;
	}

	/**
	 * Get the default embedding model ID
	 *
	 * Ollama Cloud does not provide embedding models, so we return null to
	 * indicate that embeddings should be disabled by default for this provider.
	 *
	 * @return     string|null Default embedding model ID or null if none
	 * @since     1.0.1
	 */
	public function get_default_embedding_model() {
		return null;
	}
}
