<?php

declare(strict_types=1);

namespace Drupal\flowdrop_stategraph\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_stategraph\DTO\Checkpoint;
use Drupal\flowdrop_stategraph\DTO\GraphState;
use Drupal\flowdrop_stategraph\Access\FlowDropStateCheckpointAccessControlHandler;
use Drupal\user\EntityOwnerTrait;
use Drupal\views\EntityViewsData;

/**
 * FlowDropStateCheckpoint entity for persistent checkpoint storage.
 *
 * This entity stores graph state snapshots using string_long fields
 * with JSON serialization for complex state data.
 *
 * The entity supports:
 * - Thread-based grouping for conversation continuity.
 * - Parent-child relationships for checkpoint chains.
 * - JSON-encoded string storage for state data.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
#[ContentEntityType(
  id: 'state_checkpoint',
  label: new TranslatableMarkup('State Checkpoint'),
  label_collection: new TranslatableMarkup('State Checkpoints'),
  label_singular: new TranslatableMarkup('state checkpoint'),
  label_plural: new TranslatableMarkup('state checkpoints'),
  entity_keys: [
    'id' => 'id',
    'uuid' => 'uuid',
    'label' => 'checkpoint_id',
    'owner' => 'uid',
  ],
  handlers: [
    'access' => FlowDropStateCheckpointAccessControlHandler::class,
    'views_data' => EntityViewsData::class,
  ],
  base_table: 'state_checkpoint',
  admin_permission: 'administer flowdrop_stategraph',
)]
class FlowDropStateCheckpoint extends ContentEntityBase implements FlowDropStateCheckpointInterface {

  use EntityChangedTrait;
  use EntityOwnerTrait;

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

    // Checkpoint ID - unique identifier matching the DTO.
    $fields['checkpoint_id'] = BaseFieldDefinition::create('string')
      ->setLabel(new TranslatableMarkup('Checkpoint ID'))
      ->setDescription(new TranslatableMarkup('Unique checkpoint identifier.'))
      ->setRequired(TRUE)
      ->setSetting('max_length', 255)
      ->addConstraint('UniqueField');

    // Thread ID - groups related checkpoints.
    $fields['thread_id'] = BaseFieldDefinition::create('string')
      ->setLabel(new TranslatableMarkup('Thread ID'))
      ->setDescription(new TranslatableMarkup('Groups related checkpoints for conversation threading.'))
      ->setRequired(TRUE)
      ->setSetting('max_length', 255);

    // State Data - String field for full graph state snapshot (JSON encoded).
    $fields['state_data'] = BaseFieldDefinition::create('string_long')
      ->setLabel(new TranslatableMarkup('State Data'))
      ->setDescription(new TranslatableMarkup('Full graph state snapshot as JSON string.'))
      ->setRequired(TRUE);

    // Node ID - the node where checkpoint was created.
    $fields['node_id'] = BaseFieldDefinition::create('string')
      ->setLabel(new TranslatableMarkup('Node ID'))
      ->setDescription(new TranslatableMarkup('The node ID where this checkpoint was created.'))
      ->setSetting('max_length', 255);

    // Metadata - String field for execution metadata (JSON encoded).
    $fields['metadata'] = BaseFieldDefinition::create('string_long')
      ->setLabel(new TranslatableMarkup('Metadata'))
      ->setDescription(new TranslatableMarkup('Execution metadata: timestamps, node history, etc.'));

    // Parent Checkpoint - reference to previous checkpoint in chain.
    $fields['parent_checkpoint'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(new TranslatableMarkup('Parent Checkpoint'))
      ->setDescription(new TranslatableMarkup('Previous checkpoint in the chain.'))
      ->setSetting('target_type', 'state_checkpoint');

    // Owner - the user who created this checkpoint.
    $fields['uid'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(new TranslatableMarkup('Owner'))
      ->setDescription(new TranslatableMarkup('The user who created this checkpoint.'))
      ->setSetting('target_type', 'user')
      ->setDefaultValueCallback(self::class . '::getDefaultEntityOwner');

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

    // Changed timestamp.
    $fields['changed'] = BaseFieldDefinition::create('changed')
      ->setLabel(new TranslatableMarkup('Changed'))
      ->setDescription(new TranslatableMarkup('The time that the checkpoint was last edited.'));

    return $fields;
  }

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

  /**
   * {@inheritdoc}
   */
  public function setCheckpointId(string $checkpointId): static {
    $this->set('checkpoint_id', $checkpointId);
    return $this;
  }

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

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

  /**
   * {@inheritdoc}
   */
  public function getStateData(): array {
    $value = $this->get('state_data')->value;

    if ($value === NULL || $value === '') {
      return [];
    }

    // Decode JSON string to array.
    $decoded = Json::decode($value);
    if (is_array($decoded)) {
      return $decoded;
    }

    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function setStateData(array $stateData): static {
    // Encode array to JSON string for storage.
    $this->set('state_data', Json::encode($stateData));
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getGraphState(): GraphState {
    return GraphState::fromArray($this->getStateData());
  }

  /**
   * {@inheritdoc}
   */
  public function setGraphState(GraphState $state): static {
    return $this->setStateData($state->toArray());
  }

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

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

  /**
   * {@inheritdoc}
   */
  public function getMetadata(): array {
    $value = $this->get('metadata')->value;

    if ($value === NULL || $value === '') {
      return [];
    }

    // Decode JSON string to array.
    $decoded = Json::decode($value);
    if (is_array($decoded)) {
      return $decoded;
    }

    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function setMetadata(array $metadata): static {
    // Encode array to JSON string for storage.
    $this->set('metadata', Json::encode($metadata));
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getParentCheckpointId(): ?string {
    $reference = $this->get('parent_checkpoint')->target_id;
    if ($reference === NULL) {
      return NULL;
    }

    // Load the parent entity to get its checkpoint_id.
    $parentEntity = $this->get('parent_checkpoint')->entity;
    if ($parentEntity instanceof FlowDropStateCheckpointInterface) {
      return $parentEntity->getCheckpointId();
    }

    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function setParentCheckpointId(?string $parentCheckpointId): static {
    if ($parentCheckpointId === NULL) {
      $this->set('parent_checkpoint', NULL);
      return $this;
    }

    // We store the entity reference by target_id (entity ID).
    // The caller should resolve the checkpoint_id to entity ID.
    // For now, store NULL and let EntityCheckpointer handle this.
    $this->set('parent_checkpoint', NULL);
    return $this;
  }

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

  /**
   * {@inheritdoc}
   */
  public function toCheckpointDto(): Checkpoint {
    return new Checkpoint(
      id: $this->getCheckpointId(),
      threadId: $this->getThreadId(),
      state: $this->getGraphState(),
      nodeId: $this->getNodeId(),
      createdAt: $this->getCreatedTime(),
      parentCheckpointId: $this->getParentCheckpointId(),
      metadata: $this->getMetadata(),
    );
  }

  /**
   * Creates entity from a Checkpoint DTO.
   *
   * @param \Drupal\flowdrop_stategraph\DTO\Checkpoint $checkpoint
   *   The checkpoint DTO.
   *
   * @return static
   *   A new FlowDropStateCheckpoint entity (not saved).
   */
  public static function fromCheckpointDto(Checkpoint $checkpoint): static {
    $entity = new static([], 'state_checkpoint');
    $entity->setCheckpointId($checkpoint->id);
    $entity->setThreadId($checkpoint->threadId);
    $entity->setGraphState($checkpoint->state);
    $entity->setNodeId($checkpoint->nodeId);
    $entity->setMetadata($checkpoint->metadata);

    return $entity;
  }

}
