<?php

declare(strict_types=1);

namespace Drupal\flowdrop_memory\Plugin\MemoryBackend;

use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flowdrop_memory\Attribute\MemoryBackend;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Drupal Cache API-backed memory backend.
 *
 * Stores values in a configurable Drupal cache bin. Supports TTL. Values
 * persist across requests but may be evicted by cache clears.
 *
 * A scope-level key index entry is maintained alongside each stored value so
 * that keys() and clearScope() can enumerate entries. The index is stored as
 * a separate cache entry; under concurrent writes there is a small window of
 * inconsistency (a key may appear in the index but already be expired). Reads
 * and writes are always individually consistent.
 *
 * Configuration (set via flowdrop_memory.settings backend_config.cached):
 * - cache_bin: The Drupal cache bin name (default: "default").
 *
 * @internal
 */
#[MemoryBackend(
  id: 'cached',
  label: new TranslatableMarkup('Cached (Drupal Cache API)'),
  description: 'Stores values in a Drupal cache bin. Supports TTL. Values persist across requests but may be evicted by cache clears.',
  persistent: TRUE,
  supports_ttl: TRUE,
)]
class CachedMemoryBackend extends AbstractMemoryBackend {

  /**
   * The cache backend.
   */
  private CacheBackendInterface $cache;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
    $instance = new static($configuration, $plugin_id, $plugin_definition);
    $bin = $configuration['cache_bin'] ?? 'default';
    $instance->cache = $container->get('cache.' . $bin);
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function get(string $scope, string $scope_id, string $key, mixed $default = NULL): mixed {
    $cached = $this->cache->get($this->buildStorageKey($scope, $scope_id, $key));
    return $cached !== FALSE ? $cached->data : $default;
  }

  /**
   * {@inheritdoc}
   */
  public function set(string $scope, string $scope_id, string $key, mixed $value, ?int $ttl = NULL): void {
    $expire = $ttl !== NULL && $ttl > 0
      ? (time() + $ttl)
      : CacheBackendInterface::CACHE_PERMANENT;
    $this->cache->set($this->buildStorageKey($scope, $scope_id, $key), $value, $expire);
    $this->addKeyToIndex($scope, $scope_id, $key);
  }

  /**
   * {@inheritdoc}
   */
  public function delete(string $scope, string $scope_id, string $key): void {
    $this->cache->delete($this->buildStorageKey($scope, $scope_id, $key));
    $this->removeKeyFromIndex($scope, $scope_id, $key);
  }

  /**
   * {@inheritdoc}
   */
  public function has(string $scope, string $scope_id, string $key): bool {
    return $this->cache->get($this->buildStorageKey($scope, $scope_id, $key)) !== FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function keys(string $scope, string $scope_id): array {
    $indexKey = $this->buildIndexKey($scope, $scope_id);
    $cached = $this->cache->get($indexKey);
    if ($cached === FALSE) {
      return [];
    }
    return array_values((array) $cached->data);
  }

  /**
   * {@inheritdoc}
   */
  public function clearScope(string $scope, string $scope_id): void {
    foreach ($this->keys($scope, $scope_id) as $key) {
      $this->cache->delete($this->buildStorageKey($scope, $scope_id, $key));
    }
    $this->cache->delete($this->buildIndexKey($scope, $scope_id));
  }

  /**
   * Returns the cache key used to store the scope key index.
   */
  private function buildIndexKey(string $scope, string $scope_id): string {
    return $this->buildScopePrefix($scope, $scope_id) . ':__index__';
  }

  /**
   * Adds a key to the scope index.
   */
  private function addKeyToIndex(string $scope, string $scope_id, string $key): void {
    $indexKey = $this->buildIndexKey($scope, $scope_id);
    $cached = $this->cache->get($indexKey);
    $keys = $cached !== FALSE ? (array) $cached->data : [];
    if (!in_array($key, $keys, TRUE)) {
      $keys[] = $key;
      $this->cache->set($indexKey, $keys, CacheBackendInterface::CACHE_PERMANENT);
    }
  }

  /**
   * Removes a key from the scope index.
   */
  private function removeKeyFromIndex(string $scope, string $scope_id, string $key): void {
    $indexKey = $this->buildIndexKey($scope, $scope_id);
    $cached = $this->cache->get($indexKey);
    if ($cached === FALSE) {
      return;
    }
    $keys = array_values(array_filter((array) $cached->data, fn($k) => $k !== $key));
    $this->cache->set($indexKey, $keys, CacheBackendInterface::CACHE_PERMANENT);
  }

}
