<?php

declare(strict_types=1);

namespace Drupal\flowdrop_interrupt\Plugin\FlowDropNodeProcessor;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flowdrop\Attribute\FlowDropNodeProcessor;
use Drupal\flowdrop\DTO\ExecutionContextDTO;
use Drupal\flowdrop\DTO\ParameterBagInterface;
use Drupal\flowdrop\DTO\ValidationResult;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\AbstractFlowDropNodeProcessor;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\ExecutionContextAwareInterface;
use Drupal\flowdrop_interrupt\Entity\FlowDropInterruptInterface;
use Drupal\flowdrop_interrupt\Enum\InterruptType;
use Drupal\flowdrop_interrupt\Exception\InterruptRequiredException;
use Drupal\flowdrop_interrupt\Service\InterruptManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Form input node processor for human-in-loop workflows.
 *
 * Pauses workflow execution and presents a JSON Schema-based form to the user.
 * This is the most flexible input type, allowing complex data collection.
 *
 * Config:
 * - message: The prompt message to display above the form.
 * - schema: JSON Schema defining the form fields.
 * - default_values: Default values for form fields.
 *
 * Outputs:
 * - data: The form data as an object matching the schema.
 * - response_time: Timestamp when the response was received.
 * - user_id: ID of the user who responded (if authenticated).
 *
 * @internal This class is not part of the public API and may change without notice.
 */
#[FlowDropNodeProcessor(
  id: "form_input",
  label: new TranslatableMarkup("Form Input"),
  description: "Pauses workflow for user to fill a JSON Schema form",
  version: "1.0.0"
)]
class FormInputNode extends AbstractFlowDropNodeProcessor implements ExecutionContextAwareInterface {

  /**
   * The interrupt manager service.
   *
   * @var \Drupal\flowdrop_interrupt\Service\InterruptManagerInterface|null
   */
  protected ?InterruptManagerInterface $interruptManager = NULL;

