<?php

declare(strict_types=1);

namespace Drupal\ai;

use Drupal\ai\Exception\AiRequestErrorException;
use Drupal\ai\OperationType\Chat\ChatInput;
use Drupal\ai\OperationType\Chat\ChatInterface;
use Drupal\ai\OperationType\Chat\ChatMessage;
use Drupal\ai\OperationType\Chat\ChatOutput;
use Drupal\ai\Plugin\AiPlatform\AiPlatformInterface;
use Drupal\ai\Symfony\InputOutputConverter;
use Symfony\AI\Platform\Capability;
use Symfony\AI\Platform\ModelCatalog\ModelCatalogInterface;
use Symfony\AI\Platform\Result\DeferredResult;

/**
 * Proxy wrapping an AiPlatformInterface plugin.
 *
 * Implements AiPlatformInterface for the new Symfony-first API, and
 * AiProviderInterface + ChatInterface as a deprecated BC layer for existing
 * callers.
 */
final class PlatformProxy implements AiPlatformInterface, AiProviderInterface, ChatInterface {

  /**
   * The configuration array.
   *
   * @var array
   *
   * @todo Remove this once ProviderProxy no longer accesses it directly.
   */
  public array $configuration = [];

  /**
   * Tags for tracking.
   *
   * @var string[]
   */
  protected array $tags = [];

  /**
   * Debug data.
   *
   * @var array
   */
  protected array $debugData = [];

  /**
   * Constructs a PlatformProxy.
   *
   * @param \Drupal\ai\Plugin\AiPlatform\AiPlatformInterface $platform
   *   The AI platform plugin instance.
   * @param \Drupal\ai\Symfony\InputOutputConverter $converter
   *   The input/output converter.
   */
  public function __construct(
    protected readonly AiPlatformInterface $platform,
    protected readonly InputOutputConverter $converter,
  ) {}

  /**
   * Returns the wrapped platform plugin instance.
   *
   * @return \Drupal\ai\Plugin\AiPlatform\AiPlatformInterface
   *   The platform plugin.
   */
  public function getPlatform(): AiPlatformInterface {
    return $this->platform;
  }

  /**
   * {@inheritdoc}
   */
  public function invoke(string $model, array|string|object $input, array $options = []): DeferredResult {
    return $this->platform->invoke($model, $input, $options);
  }

  /**
   * {@inheritdoc}
   */
  public function getModelCatalog(): ModelCatalogInterface {
    return $this->platform->getModelCatalog();
  }

  /**
   * {@inheritdoc}
   */
  public function label(): string {
    return $this->platform->label();
  }

  /**
   * {@inheritdoc}
   */
  public function chat(array|string|ChatInput $input, string $model_id, array $tags = []): ChatOutput {
    // Normalize string input to ChatInput.
    if (is_string($input)) {
      $input = new ChatInput([new ChatMessage('user', $input)]);
    }
    elseif (is_array($input)) {
      $input = new ChatInput($input);
    }

    // Validate that the input has meaningful content.
    $messages = $input->getMessages();
    if (empty($messages) || (count($messages) === 1 && empty($messages[0]->getRole()) && empty($messages[0]->getText()))) {
      throw new AiRequestErrorException('The chat input is empty.');
    }

    $messageBag = $this->converter->toMessageBag($input);
    $options = $this->converter->buildOptions($input);

    $result = $this->invoke($model_id, $messageBag, $options);

    if ($input->isStreamedOutput()) {
      return $this->converter->toStreamedChatOutput($result);
    }

    return $this->converter->toChatOutput($result);
  }

  /**
   * {@inheritdoc}
   */
  public function getMaxInputTokens(string $model_id): int {
    // @todo Retrieve from model catalog or platform plugin.
    return 128000;
  }

  /**
   * {@inheritdoc}
   */
  public function getMaxOutputTokens(string $model_id): int {
    // @todo Retrieve from model catalog or platform plugin.
    return 16384;
  }

