<?php

namespace Drupal\xb_ai\Plugin\AiFunctionCall;

use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\ai\Attribute\FunctionCall;
use Drupal\ai\Base\FunctionCallBase;
use Drupal\ai\Service\FunctionCalling\ExecutableFunctionCallInterface;
use Drupal\ai\Service\FunctionCalling\FunctionCallInterface;
use Drupal\ai\Utility\ContextDefinitionNormalizer;
use Drupal\ai_agents\PluginInterfaces\AiAgentContextInterface;
use Drupal\experience_builder\Entity\Component;
use Drupal\experience_builder\Exception\ConstraintViolationException;
use Drupal\experience_builder\Plugin\Field\FieldType\ComponentTreeItemListInstantiatorTrait;
use Drupal\xb_ai\XbAiPageBuilderHelper;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Validation\BasicRecursiveValidatorFactory;
use Drupal\experience_builder\Validation\ConstraintPropertyPathTranslatorTrait;
use Drupal\Component\Uuid\UuidInterface;
use Symfony\Component\Yaml\Yaml;

/**
 * Function call plugin to set the component structure generated by AI.
 */
#[FunctionCall(
  id: 'xb_ai:set_component_structure',
  function_name: 'set_component_structure',
  name: 'Set Component Structure',
  description: 'Validates and stores the component structure in the tempstore.',
  group: 'modification_tools',
  context_definitions: [
    'component_structure' => new ContextDefinition(
      data_type: 'string',
      label: new TranslatableMarkup("Component structure in yml format"),
      description: new TranslatableMarkup("The component structure to store in YAML format."),
      required: TRUE,
    ),
  ],
)]
final class SetAIGeneratedComponentStructure extends FunctionCallBase implements ExecutableFunctionCallInterface, AiAgentContextInterface {

  use ComponentTreeItemListInstantiatorTrait;
  use ConstraintPropertyPathTranslatorTrait;

  /**
   * The XB page builder helper service.
   *
   * @var \Drupal\xb_ai\XbAiPageBuilderHelper
   */
  protected XbAiPageBuilderHelper $pageBuilderHelper;

  /**
   * The logger factory.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected LoggerChannelFactoryInterface $loggerFactory;

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected AccountProxyInterface $currentUser;

  /**
   * The validator factory.
   *
   * @var \Drupal\Core\Validation\BasicRecursiveValidatorFactory
   */
  protected BasicRecursiveValidatorFactory $validatorFactory;

  /**
   * The UUID service.
   *
   * @var \Drupal\Component\Uuid\UuidInterface
   */
  protected UuidInterface $uuidService;

