<?php

declare(strict_types=1);

namespace Drupal\flowdrop_node_processor\Plugin\FlowDropNodeProcessor;

use Drupal\Core\Template\TwigEnvironment;
use Drupal\flowdrop\Attribute\FlowDropNodeProcessor;
use Drupal\flowdrop\DTO\ParameterBagInterface;
use Drupal\flowdrop\DTO\ValidationResult;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\AbstractFlowDropNodeProcessor;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\DynamicPortTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Executor for Prompt Template nodes.
 *
 * Renders a Twig template with variables drawn from two sources that share the
 * top-level template namespace:
 * - The `variables` bag: a single connectable port carrying a map of values,
 *   spread as top-level vars and also reachable whole as `{{ variables.x }}`.
 * - Named dynamic input ports (via DynamicPortTrait): each declared port
 *   supplies one value referenced as `{{ port_name }}`.
 *
 * Precedence: a named port value wins over a `variables` bag entry of the same
 * key (an explicit wired port is more specific than the bag). An *unconnected*
 * declared port resolves to NULL and does NOT clobber a bag key of that name.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
#[FlowDropNodeProcessor(
  id: "prompt_template",
  label: new \Drupal\Core\StringTranslation\TranslatableMarkup("Prompt Template"),
  description: "Create prompts using templates with variable substitution",
  version: "1.0.0"
)]
class PromptTemplate extends AbstractFlowDropNodeProcessor {

  use DynamicPortTrait;

  /**
   * Constructs a PromptTemplate object.
   *
   * @param array<string, mixed> $configuration
   *   The plugin configuration.
   * @param string $plugin_id
   *   The plugin ID.
   * @param mixed $plugin_definition
   *   The plugin definition.
   * @param \Drupal\Core\Template\TwigEnvironment $twig
   *   The Twig environment.
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    protected readonly TwigEnvironment $twig,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(
    ContainerInterface $container,
    array $configuration,
    $plugin_id,
    $plugin_definition,
  ): static {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get("twig"),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function validateParams(array $params): ValidationResult {
    // Prompt template can accept any inputs or none.
    return ValidationResult::success();
  }

  /**
   * {@inheritdoc}
   */
  public function process(ParameterBagInterface $params): array {
    $template = $params->get("template", "");
    $variables = $params->get("variables", []);

    // Base context from the variables bag (existing behaviour): spread as
    // top-level vars so {{ key }} works, and keep the whole bag reachable as
    // {{ variables.x }}.
    if (is_array($variables)) {
      $context = $variables;
    }
    elseif (is_object($variables)) {
      $context = (array) $variables;
    }
    else {
      $context = [];
    }

    // Merge named dynamic input ports over the bag. Each declared port supplies
    // one value keyed by port name. Named ports win over a same-key bag entry,
    // but an unconnected port (NULL) must not clobber a bag key of that name.
    [$inputDefs] = $this->extractDynamicPortDefs($params);
    // Inputs only: this node has fixed outputs, no dynamic output ports.
    $this->validatePortNames($inputDefs, []);
    $portValues = array_filter(
      $this->collectDynamicInputValues($params),
      static fn ($value) => $value !== NULL,
    );
    $context = array_merge($context, $portValues);

    $context['variables'] = $variables;

    $prompt = $this->twig->createTemplate($template)
      ->render($context);

    return [
      "prompt" => $prompt,
      "template" => $template,
      "variables" => $variables,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "variables" => [
          "type" => "mixed",
          "title" => "Variables",
          "description" => "Default variables for the template",
          "default" => [],
        ],
        "template" => [
          "type" => "string",
          "title" => "Template",
          "description" => "The prompt template with variables in {{variable}} format",
          "default" => "",
          "format" => "template",
        ],
        // Named input ports: each declared port is referenced in the template
        // as {{ port_name }}.
        ...$this->buildDynamicInputsSchema(),
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getOutputSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "prompt" => [
          "type" => "string",
          "description" => "The generated prompt",
        ],
        "template" => [
          "type" => "string",
          "description" => "The original template",
        ],
        "variables" => [
          "type" => "mixed",
          "description" => "The variables used",
        ],
      ],
    ];
  }

}
