<?php

declare(strict_types=1);

namespace Drupal\flowdrop_runtime\Entity;

use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\Attribute\ContentEntityType;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\ContentEntityDeleteForm;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Form\DeleteMultipleForm;
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flowdrop_orchestration\DTO\Snapshot\NodeSnapshot;
use Drupal\flowdrop_orchestration\DTO\Snapshot\WorkflowSnapshot;
use Drupal\flowdrop_runtime\FlowDropWorkflowSnapshotAccessControlHandler;
use Drupal\flowdrop_runtime\FlowDropWorkflowSnapshotListBuilder;
use Drupal\user\EntityOwnerTrait;
use Drupal\views\EntityViewsData;

/**
 * FlowDrop Workflow Snapshot Entity.
 *
 * Provides persistent storage for workflow execution snapshots, enabling
 * workflow resume, handover between orchestrators, and state inspection.
 *
 * @internal
 *   This class is not part of the public API and may change without notice.
 */
#[ContentEntityType(
  id: "flowdrop_workflow_snapshot",
  label: new TranslatableMarkup("FlowDrop Workflow Snapshot"),
  label_collection: new TranslatableMarkup("FlowDrop Workflow Snapshots"),
  label_singular: new TranslatableMarkup("workflow snapshot"),
  label_plural: new TranslatableMarkup("workflow snapshots"),
  entity_keys: [
    "id" => "id",
    "label" => "label",
    "owner" => "uid",
    "uuid" => "uuid",
  ],
  handlers: [
    "list_builder" => FlowDropWorkflowSnapshotListBuilder::class,
    "views_data" => EntityViewsData::class,
    "access" => FlowDropWorkflowSnapshotAccessControlHandler::class,
    "form" => [
      "delete" => ContentEntityDeleteForm::class,
      "delete-multiple-confirm" => DeleteMultipleForm::class,
    ],
    "route_provider" => [
      "html" => AdminHtmlRouteProvider::class,
    ],
  ],
  links: [
    "collection" => "/admin/flowdrop/snapshots",
    "canonical" => "/admin/flowdrop/snapshots/{flowdrop_workflow_snapshot}",
    "delete-form" => "/admin/flowdrop/snapshots/{flowdrop_workflow_snapshot}/delete",
    "delete-multiple-form" => "/admin/flowdrop/snapshots/delete-multiple",
  ],
  admin_permission: "administer flowdrop_workflow_snapshot",
  base_table: "flowdrop_workflow_snapshot",
  label_count: [
    "singular" => "@count workflow snapshot",
    "plural" => "@count workflow snapshots",
  ],
)]
class FlowDropWorkflowSnapshot extends ContentEntityBase implements FlowDropWorkflowSnapshotInterface {

  use EntityChangedTrait;
  use EntityOwnerTrait;

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

    // Label field (auto-generated from workflow ID and timestamp).
    $fields["label"] = BaseFieldDefinition::create("string")
      ->setLabel(t("Label"))
      ->setRequired(TRUE)
      ->setSetting("max_length", 255)
      ->setDisplayOptions("view", [
        "label" => "hidden",
        "type" => "string",
        "weight" => -5,
      ])
      ->setDisplayConfigurable("view", TRUE);

    // Workflow ID reference.
    $fields["workflow_id"] = BaseFieldDefinition::create("string")
      ->setLabel(t("Workflow ID"))
      ->setDescription(t("The ID of the workflow this snapshot belongs to."))
      ->setRequired(TRUE)
      ->setSetting("max_length", 255)
      ->setDisplayOptions("view", [
        "label" => "inline",
        "type" => "string",
        "weight" => 0,
      ])
      ->setDisplayConfigurable("view", TRUE);

    // Workflow version hash for drift detection.
    $fields["workflow_version"] = BaseFieldDefinition::create("string")
      ->setLabel(t("Workflow Version"))
      ->setDescription(t("Hash of the workflow definition at snapshot time."))
      ->setRequired(TRUE)
      ->setSetting("max_length", 64)
      ->setDisplayOptions("view", [
        "label" => "inline",
        "type" => "string",
        "weight" => 1,
      ])
      ->setDisplayConfigurable("view", TRUE);

    // Snapshot status.
    $fields["snapshot_status"] = BaseFieldDefinition::create("string")
      ->setLabel(t("Status"))
      ->setDescription(t("The workflow execution status."))
      ->setRequired(TRUE)
      ->setDefaultValue(WorkflowSnapshot::STATUS_PENDING)
      ->setSetting("max_length", 32)
      ->setDisplayOptions("view", [
        "label" => "inline",
        "type" => "string",
        "weight" => 2,
      ])
      ->setDisplayConfigurable("view", TRUE);

    // Node states (JSON serialized).
    $fields["node_states"] = BaseFieldDefinition::create("string_long")
      ->setLabel(t("Node States"))
      ->setDescription(t("JSON-encoded map of node ID to node state."))
      ->setDefaultValue("{}");