  /**
   * {@inheritdoc}
   */
  public function getConfiguredModels(?string $operation_type = NULL, array $capabilities = []): array {
    $models = [];
    try {
      $catalog = $this->platform->getModelCatalog();
      foreach ($catalog->getModels() as $model_id => $details) {
        if ($operation_type === 'chat' || $operation_type === NULL) {
          if (in_array(Capability::INPUT_MESSAGES, $details['capabilities'], TRUE)) {
            $models[$model_id] = $model_id;
          }
        }
        if ($operation_type === 'embeddings') {
          if (in_array(Capability::EMBEDDINGS, $details['capabilities'], TRUE)) {
            $models[$model_id] = $model_id;
          }
        }
      }
    }
    catch (\Exception $e) {
      // Catalog may be unavailable.
    }
    return $models;
  }

  /**
   * {@inheritdoc}
   */
  public function isUsable(?string $operation_type = NULL, array $capabilities = []): bool {
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getSupportedOperationTypes(): array {
    return ['chat'];
  }

  /**
   * {@inheritdoc}
   */
  public function getSupportedCapabilities(): array {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function getAvailableConfiguration(string $operation_type, string $model_id): array {
    return $this->platform->getAvailableConfiguration($operation_type, $model_id);
  }

  /**
   * {@inheritdoc}
   */
  public function getDefaultConfigurationValues(string $operation_type, string $model_id): array {
    $configs = $this->getAvailableConfiguration($operation_type, $model_id);
    $defaults = [];
    foreach ($configs as $key => $values) {
      if (isset($values['default']) && !empty($values['required'])) {
        $defaults[$key] = $values['default'];
      }
    }
    return $defaults;
  }

  /**
   * {@inheritdoc}
   */
  public function getInputExample(string $operation_type, string $model_id): mixed {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function getAuthenticationExample(string $operation_type, string $model_id): mixed {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function setAuthentication(mixed $authentication): void {
  }

  /**
   * {@inheritdoc}
   */
  public function setConfiguration(array $configuration): void {
    $this->configuration = $configuration;
  }

  /**
   * {@inheritdoc}
   */
  public function getConfiguration(): array {
    return $this->configuration;
  }

  /**
   * {@inheritdoc}
   */
  public function setTag(string $tag): void {
    $this->tags[] = $tag;
  }

  /**
   * {@inheritdoc}
   */
  public function getTags(): array {
    return $this->tags;
  }

  /**
   * {@inheritdoc}
   */
  public function resetTags(): void {
    $this->tags = [];
  }

  /**
   * {@inheritdoc}
   */
  public function setDebugData(string $key, mixed $value): void {
    $this->debugData[$key] = $value;
  }

  /**
   * {@inheritdoc}
   */
  public function getDebugData(): array {
    return $this->debugData;
  }

  /**
   * {@inheritdoc}
   */
  public function removeTag(string $tag): void {
    $this->tags = array_filter($this->tags, fn($t) => $t !== $tag);
  }

  /**
   * {@inheritdoc}
   *
   * @todo Implement model settings form for platforms. Currently only used by
   *   the model settings page (/admin/config/ai/settings), not by the
   *   AiProviderConfiguration form element which uses
   *   getAvailableConfiguration() instead.
   */
  public function loadModelsForm(array $form, $form_state, string $operation_type, ?string $model_id = NULL): array {
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateModelsForm(array $form, $form_state): void {
  }

  /**
   * {@inheritdoc}
   */
  public function hasPredefinedModels(): bool {
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getSetupData(): array {
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function getTokenizerForModel(string $model_id): string {
    return '';
  }

  /**
   * {@inheritdoc}
   */
  public function getPluginId(): string {
    return $this->platform->getPluginId();
  }

  /**
   * {@inheritdoc}
   */
  public function getPluginDefinition() {
    return $this->platform->getPluginDefinition();
  }

}
