<?php

namespace Antimanual;

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

/**
 * OpenCode Zen AI Provider.
 *
 * OpenCode Zen provides a curated list of tested and verified models
 * for coding agents. It exposes an OpenAI-compatible Chat Completions
 * endpoint. Embeddings fall back to OpenAI/Gemini at the AIProvider
 * factory level.
 *
 * @package Antimanual
 */
class OpenCodeZen {

	/**
	 * Base URL for the OpenCode Zen API.
	 *
	 * @var string
	 */
	private static $base_url = 'https://opencode.ai/zen/v1';

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

	/**
	 * Chat model name.
	 *
	 * @var string
	 */
	private $model = 'qwen3.6-plus';

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

	/**
	 * Create a conversation placeholder.
	 *
	 * OpenCode Zen 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 'opencodezen-conversation-' . wp_generate_uuid4();
	}

	/**
	 * Get an AI reply for the given input.
	 *
	 * @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.
	 * @return array Reply array on success, error array on failure.
	 */
	public function get_reply( $input, $conversation_id = '', $instructions = '', $max_output_tokens = 0 ) {
		$messages = $this->build_chat_messages( $input, (string) $instructions );

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

		$payload = [
			'model'    => $this->model,
			'messages' => $messages,
		];

		if ( $max_output_tokens > 0 ) {
			$payload['max_tokens'] = min( $max_output_tokens, 32000 );
		}

		$response = $this->request( '/chat/completions', $payload );

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

		$reply = $response['choices'][0]['message']['content'] ?? '';

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

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

	/**
	 * Get messages from a conversation (no-op — local storage only).
	 *
	 * @param string $conversation_id The conversation ID.
	 * @return array
	 */
	public function get_messages( string $conversation_id ): array {
		return [];
	}

	/**
	 * Convert plugin-style structured input into OpenAI-compatible messages.
	 *
	 * @param array  $input        Raw input array.
	 * @param string $instructions Optional system instructions to prepend.
	 * @return array<int, array{role:string,content:string}>
	 */
	private function build_chat_messages( array $input, string $instructions ): array {
		$messages = [];

		if ( '' !== trim( $instructions ) ) {
			$messages[] = [
				'role'    => 'system',
				'content' => $instructions,
			];
		}

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

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

			$mapped_role = in_array( $role, [ 'user', 'assistant', 'system' ], true ) ? $role : 'user';

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

		return $messages;
	}

	/**
	 * Extract a plain-text payload from a message content value.
	 *
	 * @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 OpenCode Zen API.
	 *
	 * @param string $endpoint Path beginning with '/'.
	 * @param array  $payload  Request body.
	 * @return array Decoded response or error array.
	 */
	private function request( string $endpoint, array $payload ): array {
		if ( '' === $this->api_key ) {
			return [ 'error' => __( 'OpenCode Zen 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::$base_url . $endpoint,
			[
				'method'  => 'POST',
				'headers' => [
					'Authorization' => 'Bearer ' . $this->api_key,
					'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 OpenCode Zen.', 'antimanual' ),
		];
	}
}
