<?php

declare(strict_types=1);

namespace Drupal\flowdrop_stategraph\Plugin\FlowDropNodeProcessor;

use Drupal\flowdrop\Attribute\FlowDropNodeProcessor;
use Drupal\flowdrop\DTO\ParameterBagInterface;
use Drupal\flowdrop\DTO\ValidationError;
use Drupal\flowdrop\DTO\ValidationResult;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\AbstractFlowDropNodeProcessor;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\ExecutionContextAwareInterface;
use Drupal\flowdrop\DTO\ExecutionContextDTO;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flowdrop_stategraph\ApprovalGate\ApprovalGateHandler;
use Drupal\flowdrop_stategraph\ApprovalGate\ApprovalStatus;
use Drupal\flowdrop_stategraph\DTO\Checkpoint;
use Drupal\flowdrop_stategraph\DTO\GraphState;
use Drupal\flowdrop_stategraph\Exception\ApprovalRequiredException;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Approval Gate node processor.
 *
 * Pauses execution and requests human approval before continuing.
 * When this node is reached, it creates an approval request and
 * throws ApprovalRequiredException to pause the pipeline.
 *
 * The workflow can be resumed after approval is granted via the
 * ApprovalGateHandler.
 *
 * Config:
 * - message: The approval request message.
 * - timeout_seconds: Optional timeout for auto-rejection.
 *
 * Outputs:
 * - approved: Boolean indicating if approved (on resume).
 * - approval_notes: Notes from the approver.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
#[FlowDropNodeProcessor(
  id: "approval_gate",
  label: new TranslatableMarkup("Approval Gate"),
  description: "Pauses execution for human approval",
  version: "1.0.0"
)]
class ApprovalGateNode extends AbstractFlowDropNodeProcessor implements ExecutionContextAwareInterface {

  /**
   * The approval gate handler.
   *
   * @var \Drupal\flowdrop_stategraph\ApprovalGate\ApprovalGateHandler|null
   */
  private ?ApprovalGateHandler $approvalHandler = NULL;

  /**
   * The execution context.
   *
   * @var \Drupal\flowdrop\DTO\ExecutionContextDTO|null
   */
  private ?ExecutionContextDTO $executionContext = NULL;

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

    // Inject approval handler if available.
    if ($container->has("flowdrop_stategraph.approval_gate_handler")) {
      $handler = $container->get("flowdrop_stategraph.approval_gate_handler");
      if ($handler instanceof ApprovalGateHandler) {
        $instance->approvalHandler = $handler;
      }
    }

    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function setExecutionContext(ExecutionContextDTO $context): void {
    $this->executionContext = $context;
  }

  /**
   * {@inheritdoc}
   */
  public function validateParams(array $params): ValidationResult {
    // Approval gate requires StateGraph execution context.
    if (!isset($params["__state__"])) {
      return ValidationResult::failure([
        new ValidationError('__state__', "Approval gate requires StateGraph execution context. " .
          "Use the StateGraph orchestrator to enable approval gates."),
      ]);
    }

    return ValidationResult::success();
  }

  /**
   * {@inheritdoc}
   */
  public function process(ParameterBagInterface $params): array {
    $message = $params->get("message", "Approval required to continue");
    $requestId = $params->get("_approval_request_id");

    // Get state from params.
    $stateData = $params->get("__state__", []);
    $state = GraphState::fromArray($stateData);

    // Get node ID from context or generate one.
    $nodeId = "approval_gate";
    if ($this->executionContext !== NULL) {
      $metadata = $this->executionContext->getMetadata();
      $nodeId = $metadata["node_id"] ?? $nodeId;
    }

    // If we have a request ID, check its status (this is a resume).
    if ($requestId !== NULL && $this->approvalHandler !== NULL) {
      $status = $this->approvalHandler->getStatus($requestId);

      if ($status === ApprovalStatus::Approved) {
        $request = $this->approvalHandler->getRequest($requestId);
        return [
          "approved" => TRUE,
          "approval_notes" => $request["notes"] ?? "",
          "approved_by" => $request["resolvedBy"],
          "approved_at" => $request["resolvedAt"],
        ];
      }

      if ($status === ApprovalStatus::Rejected) {
        $request = $this->approvalHandler->getRequest($requestId);
        return [
          "approved" => FALSE,
          "rejection_reason" => $request["notes"] ?? "",
          "rejected_by" => $request["resolvedBy"],
          "rejected_at" => $request["resolvedAt"],
        ];
      }

      // Still pending - throw exception again.
      $checkpoint = $this->approvalHandler->getCheckpoint($requestId);
      if ($checkpoint !== NULL) {
        throw new ApprovalRequiredException(
          nodeId: $nodeId,
          checkpoint: $checkpoint,
          message: $message
        );
      }
    }

    // Create new checkpoint and approval request.
    $checkpoint = Checkpoint::create($state, $nodeId);

    // Create approval request if handler available.
    if ($this->approvalHandler !== NULL) {
      $requestId = $this->approvalHandler->createRequest(
        $nodeId,
        $checkpoint,
        [
          "message" => $message,
          "workflow_id" => $this->executionContext?->getWorkflowId() ?? "",
          "pipeline_id" => $this->executionContext?->getPipelineId() ?? "",
        ]
      );
    }

    // Throw exception to pause execution.
    throw new ApprovalRequiredException(
      nodeId: $nodeId,
      checkpoint: $checkpoint,
      message: $message
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "message" => [
          "type" => "string",
          "description" => "The approval request message",
          "default" => "Approval required to continue",
        ],
        "timeout_seconds" => [
          "type" => "integer",
          "description" => "Optional timeout for auto-rejection (0 = no timeout)",
          "default" => 0,
        ],
        "_approval_request_id" => [
          "type" => "string",
          "description" => "Internal: Request ID for resume operations",
        ],
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getOutputSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "approved" => [
          "type" => "boolean",
          "description" => "Whether the request was approved",
        ],
        "approval_notes" => [
          "type" => "string",
          "description" => "Notes from the approver",
        ],
        "rejection_reason" => [
          "type" => "string",
          "description" => "Reason for rejection (if rejected)",
        ],
        "approved_by" => [
          "type" => "integer",
          "description" => "User ID who approved/rejected",
        ],
        "approved_at" => [
          "type" => "integer",
          "description" => "Timestamp when approved/rejected",
        ],
      ],
    ];
  }

}