  /**
   * The execution context.
   *
   * @var \Drupal\flowdrop\DTO\ExecutionContextDTO|null
   */
  protected ?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);

    if ($container->has("flowdrop_interrupt.manager")) {
      $manager = $container->get("flowdrop_interrupt.manager");
      if ($manager instanceof InterruptManagerInterface) {
        $instance->interruptManager = $manager;
      }
    }

    return $instance;
  }

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

  /**
   * {@inheritdoc}
   */
  public function validateParams(array $params): ValidationResult {
    if (empty($params["message"])) {
      return ValidationResult::error("message", "Prompt message is required.", "REQUIRED");
    }

    if (empty($params["schema"]) || !is_array($params["schema"])) {
      return ValidationResult::error("schema", "JSON Schema is required.", "REQUIRED");
    }

    // Basic JSON Schema validation.
    $schema = $params["schema"];
    if (!isset($schema["type"])) {
      return ValidationResult::error("schema", "Schema must have a 'type' property.", "INVALID_SCHEMA");
    }

    return ValidationResult::success();
  }

  /**
   * {@inheritdoc}
   */
  public function process(ParameterBagInterface $params): array {
    $message = $params->getString("message", "Please fill out the form");
    $schema = $params->getArray("schema", []);
    $defaultValues = $params->getArray("default_values", []);

    // Check if we have a pending interrupt ID (this is a resume).
    $interruptId = $params->get("__interrupt_id__");

    if ($interruptId !== NULL && $this->interruptManager !== NULL) {
      $interrupt = $this->interruptManager->getInterrupt((string) $interruptId);

      if ($interrupt !== NULL && $interrupt->isResolved()) {
        return $this->buildOutputFromInterrupt($interrupt);
      }

      if ($interrupt !== NULL && $interrupt->isPending()) {
        throw new InterruptRequiredException(
          $interrupt,
          $this->buildExecutionContext(),
          "Waiting for user form submission"
        );
      }

      if ($interrupt !== NULL) {
        return [
          "data" => [],
          "response_time" => $interrupt->getResolvedAt() ?? time(),
          "user_id" => NULL,
          "status" => $interrupt->getStatus()->value,
        ];
      }
    }

    if ($this->interruptManager === NULL) {
      throw new \RuntimeException(
        "InterruptManager service is not available. Ensure flowdrop_interrupt module is enabled."
      );
    }

    // Get context from execution.
    $workflowId = "";
    $nodeId = "form_input";
    $pipelineId = NULL;
    $sessionId = NULL;

    if ($this->executionContext !== NULL) {
      $workflowId = $this->executionContext->getWorkflowId();
      $pipelineId = $this->executionContext->getPipelineId();
      $metadata = $this->executionContext->getMetadata();
      $nodeId = $metadata["node_id"] ?? $nodeId;
      $sessionId = isset($metadata["session_id"]) ? (int) $metadata["session_id"] : NULL;
    }

    $interrupt = $this->interruptManager->createInterrupt(
      InterruptType::Form,
      $workflowId,
      $nodeId,
      $message,
      $schema,
      [],
      $this->buildExecutionContext(),
      !empty($defaultValues) ? $defaultValues : NULL,
      $pipelineId,
      $sessionId,
    );

    throw new InterruptRequiredException(
      $interrupt,
      $this->buildExecutionContext(),
      "Workflow paused for user form input"
    );
  }

  /**
   * Builds the output from a resolved interrupt.
   *
   * @param \Drupal\flowdrop_interrupt\Entity\FlowDropInterruptInterface $interrupt
   *   The resolved interrupt entity.
   *
   * @return array<string, mixed>
   *   The output array.
   */
  protected function buildOutputFromInterrupt(FlowDropInterruptInterface $interrupt): array {
    $responseData = $interrupt->getResponseData();

    // Normalize response to array.
    $data = [];
    if (is_array($responseData)) {
      $data = $responseData;
    }

    return [
      "data" => $data,
      "response_time" => $interrupt->getResolvedAt() ?? time(),
      "user_id" => $interrupt->getResolvedBy(),
      "interrupt_id" => $interrupt->uuid(),
    ];
  }

  /**
   * Builds the execution context for resuming.
   *
   * @return array<string, mixed>
   *   The execution context.
   */
  protected function buildExecutionContext(): array {
    $context = [];

    if ($this->executionContext !== NULL) {
      $context["workflow_id"] = $this->executionContext->getWorkflowId();
      $context["pipeline_id"] = $this->executionContext->getPipelineId();
      $context["metadata"] = $this->executionContext->getMetadata();
    }

    return $context;
  }

  /**
   * {@inheritdoc}
   */
  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "message" => [
          "type" => "string",
          "title" => "Prompt Message",
          "description" => "The message to display above the form",
          "default" => "Please fill out the form",
          "format" => "multiline",
        ],
        "schema" => [
          "type" => "object",
          "title" => "Form Schema",
          "description" => "JSON Schema defining the form fields",
          "format" => "json",
          "default" => [
            "type" => "object",
            "properties" => [
              "name" => [
                "type" => "string",
                "title" => "Name",
              ],
            ],
          ],
        ],
        "default_values" => [
          "type" => "object",
          "title" => "Default Values",
          "description" => "Default values for form fields",
          "format" => "json",
        ],
        "__interrupt_id__" => [
          "type" => "string",
          "description" => "Internal: Interrupt ID for resume operations",
          "connectable" => FALSE,
          "configurable" => FALSE,
        ],
      ],
      "required" => ["message", "schema"],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getOutputSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "data" => [
          "type" => "object",
          "description" => "The form data submitted by the user",
        ],
        "response_time" => [
          "type" => "integer",
          "description" => "Unix timestamp when the user responded",
        ],
        "user_id" => [
          "type" => "integer",
          "description" => "ID of the user who responded (null if anonymous)",
        ],
        "interrupt_id" => [
          "type" => "string",
          "description" => "UUID of the interrupt that was resolved",
        ],
      ],
    ];
  }

}
