<?php

namespace Drupal\ai_automators\PluginBaseClasses;

use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\ai\AiProviderPluginManager;
use Drupal\ai\Service\PromptJsonDecoder\PromptJsonDecoderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * This is a base class that can be used for LLMs simple address rules.
 */
class Address extends RuleBase {

  const ADDRESSING_COMPONENT_FIELD_OVERRIDE = '\CommerceGuys\Addressing\AddressFormat\FieldOverride';
  const ADDRESSING_COMPONENT_ADDRESS_FIELD = '\CommerceGuys\Addressing\AddressFormat\AddressField';

  /**
   * The module handler.
   */
  protected ModuleHandlerInterface $moduleHandler;

  /**
   * The constructor.
   *
   * @param array<mixed> $configuration
   *   A configuration array for the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\ai\AiProviderPluginManager $provider
   *   The AI provider plugin manager.
   * @param \Drupal\ai\Service\PromptJsonDecoder\PromptJsonDecoderInterface $promptJsonDecoder
   *   The prompt JSON decoder.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
   *   The module handler.
   */
  public function __construct(
    $configuration,
    $plugin_id,
    $plugin_definition,
    AiProviderPluginManager $provider,
    PromptJsonDecoderInterface $promptJsonDecoder,
    ModuleHandlerInterface $moduleHandler,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $provider, $promptJsonDecoder);
    $this->moduleHandler = $moduleHandler;
  }

  /**
   * Load from dependency injection container.
   *
   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
   *   The container.
   * @param array<string,mixed> $configuration
   *   The plugin configuration.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('ai.provider'),
      $container->get('ai.prompt_json_decode'),
      $container->get('module_handler')
    );
  }

  /**
   * {@inheritDoc}
   */
  public function helpText() {
    return "This can help find address in text.";
  }

  /**
   * {@inheritDoc}
   */
  public function placeholderText() {
    return "Based on the context text return all addresses listed.\n\nContext:\n{{ context }}";
  }

  /**
   * {@inheritDoc}
   */
  public function ruleIsAllowed(ContentEntityInterface $entity, FieldDefinitionInterface $fieldDefinition): bool {
    if ($this->checkAddressClasses() && $this->addressModuleEnabled()) {
      return parent::ruleIsAllowed($entity, $fieldDefinition);
    }
    return FALSE;
  }

  /**
   * {@inheritDoc}
   */
  public function generate(ContentEntityInterface $entity, FieldDefinitionInterface $fieldDefinition, array $automatorConfig) {

    if (!$this->checkAddressClasses()) {
      return [];
    }
    if (!$this->addressModuleEnabled()) {
      return [];
    }
    // Generate the real prompt if needed.
    $prompts = parent::generate($entity, $fieldDefinition, $automatorConfig);
    $this->setStructuredOutputSchema($entity, $fieldDefinition, $automatorConfig);

    foreach ($prompts as $key => $prompt) {
      $prompt .= "\n\nDo not make up any data. Only transfer data present in the context.";
      $prompts[$key] = $prompt;
    }
    $total = [];
    $instance = $this->prepareLlmInstance('chat', $automatorConfig);
    foreach ($prompts as $prompt) {
      // Create new messages.
      $values = $this->runChatMessage($prompt, $automatorConfig, $instance, $entity);
      if (!empty($values)) {
        $total = array_merge_recursive($total, $values);
      }
    }
    return $total;
  }

  /**
   * {@inheritDoc}
   */
  public function verifyValue(ContentEntityInterface $entity, $value, FieldDefinitionInterface $fieldDefinition, array $automatorConfig) {
    // Should be an array.
    if (!is_array($value)) {
      return FALSE;
    }
    // Check if the address component classes are available.
    if (!$this->checkAddressClasses()) {
      return FALSE;
    }
    // Country code is always mandatory.
    if (!isset($value['country_code'])) {
      return FALSE;
    }

    $addressFieldOverrideClass = self::ADDRESSING_COMPONENT_FIELD_OVERRIDE;

    // Get all possible fields and fields overrides to verify that required
    // fields are present.
    $fields = $this->getAllAddressFields();
    $fieldOverrides = $this->getFieldOverrides($fieldDefinition);
    foreach ($fields as $field) {
      if (isset($fieldOverrides[$field]) &&
        $fieldOverrides[$field] == $addressFieldOverrideClass::REQUIRED) {
        $property_name = $this->getPropertyName($field);
        if (!empty($property_name) && empty($value[$property_name])) {
          return FALSE;
        }
      }
    }

    // Otherwise it is ok.
    return TRUE;
  }

  /**
   * Local implementation of AddressItem::getFieldOverrides().
   *
   * @param \Drupal\Core\Field\FieldDefinitionInterface $fieldDefinition
   *   The target field definition.
   *
   * @return array<mixed>|false
   *   Array of field overrides or FALSE if there are missing classes
   */
  protected function getFieldOverrides(FieldDefinitionInterface $fieldDefinition): array|FALSE {

    if (!$this->checkAddressClasses()) {
      return FALSE;
    }
    $addressFieldOverrideClass = self::ADDRESSING_COMPONENT_FIELD_OVERRIDE;
    $addressFormatClass = self::ADDRESSING_COMPONENT_ADDRESS_FIELD;

    $field_overrides = [];

    if ($fields = $fieldDefinition->getSetting('fields')) {
      $unused_fields = array_diff($addressFormatClass::getAll(), $fields);
      foreach ($unused_fields as $field) {
        $field_overrides[$field] = $addressFieldOverrideClass::HIDDEN;
      }
    }
    else {
      if ($overrides = $fieldDefinition->getSetting('field_overrides')) {
        foreach ($overrides as $field => $data) {
          $field_overrides[$field] = $data['override'];
        }
      }
    }
    return $field_overrides;
  }

  /**
   * Get all possible address fields.
   *
   * @return array<string>
   *   Array of all possible address fields
   *
   * @throws \ReflectionException
   */
  protected function getAllAddressFields(): array {
    if (!$this->checkAddressClasses()) {
      return [];
    }
    $addressFieldClass = self::ADDRESSING_COMPONENT_ADDRESS_FIELD;
    return $addressFieldClass::getAll();
  }

  /**
   * Get generic field labels from address module.
   *
   * @return array<string, string>
   *   Array of generic field labels keyed by field name.
   */
  protected function getGenericFieldLabels(): array {
    if (!$this->addressModuleEnabled()) {
      return [];
    }
    $labelHelperClass = '\Drupal\address\LabelHelper';
    return $labelHelperClass::getGenericFieldLabels();
  }

  /**
   * Get property name for the given field.
   *
   * @param string $field
   *   Address field name.
   *
   * @return string
   *   Property name for the given field. Empty string if FieldHelper class
   *   is not available.
   */
  protected function getPropertyName(string $field): string {
    if (!$this->addressModuleEnabled()) {
      return '';
    }
    $fieldHelperClass = '\Drupal\address\FieldHelper';
    return $fieldHelperClass::getPropertyName($field);
  }

  /**
   * Checks that the address module is enabled.
   *
   * @return bool
   *   True if the address module is enabled false otherwise
   */
  protected function addressModuleEnabled() {
    return $this->moduleHandler->moduleExists('address');
  }

  /**
   * Check if Address component classes are available.
   *
   * Do not "use" directly classes as we are not sure that address field
   * form address module is enabled even if this plugin definition targets
   * only "address" field type.
   *
   * @return bool
   *   True if the required classes are available false otherwise
   */
  protected function checkAddressClasses() {
    return class_exists(self::ADDRESSING_COMPONENT_FIELD_OVERRIDE) &&
      class_exists(self::ADDRESSING_COMPONENT_ADDRESS_FIELD);
  }

  /**
   * {@inheritdoc}
   */
  protected function getStructuredOutputValueSchema(ContentEntityInterface $entity, FieldDefinitionInterface $fieldDefinition, array $automatorConfig): array {
    if (!$this->checkAddressClasses() || !$this->addressModuleEnabled()) {
      return parent::getStructuredOutputValueSchema($entity, $fieldDefinition, $automatorConfig);
    }

    $addressFieldOverrideClass = self::ADDRESSING_COMPONENT_FIELD_OVERRIDE;
    $fields = $this->getAllAddressFields();
    $labels = $this->getGenericFieldLabels();
    $fieldOverrides = $this->getFieldOverrides($fieldDefinition);

    $properties = [
      'country_code' => [
        'type' => 'string',
        'pattern' => '^[A-Z]{2}$',
      ],
    ];

    foreach ($fields as $field) {
      if (!isset($labels[$field])) {
        continue;
      }
      if (isset($fieldOverrides[$field]) && $fieldOverrides[$field] == $addressFieldOverrideClass::HIDDEN) {
        continue;
      }
      $propertyName = $this->getPropertyName($field);
      if ($propertyName === '') {
        continue;
      }
      $properties[$propertyName] = [
        'type' => 'string',
      ];
    }

    return [
      'type' => 'object',
      'additionalProperties' => FALSE,
      'required' => array_keys($properties),
      'properties' => $properties,
    ];
  }

}