    // Initial input data (JSON serialized).
    $fields["initial_input"] = BaseFieldDefinition::create("string_long")
      ->setLabel(t("Initial Input"))
      ->setDescription(t("JSON-encoded initial input data."))
      ->setDefaultValue("{}");

    // Metadata (JSON serialized).
    $fields["snapshot_metadata"] = BaseFieldDefinition::create("string_long")
      ->setLabel(t("Metadata"))
      ->setDescription(t("JSON-encoded snapshot metadata."))
      ->setDefaultValue("{}");

    // StateGraph specific: Messages (JSON serialized).
    $fields["messages"] = BaseFieldDefinition::create("string_long")
      ->setLabel(t("Messages"))
      ->setDescription(t("JSON-encoded message history for chat/agent workflows."))
      ->setDefaultValue("[]");

    // StateGraph specific: Data (JSON serialized).
    $fields["data"] = BaseFieldDefinition::create("string_long")
      ->setLabel(t("Data"))
      ->setDescription(t("JSON-encoded general data storage."))
      ->setDefaultValue("{}");

    // StateGraph specific: Iterator state (JSON serialized).
    $fields["iterator_state"] = BaseFieldDefinition::create("string_long")
      ->setLabel(t("Iterator State"))
      ->setDescription(t("JSON-encoded iterator state for array processing."));

    // StateGraph specific: Current node ID.
    $fields["current_node_id"] = BaseFieldDefinition::create("string")
      ->setLabel(t("Current Node ID"))
      ->setDescription(t("The ID of the currently executing node."))
      ->setSetting("max_length", 255)
      ->setDefaultValue("");

    // StateGraph specific: Iteration count.
    $fields["iteration_count"] = BaseFieldDefinition::create("integer")
      ->setLabel(t("Iteration Count"))
      ->setDescription(t("Current iteration count for loop protection."))
      ->setDefaultValue(0);

    // StateGraph specific: Error message.
    $fields["error"] = BaseFieldDefinition::create("string_long")
      ->setLabel(t("Error"))
      ->setDescription(t("Error message if execution failed."));

    // Thread ID for conversation continuity.
    $fields["thread_id"] = BaseFieldDefinition::create("string")
      ->setLabel(t("Thread ID"))
      ->setDescription(t("Thread ID for conversation continuity."))
      ->setSetting("max_length", 255);

    // Execution ID that created this snapshot.
    $fields["execution_id"] = BaseFieldDefinition::create("string")
      ->setLabel(t("Execution ID"))
      ->setDescription(t("The execution ID that created this snapshot."))
      ->setSetting("max_length", 255);

    // Owner field.
    $fields["uid"] = BaseFieldDefinition::create("entity_reference")
      ->setLabel(t("Author"))
      ->setSetting("target_type", "user")
      ->setDefaultValueCallback(self::class . "::getDefaultEntityOwner")
      ->setDisplayOptions("view", [
        "label" => "inline",
        "type" => "author",
        "weight" => 10,
      ])
      ->setDisplayConfigurable("view", TRUE);

    // Created timestamp.
    $fields["created"] = BaseFieldDefinition::create("created")
      ->setLabel(t("Created"))
      ->setDescription(t("The time the snapshot was created."))
      ->setDisplayOptions("view", [
        "label" => "inline",
        "type" => "timestamp",
        "weight" => 11,
      ])
      ->setDisplayConfigurable("view", TRUE);

    // Changed timestamp.
    $fields["changed"] = BaseFieldDefinition::create("changed")
      ->setLabel(t("Changed"))
      ->setDescription(t("The time the snapshot was last updated."));

