<?php

declare(strict_types=1);

namespace Drupal\flowdrop_stategraph\DTO;

/**
 * Represents a checkpoint snapshot of graph state.
 *
 * Checkpoints capture the complete state at a point in execution,
 * enabling:
 * - Durable execution (resume after failures).
 * - Time-travel debugging (inspect past states).
 * - Conversation threading (group related executions).
 * - Human-in-the-loop (pause and resume with state inspection).
 *
 * Example usage:
 * @code
 * $checkpoint = new Checkpoint(
 *   id: "cp_123",
 *   threadId: "thread_abc",
 *   state: $graphState,
 *   nodeId: "process_data",
 *   createdAt: time(),
 * );
 * @endcode
 *
 * @api
 */
final class Checkpoint implements \JsonSerializable {

  /**
   * Constructs a new Checkpoint.
   *
   * @param string $id
   *   Unique checkpoint identifier.
   * @param string $threadId
   *   Thread ID for grouping related checkpoints.
   * @param \Drupal\flowdrop_stategraph\DTO\GraphState $state
   *   The full graph state snapshot.
   * @param string $nodeId
   *   The node ID where checkpoint was created.
   * @param int $createdAt
   *   Unix timestamp of checkpoint creation.
   * @param string|null $parentCheckpointId
   *   ID of the parent checkpoint (for checkpoint chains).
   * @param array<string, mixed> $metadata
   *   Additional checkpoint metadata.
   */
  public function __construct(
    public readonly string $id,
    public readonly string $threadId,
    public readonly GraphState $state,
    public readonly string $nodeId,
    public readonly int $createdAt,
    public readonly ?string $parentCheckpointId = NULL,
    public readonly array $metadata = [],
  ) {}

  /**
   * Creates a new Checkpoint from state.
   *
   * @param \Drupal\flowdrop_stategraph\DTO\GraphState $state
   *   The current graph state.
   * @param string $nodeId
   *   The current node ID.
   * @param string|null $parentCheckpointId
   *   Optional parent checkpoint ID.
   *
   * @return self
   *   A new Checkpoint instance.
   */
  public static function create(
    GraphState $state,
    string $nodeId,
    ?string $parentCheckpointId = NULL,
  ): self {
    return new self(
      id: self::generateId(),
      threadId: $state->threadId !== "" ? $state->threadId : self::generateThreadId(),
      state: $state,
      nodeId: $nodeId,
      createdAt: time(),
      parentCheckpointId: $parentCheckpointId,
      metadata: [
        "iteration_count" => $state->iterationCount,
        "is_complete" => $state->isComplete,
      ],
    );
  }

  /**
   * Generates a unique checkpoint ID.
   *
   * @return string
   *   A unique checkpoint ID.
   */
  private static function generateId(): string {
    return "cp_" . bin2hex(random_bytes(12));
  }

  /**
   * Generates a unique thread ID.
   *
   * @return string
   *   A unique thread ID.
   */
  private static function generateThreadId(): string {
    return "thread_" . bin2hex(random_bytes(8));
  }

  /**
   * Creates a Checkpoint from an array.
   *
   * @param array<string, mixed> $data
   *   The checkpoint data.
   *
   * @return self
   *   A new Checkpoint instance.
   */
  public static function fromArray(array $data): self {
    $stateData = $data["state"] ?? [];
    $state = $stateData instanceof GraphState
      ? $stateData
      : GraphState::fromArray(is_array($stateData) ? $stateData : []);

    return new self(
      id: (string) ($data["id"] ?? self::generateId()),
      threadId: (string) ($data["threadId"] ?? self::generateThreadId()),
      state: $state,
      nodeId: (string) ($data["nodeId"] ?? ""),
      createdAt: (int) ($data["createdAt"] ?? time()),
      parentCheckpointId: isset($data["parentCheckpointId"])
        ? (string) $data["parentCheckpointId"]
        : NULL,
      metadata: $data["metadata"] ?? [],
    );
  }

  /**
   * Converts the checkpoint to an array.
   *
   * @return array<string, mixed>
   *   The checkpoint as an array.
   */
  public function toArray(): array {
    return [
      "id" => $this->id,
      "threadId" => $this->threadId,
      "state" => $this->state->toArray(),
      "nodeId" => $this->nodeId,
      "createdAt" => $this->createdAt,
      "parentCheckpointId" => $this->parentCheckpointId,
      "metadata" => $this->metadata,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function jsonSerialize(): mixed {
    return $this->toArray();
  }

}
