<?php

namespace Drupal\canvas_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_agents\PluginInterfaces\AiAgentContextInterface;
use Drupal\canvas_ai\AiResponseValidator;
use Drupal\canvas_ai\CanvasAiPageBuilderHelper;
use Drupal\canvas_ai\CanvasAiPermissions;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Symfony\Component\Yaml\Yaml;

/**
 * Function call plugin to set the component structure generated by AI.
 */
#[FunctionCall(
  id: 'canvas_ai:set_component_structure',
  function_name: 'set_component_structure',
  name: 'Set Component Structure',
  description: 'This is the tool that should be used to apply the component structure generated to the current page. The input should be a YAML string representing the component structure with operations. Components will not be added to the page unless this tool is called. It may also return errors if the structure is invalid.',
  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, BuilderResponseFunctionCallInterface {

  /**
   * The Canvas page builder helper service.
   *
   * @var \Drupal\canvas_ai\CanvasAiPageBuilderHelper
   */
  protected CanvasAiPageBuilderHelper $pageBuilderHelper;

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

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

  /**
   * The response validator service.
   *
   * @var \Drupal\canvas_ai\AiResponseValidator
   */
  protected AiResponseValidator $responseValidator;

  /**
   * 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,
      $container->get('ai.context_definition_normalizer'),
    );
    $instance->pageBuilderHelper = $container->get('canvas_ai.page_builder_helper');
    $instance->loggerFactory = $container->get('logger.factory');
    $instance->currentUser = $container->get('current_user');
    $instance->responseValidator = $container->get('canvas_ai.response_validator');
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function execute(): void {
    // Make sure that the user has the right permissions.
    if (!$this->currentUser->hasPermission(CanvasAiPermissions::USE_CANVAS_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);
      if (empty($component_structure_array['operations'])) {
        throw new \Exception('The operations key is missing in the component structure.');
      }
      $allErrors = [];

      foreach ($component_structure_array['operations'] as $index => $operation) {
        $allErrors = array_merge($allErrors, $this->validatePlacementParams($operation, $index));
        $this->responseValidator->validateComponentStructure($operation['components']);
      }

      if (!empty($allErrors)) {
        throw new \Exception(Yaml::dump($allErrors));
      }

      // Once validated, convert this yml to JSON that will be processed by
      // the Canvas UI.
      $custom_yaml = $this->pageBuilderHelper->customYamlToArrayMapper($component_structure);
      \assert(\array_keys($custom_yaml) === ['operations']);
      $this->setStructuredOutput($custom_yaml);
      $this->setOutput('Component structure processed successfully.');
    }
    catch (\Exception $e) {
      $this->loggerFactory->get('canvas_ai')->error($e->getMessage());
      $this->setOutput(\sprintf('Failed to process layout data: %s', $e->getMessage()));
    }
  }

  private function validatePlacementParams(array $operation, int $index): array {
    $errors = [];
    $errorKey = 'Operation ' . $index;

    if (!isset($operation['placement']) || !\in_array($operation['placement'], ['above', 'below', 'inside'], TRUE)) {
      $errors[$errorKey][] = 'The placement key is missing or invalid in the operation.';
      return $errors;
    }

    $placement = $operation['placement'];
    // If placement is 'above' or 'below', `reference_uuid` must be provided.
    if (\in_array($placement, ['above', 'below'], TRUE) && empty($operation['reference_uuid'])) {
      $errors[$errorKey][] = 'The reference_uuid must be provided for above/below placement.';
    }

    // If placement is 'inside', `reference_uuid` is not needed.
    if ($placement === 'inside') {
      if (!empty($operation['reference_uuid'])) {
        $errors[$errorKey][] = 'The reference_uuid is not required for inside placement.';
      }
      // If placement is 'inside', the target must not contain child components.
      if ($this->pageBuilderHelper->hasChildComponents($operation['target'])) {
        $errors[$errorKey][] = 'The target ' . $operation['target'] . ' has "inside" placement specified, but it contains child components. Select any child component in the target and use "above" or "below" placement instead.';
      }
    }

    // Operation must contain components.
    if (empty($operation['components'])) {
      $errors[$errorKey][] = 'The operation must contain components.';
    }

    return $errors;
  }

}
