<?php
namespace nur\sery\db\cache;

use nur\sery\cl;
use nur\sery\db\CapacitorChannel;
use nur\sery\php\time\DateTime;
use nur\sery\php\time\Delay;

class CacheChannel extends CapacitorChannel {
  /** @var int durée de vie par défaut du cache */
  const DURATION = "1D"; // jusqu'au lendemain

  const INCLUDES = null;

  const EXCLUDES = null;

  const COLUMN_DEFINITIONS = [
    "group_id" => "varchar(64) not null",
    "id" => "varchar(64) not null",
    "date_start" => "datetime",
    "duration_" => "text",
    "primary key (group_id, id)",
  ];

  static function get_cache_ids($id): array {
    if (is_array($id)) {
      $keys = array_keys($id);
      if (array_key_exists("group_id", $id)) $groupIdKey = "group_id";
      else $groupIdKey = $keys[1] ?? null;
      $groupId = $id[$groupIdKey] ?? "";
      if (array_key_exists("id", $id)) $idKey = "id";
      else $idKey = $keys[0] ?? null;
      $id = $id[$idKey] ?? "";
    } else {
      $groupId = "";
    }
    if (preg_match('/^(.*\\\\)?([^\\\\]+)$/', $groupId, $ms)) {
      # si le groupe est une classe, faire un hash du package pour limiter la
      # longueur du groupe
      [$package, $groupId] = [$ms[1], $ms[2]];
      $package = substr(md5($package), 0, 4);
      $groupId = "${groupId}_$package";
    }
    return ["group_id" => $groupId, "id" => $id];
  }

  function __construct(?string $duration=null, ?string $name=null) {
    parent::__construct($name);
    $this->duration = $duration ?? static::DURATION;
    $this->includes = static::INCLUDES;
    $this->excludes = static::EXCLUDES;
  }

  protected string $duration;

  protected ?array $includes;

  protected ?array $excludes;

  function getItemValues($item): ?array {
    return cl::merge(self::get_cache_ids($item), [
      "item" => null,
    ]);
  }

  function onCreate($item, array $values, ?array $alwaysNull, ?string $duration=null): ?array {
    $now = new DateTime();
    $duration ??= $this->duration;
    return [
      "date_start" => $now,
      "duration" => new Delay($duration, $now),
    ];
  }

  function onUpdate($item, array $values, array $pvalues, ?string $duration=null): ?array {
    $now = new DateTime();
    $duration ??= $this->duration;
    return [
      "date_start" => $now,
      "duration" => new Delay($duration, $now),
    ];
  }

  function shouldUpdate($id, bool $noCache=false): bool {
    if ($noCache) return true;

    $cacheIds = self::get_cache_ids($id);
    $groupId = $cacheIds["group_id"];
    if ($groupId) {
      $includes = $this->includes;
      $shouldInclude = $includes !== null && in_array($groupId, $includes);
      $excludes = $this->excludes;
      $shouldExclude = $excludes !== null && in_array($groupId, $excludes);
      if (!$shouldInclude || $shouldExclude) return true;
    }

    $found = false;
    $expired = false;
    $this->each($cacheIds,
      function($item, $values) use (&$found, &$expired) {
        $found = true;
        $expired = $values["duration"]->isElapsed();
      });
    return !$found || $expired;
  }

  function setCached($id, ?string $duration=null): void {
    $cacheIds = self::get_cache_ids($id);
    $this->charge($cacheIds, null, [$duration]);
  }

  function resetCached($id) {
    $cacheIds = self::get_cache_ids($id);
    $this->delete($cacheIds);
  }
}
