<?php

declare(strict_types=1);

namespace Drupal\flowdrop_stategraph\Checkpointer;

use Drupal\flowdrop_stategraph\DTO\Checkpoint;
use Psr\Log\LoggerInterface;

/**
 * In-memory checkpointer for development and testing.
 *
 * This checkpointer stores checkpoints in memory (static array),
 * making it ideal for:
 * - Unit testing without database dependencies.
 * - Development and debugging.
 * - Short-lived execution contexts.
 *
 * Note: Data is lost when the request ends. For persistent storage,
 * use EntityCheckpointer.
 *
 * @see \Drupal\flowdrop_stategraph\Checkpointer\EntityCheckpointer
 *
 * @internal This class is not part of the public API and may change without notice.
 */
class MemoryCheckpointer implements CheckpointerInterface {

  /**
   * In-memory storage for checkpoints.
   *
   * @var array<string, \Drupal\flowdrop_stategraph\DTO\Checkpoint>
   */
  private static array $storage = [];

  /**
   * Index of checkpoints by thread ID.
   *
   * @var array<string, array<string>>
   */
  private static array $threadIndex = [];

  /**
   * The logger service.
   *
   * @var \Psr\Log\LoggerInterface
   */
  private readonly LoggerInterface $logger;

  /**
   * Constructs a new MemoryCheckpointer.
   *
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger service.
   */
  public function __construct(LoggerInterface $logger) {
    $this->logger = $logger;
  }

  /**
   * {@inheritdoc}
   */
  public function get(string $checkpointId): ?Checkpoint {
    $checkpoint = self::$storage[$checkpointId] ?? NULL;

    if ($checkpoint !== NULL) {
      $this->logger->debug("Memory checkpointer: retrieved checkpoint @id", [
        "@id" => $checkpointId,
      ]);
    }

    return $checkpoint;
  }

  /**
   * {@inheritdoc}
   */
  public function put(Checkpoint $checkpoint): string {
    $checkpointId = $checkpoint->id;
    $threadId = $checkpoint->threadId;

    // Store checkpoint.
    self::$storage[$checkpointId] = $checkpoint;

    // Update thread index.
    if (!isset(self::$threadIndex[$threadId])) {
      self::$threadIndex[$threadId] = [];
    }

    if (!in_array($checkpointId, self::$threadIndex[$threadId], TRUE)) {
      self::$threadIndex[$threadId][] = $checkpointId;
    }

    $this->logger->debug("Memory checkpointer: stored checkpoint @id for thread @thread", [
      "@id" => $checkpointId,
      "@thread" => $threadId,
    ]);

    return $checkpointId;
  }

  /**
   * {@inheritdoc}
   */
  public function list(string $threadId, int $limit = 100): array {
    $checkpointIds = self::$threadIndex[$threadId] ?? [];
    $checkpoints = [];

    // Get all checkpoints for this thread.
    foreach ($checkpointIds as $id) {
      if (isset(self::$storage[$id])) {
        $checkpoints[] = self::$storage[$id];
      }
    }

    // Sort by creation time (newest first).
    usort($checkpoints, function (Checkpoint $a, Checkpoint $b): int {
      return $b->createdAt <=> $a->createdAt;
    });

    // Apply limit.
    return array_slice($checkpoints, 0, $limit);
  }

  /**
   * {@inheritdoc}
   */
  public function getLatest(string $threadId): ?Checkpoint {
    $checkpoints = $this->list($threadId, 1);
    return $checkpoints[0] ?? NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function delete(string $checkpointId): bool {
    if (!isset(self::$storage[$checkpointId])) {
      return FALSE;
    }

    $checkpoint = self::$storage[$checkpointId];
    $threadId = $checkpoint->threadId;

    // Remove from storage.
    unset(self::$storage[$checkpointId]);

    // Remove from thread index.
    if (isset(self::$threadIndex[$threadId])) {
      $index = array_search($checkpointId, self::$threadIndex[$threadId], TRUE);
      if ($index !== FALSE) {
        unset(self::$threadIndex[$threadId][$index]);
        self::$threadIndex[$threadId] = array_values(self::$threadIndex[$threadId]);
      }
    }

    $this->logger->debug("Memory checkpointer: deleted checkpoint @id", [
      "@id" => $checkpointId,
    ]);

    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function deleteThread(string $threadId): int {
    $checkpointIds = self::$threadIndex[$threadId] ?? [];
    $count = 0;

    foreach ($checkpointIds as $id) {
      if (isset(self::$storage[$id])) {
        unset(self::$storage[$id]);
        $count++;
      }
    }

    unset(self::$threadIndex[$threadId]);

    $this->logger->debug("Memory checkpointer: deleted @count checkpoints for thread @thread", [
      "@count" => $count,
      "@thread" => $threadId,
    ]);

    return $count;
  }

  /**
   * {@inheritdoc}
   */
  public function clear(): int {
    $count = count(self::$storage);

    self::$storage = [];
    self::$threadIndex = [];

    $this->logger->info("Memory checkpointer: cleared all @count checkpoints", [
      "@count" => $count,
    ]);

    return $count;
  }

}
