<?php

declare(strict_types=1);

namespace Drupal\flowdrop_interrupt\Entity;

use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\Attribute\ContentEntityType;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flowdrop_interrupt\Access\FlowDropInterruptAccessControlHandler;
use Drupal\flowdrop_interrupt\Enum\InterruptStatus;
use Drupal\flowdrop_interrupt\Enum\InterruptType;
use Drupal\user\EntityOwnerTrait;
use Drupal\views\EntityViewsData;

/**
 * Defines the FlowDrop Interrupt entity.
 *
 * Stores interrupt requests for human-in-loop workflows. When a workflow
 * encounters an interrupt node, it creates an interrupt entity and pauses
 * execution until the user provides a response.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
#[ContentEntityType(
  id: 'flowdrop_interrupt',
  label: new TranslatableMarkup('FlowDrop Interrupt'),
  label_collection: new TranslatableMarkup('FlowDrop Interrupts'),
  label_singular: new TranslatableMarkup('interrupt'),
  label_plural: new TranslatableMarkup('interrupts'),
  entity_keys: [
    'id' => 'id',
    'uuid' => 'uuid',
    'owner' => 'uid',
  ],
  handlers: [
    'storage' => 'Drupal\Core\Entity\Sql\SqlContentEntityStorage',
    'access' => FlowDropInterruptAccessControlHandler::class,
    'views_data' => EntityViewsData::class,
  ],
  base_table: 'flowdrop_interrupt',
  admin_permission: 'administer flowdrop interrupts',
  label_count: [
    'singular' => '@count interrupt',
    'plural' => '@count interrupts',
  ],
)]
class FlowDropInterrupt extends ContentEntityBase implements FlowDropInterruptInterface {

  use EntityChangedTrait;
  use EntityOwnerTrait;

  /**
   * {@inheritdoc}
   */
  public function getWorkflowId(): string {
    $value = $this->get("workflow_id")->value;
    return $value !== NULL ? (string) $value : "";
  }

  /**
   * {@inheritdoc}
   */
  public function setWorkflowId(string $workflow_id): static {
    $this->set("workflow_id", $workflow_id);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getPipelineId(): ?string {
    $value = $this->get("pipeline_id")->value;
    return $value !== NULL && $value !== "" ? (string) $value : NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function setPipelineId(?string $pipeline_id): static {
    $this->set("pipeline_id", $pipeline_id);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getSessionId(): ?int {
    $value = $this->get("session_id")->target_id;
    return $value !== NULL ? (int) $value : NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function setSessionId(?int $session_id): static {
    if ($session_id === NULL) {
      $this->set("session_id", NULL);
    }
    else {
      $this->set("session_id", ["target_id" => $session_id]);
    }
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getNodeId(): string {
    $value = $this->get("node_id")->value;
    return $value !== NULL ? (string) $value : "";
  }

  /**
   * {@inheritdoc}
   */
  public function setNodeId(string $node_id): static {
    $this->set("node_id", $node_id);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getType(): InterruptType {
    $value = $this->get("type")->value;
    return InterruptType::tryFrom((string) $value) ?? InterruptType::Confirmation;
  }

  /**
   * {@inheritdoc}
   */
  public function setType(InterruptType $type): static {
    $this->set("type", $type->value);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getStatus(): InterruptStatus {
    $value = $this->get("status")->value;
    return InterruptStatus::tryFrom((string) $value) ?? InterruptStatus::Pending;
  }

  /**
   * {@inheritdoc}
   */
  public function setStatus(InterruptStatus $status): static {
    $this->set("status", $status->value);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getMessage(): string {
    $value = $this->get("message")->value;
    return $value !== NULL ? (string) $value : "";
  }

  /**
   * {@inheritdoc}
   */
  public function setMessage(string $message): static {
    $this->set("message", $message);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getSchema(): array {
    $data = $this->get("schema")->value;
    if ($data === NULL || $data === "") {
      return [];
    }
    $decoded = Json::decode($data);
    return is_array($decoded) ? $decoded : [];
  }

  /**
   * {@inheritdoc}
   */
  public function setSchema(array $schema): static {
    $this->set("schema", Json::encode($schema));
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getOptions(): array {
    $data = $this->get("options")->value;
    if ($data === NULL || $data === "") {
      return [];
    }
    $decoded = Json::decode($data);
    return is_array($decoded) ? $decoded : [];
  }

  /**
   * {@inheritdoc}
   */
  public function setOptions(array $options): static {
    $this->set("options", Json::encode($options));
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getDefaultValue(): mixed {
    $data = $this->get("default_value")->value;
    if ($data === NULL || $data === "") {
      return NULL;
    }
    return Json::decode($data);
  }

  /**
   * {@inheritdoc}
   */
  public function setDefaultValue(mixed $value): static {
    $this->set("default_value", Json::encode($value));
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getResponseData(): mixed {
    $data = $this->get("response_data")->value;
    if ($data === NULL || $data === "") {
      return NULL;
    }
    return Json::decode($data);
  }

  /**
   * {@inheritdoc}
   */
  public function setResponseData(mixed $data): static {
    $this->set("response_data", Json::encode($data));
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getContext(): array {
    $data = $this->get("context")->value;
    if ($data === NULL || $data === "") {
      return [];
    }
    $decoded = Json::decode($data);
    return is_array($decoded) ? $decoded : [];
  }

  /**
   * {@inheritdoc}
   */
  public function setContext(array $context): static {
    $this->set("context", Json::encode($context));
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getExpires(): ?int {
    $value = $this->get("expires")->value;
    return $value !== NULL ? (int) $value : NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function setExpires(?int $timestamp): static {
    $this->set("expires", $timestamp);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getResolvedBy(): ?int {
    $value = $this->get("resolved_by")->target_id;
    return $value !== NULL ? (int) $value : NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function setResolvedBy(?int $user_id): static {
    if ($user_id === NULL) {
      $this->set("resolved_by", NULL);
    }
    else {
      $this->set("resolved_by", ["target_id" => $user_id]);
    }
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getResolvedAt(): ?int {
    $value = $this->get("resolved_at")->value;
    return $value !== NULL ? (int) $value : NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function setResolvedAt(?int $timestamp): static {
    $this->set("resolved_at", $timestamp);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getCreatedTime(): int {
    $value = $this->get("created")->value;
    return $value !== NULL ? (int) $value : 0;
  }

  /**
   * {@inheritdoc}
   */
  public function getChangedTime(): int {
    $value = $this->get("changed")->value;
    return $value !== NULL ? (int) $value : 0;
  }

  /**
   * {@inheritdoc}
   */
  public function isPending(): bool {
    return $this->getStatus() === InterruptStatus::Pending;
  }

  /**
   * {@inheritdoc}
   */
  public function isResolved(): bool {
    return $this->getStatus() === InterruptStatus::Resolved;
  }

  /**
   * {@inheritdoc}
   */
  public function isExpired(): bool {
    $status = $this->getStatus();
    if ($status === InterruptStatus::Expired) {
      return TRUE;
    }

    // Check if past expiration time.
    $expires = $this->getExpires();
    if ($expires !== NULL && $expires > 0 && time() > $expires) {
      return TRUE;
    }

    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function resolve(mixed $response_data, ?int $user_id = NULL): static {
    $this->setStatus(InterruptStatus::Resolved);
    $this->setResponseData($response_data);
    $this->setResolvedBy($user_id);
    $this->setResolvedAt(time());
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function cancel(): static {
    $this->setStatus(InterruptStatus::Cancelled);
    $this->setResolvedAt(time());
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function expire(): static {
    $this->setStatus(InterruptStatus::Expired);
    $this->setResolvedAt(time());
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function toApiArray(): array {
    return [
      "id" => $this->uuid(),
      "type" => $this->getType()->value,
      "status" => $this->getStatus()->value,
      "message" => $this->getMessage(),
      "nodeId" => $this->getNodeId(),
      "workflowId" => $this->getWorkflowId(),
      "pipelineId" => $this->getPipelineId(),
      "sessionId" => $this->getSessionId(),
      "schema" => $this->getSchema() ?: NULL,
      "options" => $this->getOptions() ?: NULL,
      "context" => $this->getContext() ?: NULL,
      "defaultValue" => $this->getDefaultValue(),
      "responseData" => $this->getResponseData(),
      "createdAt" => date("c", $this->getCreatedTime()),
      "expiresAt" => $this->getExpires() !== NULL ? date("c", $this->getExpires()) : NULL,
      "resolvedAt" => $this->getResolvedAt() !== NULL ? date("c", $this->getResolvedAt()) : NULL,
      "resolvedBy" => $this->getResolvedBy(),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public static function baseFieldDefinitions(EntityTypeInterface $entity_type): array {
    $fields = parent::baseFieldDefinitions($entity_type);

    // Add the owner field.
    $fields += static::ownerBaseFieldDefinitions($entity_type);

    $fields["workflow_id"] = BaseFieldDefinition::create("string")
      ->setLabel(new TranslatableMarkup("Workflow ID"))
      ->setDescription(new TranslatableMarkup("The workflow that triggered this interrupt."))
      ->setRequired(TRUE)
      ->setSettings([
        "max_length" => 255,
      ])
      ->setDisplayConfigurable("form", FALSE)
      ->setDisplayConfigurable("view", TRUE);

    $fields["pipeline_id"] = BaseFieldDefinition::create("string")
      ->setLabel(new TranslatableMarkup("Pipeline ID"))
      ->setDescription(new TranslatableMarkup("The pipeline/execution ID (optional)."))
      ->setRequired(FALSE)
      ->setSettings([
        "max_length" => 255,
      ])
      ->setDisplayConfigurable("form", FALSE)
      ->setDisplayConfigurable("view", TRUE);

    $fields["session_id"] = BaseFieldDefinition::create("entity_reference")
      ->setLabel(new TranslatableMarkup("Session"))
      ->setDescription(new TranslatableMarkup("The playground session (optional)."))
      ->setRequired(FALSE)
      ->setSetting("target_type", "flowdrop_session")
      ->setDisplayConfigurable("form", FALSE)
      ->setDisplayConfigurable("view", TRUE);

    $fields["node_id"] = BaseFieldDefinition::create("string")
      ->setLabel(new TranslatableMarkup("Node ID"))
      ->setDescription(new TranslatableMarkup("The node that triggered this interrupt."))
      ->setRequired(TRUE)
      ->setSettings([
        "max_length" => 255,
      ])
      ->setDisplayConfigurable("form", FALSE)
      ->setDisplayConfigurable("view", TRUE);

    $fields["type"] = BaseFieldDefinition::create("string")
      ->setLabel(new TranslatableMarkup("Type"))
      ->setDescription(new TranslatableMarkup("The interrupt type (confirmation, choice, text, form)."))
      ->setRequired(TRUE)
      ->setDefaultValue("confirmation")
      ->setSettings([
        "max_length" => 32,
      ])
      ->setDisplayConfigurable("form", FALSE)
      ->setDisplayConfigurable("view", TRUE);

    $fields["status"] = BaseFieldDefinition::create("string")
      ->setLabel(new TranslatableMarkup("Status"))
      ->setDescription(new TranslatableMarkup("The interrupt status (pending, resolved, expired, cancelled)."))
      ->setRequired(TRUE)
      ->setDefaultValue("pending")
      ->setSettings([
        "max_length" => 32,
      ])
      ->setDisplayConfigurable("form", FALSE)
      ->setDisplayConfigurable("view", TRUE);

    $fields["message"] = BaseFieldDefinition::create("string_long")
      ->setLabel(new TranslatableMarkup("Message"))
      ->setDescription(new TranslatableMarkup("The message to display to the user."))
      ->setRequired(TRUE)
      ->setDisplayConfigurable("form", FALSE)
      ->setDisplayConfigurable("view", TRUE);

    $fields["schema"] = BaseFieldDefinition::create("string_long")
      ->setLabel(new TranslatableMarkup("Schema"))
      ->setDescription(new TranslatableMarkup("JSON Schema for input validation."))
      ->setRequired(FALSE)
      ->setDisplayConfigurable("form", FALSE)
      ->setDisplayConfigurable("view", FALSE);

    $fields["options"] = BaseFieldDefinition::create("string_long")
      ->setLabel(new TranslatableMarkup("Options"))
      ->setDescription(new TranslatableMarkup("Options for choice-type interrupts (JSON)."))
      ->setRequired(FALSE)
      ->setDisplayConfigurable("form", FALSE)
      ->setDisplayConfigurable("view", FALSE);

    $fields["default_value"] = BaseFieldDefinition::create("string_long")
      ->setLabel(new TranslatableMarkup("Default Value"))
      ->setDescription(new TranslatableMarkup("Default/pre-filled value (JSON)."))
      ->setRequired(FALSE)
      ->setDisplayConfigurable("form", FALSE)
      ->setDisplayConfigurable("view", FALSE);

    $fields["response_data"] = BaseFieldDefinition::create("string_long")
      ->setLabel(new TranslatableMarkup("Response Data"))
      ->setDescription(new TranslatableMarkup("User's response data (JSON)."))
      ->setRequired(FALSE)
      ->setDisplayConfigurable("form", FALSE)
      ->setDisplayConfigurable("view", FALSE);

    $fields["context"] = BaseFieldDefinition::create("string_long")
      ->setLabel(new TranslatableMarkup("Context"))
      ->setDescription(new TranslatableMarkup("Execution context for resuming (JSON)."))
      ->setRequired(FALSE)
      ->setDisplayConfigurable("form", FALSE)
      ->setDisplayConfigurable("view", FALSE);

    $fields["expires"] = BaseFieldDefinition::create("timestamp")
      ->setLabel(new TranslatableMarkup("Expires"))
      ->setDescription(new TranslatableMarkup("When the interrupt expires."))
      ->setRequired(FALSE)
      ->setDisplayConfigurable("form", FALSE)
      ->setDisplayConfigurable("view", TRUE);

    $fields["resolved_by"] = BaseFieldDefinition::create("entity_reference")
      ->setLabel(new TranslatableMarkup("Resolved By"))
      ->setDescription(new TranslatableMarkup("The user who resolved the interrupt."))
      ->setRequired(FALSE)
      ->setSetting("target_type", "user")
      ->setDisplayConfigurable("form", FALSE)
      ->setDisplayConfigurable("view", TRUE);

    $fields["resolved_at"] = BaseFieldDefinition::create("timestamp")
      ->setLabel(new TranslatableMarkup("Resolved At"))
      ->setDescription(new TranslatableMarkup("When the interrupt was resolved."))
      ->setRequired(FALSE)
      ->setDisplayConfigurable("form", FALSE)
      ->setDisplayConfigurable("view", TRUE);

    $fields["created"] = BaseFieldDefinition::create("created")
      ->setLabel(new TranslatableMarkup("Created"))
      ->setDescription(new TranslatableMarkup("The time that the interrupt was created."));

    $fields["changed"] = BaseFieldDefinition::create("changed")
      ->setLabel(new TranslatableMarkup("Changed"))
      ->setDescription(new TranslatableMarkup("The time that the interrupt was last updated."));

    return $fields;
  }

}
