<?php

namespace Antimanual;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Anthropic (Claude) AI Provider.
 *
 * Handles chat completions via the Anthropic Messages API. Anthropic does not
 * provide an embeddings or image-generation endpoint; embeddings fall back to
 * OpenAI/Gemini at the AIProvider factory level.
 *
 * @package Antimanual
 */
class Anthropic {

	/**
	 * Anthropic Messages API endpoint.
	 *
	 * @var string
	 */
	private static $endpoint = 'https://api.anthropic.com/v1/messages';

	/**
	 * Anthropic API version header value.
	 *
	 * @var string
	 */
	private static $api_version = '2023-06-01';

	/**
	 * API key for authentication.
	 *
	 * @var string
	 */
	private $api_key = '';

	/**
	 * Chat model name.
	 *
	 * @var string
	 */
	private $model = 'claude-sonnet-4-6';

	/**
	 * Constructor. Initialises API key and model from saved options.
	 */
	public function __construct() {
		$this->api_key = (string) atml_option( 'anthropic_api_key' );
		$saved_model   = trim( (string) atml_option( 'anthropic_response_model' ) );
		if ( '' !== $saved_model ) {
			$this->model = $saved_model;
		}
	}

	/**
	 * Create a conversation placeholder.
	 *
	 * Anthropic doesn't store conversations server-side; callers must pass
	 * full message history. Returns a marker for callsite parity.
	 *
	 * @param array $items Initial conversation items (unused).
	 * @return string Marker conversation ID.
	 */
	public function create_conversation( array $items ) {
		return 'anthropic-conversation-' . wp_generate_uuid4();
	}

	/**
	 * Get an AI reply for the given input.
	 *
	 * Accepts the same input shape used elsewhere in the plugin: an array of
	 * `{role, content}` messages where content is either a string or an array
	 * of parts (input_text, input_file). System messages are extracted and
	 * sent via Anthropic's top-level `system` parameter.
	 *
	 * @param array  $input             Array of message objects (role, content).
	 * @param string $conversation_id   Optional marker; passed back unchanged.
	 * @param string $instructions      Optional system instructions.
	 * @param int    $max_output_tokens Optional max output tokens (defaults to 4096).
	 * @return array Reply array on success, error array on failure.
	 */
	public function get_reply( $input, $conversation_id = '', $instructions = '', $max_output_tokens = 0 ) {
		$messages = [];
		$system   = (string) $instructions;

		foreach ( (array) $input as $message ) {
			$role    = $message['role'] ?? 'user';
			$content = $message['content'] ?? '';
			$text    = $this->extract_text_content( $content );

			if ( '' === $text ) {
				continue;
			}

			if ( 'system' === $role ) {
				$system = '' === $system ? $text : $system . "\n" . $text;
				continue;
			}

			$mapped_role = ( 'assistant' === $role ) ? 'assistant' : 'user';

			$messages[] = [
				'role'    => $mapped_role,
				'content' => $text,
			];
		}

		if ( empty( $messages ) ) {
			return [
				'error' => __( 'No user message to send to Anthropic.', 'antimanual' ),
			];
		}

		$payload = [
			'model'      => $this->model,
			'max_tokens' => $max_output_tokens > 0 ? min( $max_output_tokens, 16000 ) : 4096,
			'messages'   => $messages,
		];

		if ( '' !== trim( $system ) ) {
			$payload['system'] = $system;
		}

		$response = $this->request( $payload );

		if ( isset( $response['error'] ) ) {
			$error_message = is_array( $response['error'] )
				? ( $response['error']['message'] ?? __( 'Failed to get response from Anthropic.', 'antimanual' ) )
				: (string) $response['error'];
			return [ 'error' => $error_message ];
		}

		$reply = '';
		foreach ( (array) ( $response['content'] ?? [] ) as $part ) {
			if ( is_array( $part ) && isset( $part['type'] ) && 'text' === $part['type'] && isset( $part['text'] ) ) {
				$reply .= ( '' === $reply ? '' : "\n\n" ) . (string) $part['text'];
			}
		}

		if ( '' === trim( $reply ) ) {
			return [
				'error' => __( 'Failed to get valid response from Anthropic.', 'antimanual' ),
			];
		}

		return [
			'reply'           => trim( $reply ),
			'conversation_id' => $conversation_id,
		];
	}

	/**
	 * Get messages from a conversation.
	 *
	 * Anthropic doesn't store conversations server-side; messages live in WP.
	 *
	 * @param string $conversation_id The conversation ID.
	 * @return array Empty array (use Conversation::list_messages instead).
	 */
	public function get_messages( string $conversation_id ): array {
		return [];
	}

	/**
	 * Extract a plain-text payload from a message content value.
	 *
	 * Supports both simple string content and the structured parts format
	 * used elsewhere in the plugin (text, input_text, input_file).
	 *
	 * @param mixed $content The message content (string or array of parts).
	 * @return string
	 */
	private function extract_text_content( $content ): string {
		if ( is_string( $content ) ) {
			return $content;
		}

		if ( ! is_array( $content ) ) {
			return '';
		}

		$parts = [];
		foreach ( $content as $part ) {
			if ( ! is_array( $part ) ) {
				continue;
			}

			if ( isset( $part['text'] ) && is_string( $part['text'] ) ) {
				$parts[] = $part['text'];
				continue;
			}

			if ( isset( $part['type'] ) && 'input_text' === $part['type'] && isset( $part['text'] ) ) {
				$parts[] = (string) $part['text'];
			}
		}

		return trim( implode( "\n", $parts ) );
	}

	/**
	 * Make an HTTP request to the Anthropic Messages API.
	 *
	 * @param array $payload Request body.
	 * @return array Decoded response or error array.
	 */
	private function request( array $payload ): array {
		if ( '' === $this->api_key ) {
			return [ 'error' => __( 'Anthropic API key is not configured.', 'antimanual' ) ];
		}

		$json = wp_json_encode( $payload, JSON_UNESCAPED_UNICODE, 2048 );

		if ( false === $json ) {
			return [
				'error' => sprintf(
					/* translators: %s: JSON encoding error message */
					__( 'JSON encoding failed: %s', 'antimanual' ),
					json_last_error_msg()
				),
			];
		}

		$response = wp_remote_request(
			self::$endpoint,
			[
				'method'  => 'POST',
				'headers' => [
					'x-api-key'         => $this->api_key,
					'anthropic-version' => self::$api_version,
					'content-type'      => 'application/json',
				],
				'body'    => $json,
				'timeout' => 360,
			]
		);

		if ( is_wp_error( $response ) ) {
			return [ 'error' => $response->get_error_message() ];
		}

		$decoded = json_decode( wp_remote_retrieve_body( $response ), true );

		return is_array( $decoded ) ? $decoded : [
			'error' => __( 'Invalid response from Anthropic.', 'antimanual' ),
		];
	}
}