    return $fields;
  }

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

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

  /**
   * {@inheritdoc}
   */
  public function getWorkflowVersion(): string {
    return (string) $this->get("workflow_version")->value;
  }

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

  /**
   * {@inheritdoc}
   */
  public function getSnapshotStatus(): string {
    return (string) $this->get("snapshot_status")->value;
  }

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

  /**
   * {@inheritdoc}
   */
  public function getNodeStates(): array {
    $value = $this->get("node_states")->value;
    if (empty($value)) {
      return [];
    }
    $decoded = Json::decode($value);
    return is_array($decoded) ? $decoded : [];
  }

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

  /**
   * {@inheritdoc}
   */
  public function getInitialInput(): array {
    $value = $this->get("initial_input")->value;
    if (empty($value)) {
      return [];
    }
    $decoded = Json::decode($value);
    return is_array($decoded) ? $decoded : [];
  }

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

  /**
   * {@inheritdoc}
   */
  public function getSnapshotMetadata(): array {
    $value = $this->get("snapshot_metadata")->value;
    if (empty($value)) {
      return [];
    }
    $decoded = Json::decode($value);
    return is_array($decoded) ? $decoded : [];
  }

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

  /**
   * {@inheritdoc}
   */
  public function getMessages(): array {
    $value = $this->get("messages")->value;
    if (empty($value)) {
      return [];
    }
    $decoded = Json::decode($value);
    return is_array($decoded) ? $decoded : [];
  }

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

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

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

  /**
   * {@inheritdoc}
   */
  public function getIteratorState(): ?array {
    $value = $this->get("iterator_state")->value;
    if (empty($value)) {
      return NULL;
    }
    $decoded = Json::decode($value);
    return is_array($decoded) ? $decoded : NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function setIteratorState(?array $iterator_state): static {
    $this->set("iterator_state", $iterator_state !== NULL ? Json::encode($iterator_state) : NULL);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getCurrentNodeId(): string {
    return (string) ($this->get("current_node_id")->value ?? "");
  }

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

  /**
   * {@inheritdoc}
   */
  public function getIterationCount(): int {
    return (int) ($this->get("iteration_count")->value ?? 0);
  }

  /**
   * {@inheritdoc}
   */
  public function setIterationCount(int $count): static {
    $this->set("iteration_count", $count);
    return $this;
  }

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

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

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

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

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

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

  /**
   * {@inheritdoc}
   */
  public function toWorkflowSnapshot(): WorkflowSnapshot {
    // Convert node states array to NodeSnapshot objects.
    $nodeStates = [];
    foreach ($this->getNodeStates() as $nodeId => $nodeStateData) {
      $nodeStates[$nodeId] = NodeSnapshot::fromArray($nodeStateData);
    }

    return new WorkflowSnapshot(
      workflowId: $this->getWorkflowId(),
      workflowVersion: $this->getWorkflowVersion(),
      executionId: $this->getExecutionId() ?? '',
      status: $this->getSnapshotStatus(),
      nodeStates: $nodeStates,
      initialInput: $this->getInitialInput(),
      messages: $this->getMessages(),
      iterator: $this->getIteratorState(),
      iterationCount: $this->getIterationCount(),
      threadId: $this->getThreadId() ?? '',
      error: $this->getError(),
      metadata: $this->getSnapshotMetadata(),
      createdAt: (int) $this->get("created")->value,
      updatedAt: (int) $this->get("changed")->value,
    );
  }

  /**
   * {@inheritdoc}
   */
  public function fromWorkflowSnapshot(WorkflowSnapshot $snapshot): static {
    // Generate label from workflow ID and timestamp.
    $createdAt = $snapshot->createdAt ?: time();
    $label = sprintf(
      "Snapshot: %s (%s)",
      $snapshot->workflowId,
      date("Y-m-d H:i:s", $createdAt)
    );

    $this->set("label", $label);
    $this->setWorkflowId($snapshot->workflowId);
    $this->setWorkflowVersion($snapshot->workflowVersion);
    $this->setSnapshotStatus($snapshot->status);
    $this->setExecutionId($snapshot->executionId);
    $this->setThreadId($snapshot->threadId);

    // Convert NodeSnapshot objects to arrays.
    $nodeStatesArray = [];
    foreach ($snapshot->nodeStates as $nodeId => $nodeSnapshot) {
      $nodeStatesArray[$nodeId] = $nodeSnapshot->toArray();
    }
    $this->setNodeStates($nodeStatesArray);

    $this->setInitialInput($snapshot->initialInput);
    $this->setSnapshotMetadata($snapshot->metadata);

    // Convert Message objects to arrays if they are objects.
    $messagesArray = [];
    foreach ($snapshot->messages as $message) {
      if (is_object($message) && method_exists($message, "toArray")) {
        $messagesArray[] = $message->toArray();
      }
      elseif (is_array($message)) {
        $messagesArray[] = $message;
      }
    }
    $this->setMessages($messagesArray);

    // Store initial input as data (WorkflowSnapshot doesn't have separate
    // data field).
    $this->setData($snapshot->initialInput);

    // Handle iterator state - could be object or array.
    if ($snapshot->iterator !== NULL) {
      if (is_object($snapshot->iterator) && method_exists($snapshot->iterator, "toArray")) {
        $this->setIteratorState($snapshot->iterator->toArray());
      }
      elseif (is_array($snapshot->iterator)) {
        $this->setIteratorState($snapshot->iterator);
      }
    }

    // Get current_node_id from metadata if available.
    $currentNodeId = $snapshot->metadata["current_node_id"] ?? "";
    $this->setCurrentNodeId($currentNodeId);

    $this->setIterationCount($snapshot->iterationCount);
    $this->setError($snapshot->error);

    // Set timestamps.
    $this->set("created", $createdAt);
    $this->set("changed", $snapshot->updatedAt ?: time());

    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public static function createFromWorkflowSnapshot(WorkflowSnapshot $snapshot, EntityStorageInterface $storage): static {
    /** @var static $entity */
    $entity = $storage->create([]);
    $entity->fromWorkflowSnapshot($snapshot);

    return $entity;
  }

}