  /**
   * Load from dependency injection container.
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): FunctionCallInterface | static {
    $instance = new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      new ContextDefinitionNormalizer(),
    );
    $instance->pageBuilderHelper = $container->get('xb_ai.page_builder_helper');
    $instance->loggerFactory = $container->get('logger.factory');
    $instance->currentUser = $container->get('current_user');
    $instance->validatorFactory = $container->get(BasicRecursiveValidatorFactory::class);
    $instance->uuidService = $container->get('uuid');
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function execute(): void {
    // Make sure that the user has the right permissions.
    if (!$this->currentUser->hasPermission('use experience builder ai')) {
      throw new \Exception('The current user does not have the right permissions to run this tool.');
    }
    try {
      $component_structure = $this->getContextValue('component_structure');
      $component_structure_array = Yaml::parse($component_structure);

      // Validate reference node path.
      if (count($component_structure_array['reference_nodepath']) % 2 !== 0) {
        throw new \Exception(sprintf('The reference_nodepath %s is incomplete and missing elements. Provide the complete nodepath from current layout.', implode(', ', $component_structure_array['reference_nodepath'])));
      }
      \assert($component_structure_array['components'], 'The components key is missing in the component structure.');
      $this->validateComponentStructure($component_structure_array['components']);

      // Once validated, convert this yml to JSON that will be processed by
      // the XB UI.
      $output = $this->pageBuilderHelper->customYamlToArrayMapper($component_structure);
      $this->setOutput(Yaml::dump($output));
    }
    catch (\Exception $e) {
      $this->loggerFactory->get('xb_ai')->error($e->getMessage());
      $this->setOutput(sprintf('Failed to process layout data: %s', $e->getMessage()));
    }
  }

  private function convertToComponentTreeData(array $componentGroups, ?string $parentUuid = NULL, ?string $slotName = NULL, string $pathPrefix = 'components', array &$pathMapping = []): array {
    $componentTreeData = [];
    foreach ($componentGroups as $groupIndex => $componentGroup) {
      foreach ($componentGroup as $componentId => $componentData) {
        $componentUuid = $this->uuidService->generate();

        $componentPath = sprintf('%s.%d.[%s]', $pathPrefix, $groupIndex, $componentId);
        $pathMapping[$componentUuid] = $componentPath;

        // Create a temp version if the component does not exist to allow
        // validation to proceed. The constraints will flag invalid components
        // later.
        $componentVersion = Component::load($componentId)?->getActiveVersion() ?? "temp-version-$componentUuid";

        $componentTreeItem = [
          'uuid' => $componentUuid,
          'component_id' => $componentId,
          'component_version' => $componentVersion,
          'inputs' => $componentData['props'] ?? [],
        ];
        if ($parentUuid !== NULL) {
          $componentTreeItem['parent_uuid'] = $parentUuid;
          $componentTreeItem['slot'] = $slotName;
        }

        $componentTreeData[] = $componentTreeItem;

        // Process slots recursively
        if (isset($componentData['slots']) && is_array($componentData['slots'])) {
          foreach ($componentData['slots'] as $slot => $slotComponentGroups) {
            $slotPath = sprintf('%s.slots.%s', $componentPath, $slot);
            $componentTreeData = array_merge($componentTreeData, $this->convertToComponentTreeData($slotComponentGroups, $componentUuid, $slot, $slotPath, $pathMapping));
          }
        }
      }
    }
    return $componentTreeData;
  }

  private function validateComponentStructure(array $componentGroups): void {
    // Create a mapping of components to their original paths
    $pathMapping = [];

    // Convert YAML structure to XB ComponentTreeItem format
    $componentTreeData = $this->convertToComponentTreeData($componentGroups, NULL, NULL, 'components', $pathMapping);

    $componentTreeItemList = $this->createDanglingComponentTreeItemList();
    $componentTreeItemList->setValue($componentTreeData);
    $violations = $componentTreeItemList->validate();

    if ($violations->count() > 0) {
      throw new ConstraintViolationException(
        $this->translateConstraintPropertyPathsAndRoot(
          $this->buildPathTranslationMap($componentTreeData, $pathMapping),
          $violations,
          ''
        ),
        'Component validation errors'
      );
    }
  }

  private function buildPathTranslationMap(array $componentTreeData, array $pathMapping): array {
    $pathMap = [];

    // Map field-level validation paths from ComponentTreeItemList->validate()
    foreach ($componentTreeData as $index => $component) {
      $uuid = $component['uuid'];
      if (isset($pathMapping[$uuid])) {
        $originalPath = $pathMapping[$uuid];

        // Map component field paths from field-level validation.
        // The actual violation paths are just numeric indices, not the full object path.
        $pathMap["{$index}.component_id"] = $originalPath;
        $pathMap["{$index}.uuid"] = $originalPath;
        $pathMap["{$index}.component_version"] = $originalPath;
        $pathMap["{$index}.parent_uuid"] = $originalPath;

        // For slot validation errors, point to the parent component
        $pathMap["{$index}.slot"] = isset($component['parent_uuid']) ?
          $pathMapping[$component['parent_uuid']] ?? '' :
          $originalPath;

        // Map input validation paths from field-level validation
        $pathMap["{$index}.inputs.{$uuid}."] = $originalPath . '.props.';
      }
    }

    return $pathMap;
  }

}
