<?php
/**
 * Anthropic Provider.
 *
 * Implements the provider interface for Anthropic's Claude models. Handles
 * model creation, API key management, and provider-specific settings. Uses the
 * orhanerday/open-ai package with Anthropic's OpenAI-compatible endpoint.
 *
 * @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://maximum.software/
 *
 * 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' ) ) {
	return;
}

/**
 * Provider_Anthropic class.
 */
class Provider_Anthropic extends Provider_OpenAI {

	/**
	 * Get the provider type ID
	 *
	 * @return     string Lowercase provider type identifier
	 * @since     1.0.0
	 */
	public function get_type_id() {
		return 'anthropic';
	}

	/**
	 * Default max tokens per minute for Anthropic
	 */
	public const DEFAULT_MAX_TOKENS_PER_MINUTE = 30000;

	/**
	 * Get the parameter definitions for this provider
	 *
	 * @return     array Array of parameter definitions
	 * @since     1.0.0
	 */
	public function get_parameter_definitions() {
		return array(
			'api_key'               => array(
				'name'        => 'API Key',
				'type'        => 'password',
				'description' => 'Your Anthropic API key. Get it from your Anthropic dashboard.',
				'required'    => true,
			),
			'max_tokens_per_minute' => array(
				'name'        => 'Max Tokens Per Minute',
				'type'        => 'number',
				'description' => 'Rate limit in tokens per minute. Increase this if you have a higher API tier.',
				'required'    => false,
				'default'     => self::DEFAULT_MAX_TOKENS_PER_MINUTE,
				'min'         => 1000,
				'step'        => 1000,
			),
		);
	}

	/**
	 * Get the URL to obtain an API key for this provider
	 *
	 * @return     string URL to the Anthropic console
	 * @since     1.0.0
	 */
	public function get_api_key_url() {
		return 'https://console.anthropic.com/';
	}

	/**
	 * Get the max tokens per minute rate limit for this provider
	 *
	 * @return     int TPM limit (never null for Anthropic)
	 * @since     1.0.0
	 */
	public function get_max_tokens_per_minute() {
		return (int) $this->get_param( 'max_tokens_per_minute', self::DEFAULT_MAX_TOKENS_PER_MINUTE );
	}

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

	/**
	 * Override base URL for Anthropic's OpenAI-compatible endpoint
	 *
	 * @return     string|null
	 * @since     1.0.0
	 */
	protected function get_client_base_url() {
		return 'https://api.anthropic.com';
	}

	/**
	 * Override headers to include Anthropic API version
	 *
	 * @return     array
	 * @since     1.0.0
	 */
	protected function get_client_headers() {
		return array(
			'User-Agent: Agenistrator/' . AGENISTRATOR_VERSION,
			'anthropic-version: 2023-06-01',
			'x-api-key: ' . $this->get_param( 'api_key' ),
		);
	}

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

	/**
	 * Create an Embedding model instance
	 *
	 * @param      string $model_id The model ID
	 * @return     LLM_Embedding Embedding model instance
	 * @throws     \Exception Anthropic does not support embeddings
	 * @since     1.0.0
	 */
	// 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( __( 'Anthropic does not currently provide embedding models.', 'agenistrator' ) );
	}

	/**
	 * Get all available models from the provider API
	 * @return     array Models categorized by type (chat only for Anthropic)
	 * @since     1.0.0
	 */
	public function get_available_models() {
		if ( ! $this->is_configured() ) {
			throw new \Exception( 'Provider is not properly configured to get available models.' );
		}

		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'] ) && isset( $response['error']['message'] ) ) {
				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 ( strncmp( $model_id, 'claude-', 7 ) === 0 && $this->is_chat_compatible( $model_id ) ) {
						$models['chat'][] = array(
							'id'   => $model_id,
							'name' => $model['display_name'] ?? $model_id,
						);
					}
				}
			}

			$this->available_models = $models;

			return $models;
		} catch ( \Throwable $e ) {
			// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output
			throw new \Exception( 'Failed to retrieve models from Anthropic API: ' . $e->getMessage() );
		}
	}

	/**
	 * Get the default chat model ID
	 *
	 * @return     string|null Default chat model ID or null if not available
	 * @since     1.0.0
	 */
	public function get_default_chat_model() {
		$default = 'claude-haiku-4-5-20251001';
		return $this->has_model( $default ) ? $default : null;
	}

	/**
	 * Get the default embedding model ID
	 *
	 * Anthropic 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.0
	 */
	public function get_default_embedding_model() {
		return null;
	}

	/**
	 * Check if a model is compatible with the messages API and supports tool calling
	 *
	 * @param      string $model_id The model ID to check
	 * @return     bool True if the model supports chat with tools
	 * @since     1.0.0
	 */
	protected function is_chat_compatible( $model_id ) {
		// Exclude Claude 2.x and earlier - they don't support tool use
		if ( stripos( $model_id, 'claude-2' ) === 0 || stripos( $model_id, 'claude-instant' ) === 0 ) {
			return false;
		}

		return true;
	}
}
