<?php

declare(strict_types=1);

namespace Drupal\flowdrop_node_processor\Plugin\FlowDropNodeProcessor;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flowdrop\Attribute\FlowDropNodeProcessor;
use Drupal\flowdrop\DTO\ParameterBagInterface;
use Drupal\flowdrop\DTO\ValidationResult;
use Drupal\flowdrop\Exception\WorkflowStopException;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\AbstractFlowDropNodeProcessor;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\TerminalNodeProcessorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Terminal node that ends the workflow run.
 *
 * Reaching this node stops the run cleanly (as COMPLETED), abandoning any
 * nodes downstream of it. It is the exit-boundary dual of a trigger: where a
 * trigger begins a run, a Stop node ends it. Unlike a trigger it is NOT a start
 * node — it is reached via the normal flow, so it keeps its trigger input.
 *
 * It signals the stop by throwing WorkflowStopException from process(); the
 * orchestrators catch that and complete the run.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
#[FlowDropNodeProcessor(
  id: "stop",
  label: new TranslatableMarkup("Stop"),
  description: "End the workflow run here, abandoning any downstream nodes.",
  version: "1.0.0"
)]
class Stop extends AbstractFlowDropNodeProcessor implements TerminalNodeProcessorInterface {

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

  /**
   * {@inheritdoc}
   */
  public function validateParams(array $params): ValidationResult {
    return ValidationResult::success();
  }

  /**
   * {@inheritdoc}
   *
   * Always throws: a Stop node never returns a result — it ends the run.
   */
  public function process(ParameterBagInterface $params): array {
    $reason = $params->get("reason");
    $reason = is_string($reason) && $reason !== "" ? $reason : NULL;

    // The optional wired-in final value becomes the run's designated result.
    // Normalize to an array so the exception's typed payload holds it.
    $result = $params->get("result");
    $payload = match (TRUE) {
      $result === NULL => [],
      is_array($result) => $result,
      default => ["value" => $result],
    };

    throw new WorkflowStopException(
      $payload,
      $reason,
      $reason ?? "Workflow stopped by a terminal node",
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "result" => [
          "type" => "object",
          "title" => "Result",
          "description" => "Optional final value to record as the run's result",
          "required" => FALSE,
        ],
        "reason" => [
          "type" => "string",
          "title" => "Reason",
          "description" => "Optional human-readable note for why the run stops",
          "default" => "",
        ],
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getOutputSchema(): array {
    // A terminal node produces no downstream output.
    return [
      "type" => "object",
      "properties" => [],
    ];
  }

}
