'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

const scripts = {
  increment: `
      local totalHits = redis.call("INCR", KEYS[1])
      local timeToExpire = redis.call("PTTL", KEYS[1])
      if timeToExpire <= 0 or ARGV[1] == "1"
      then
        redis.call("PEXPIRE", KEYS[1], tonumber(ARGV[2]))
        timeToExpire = tonumber(ARGV[2])
      end

      return { totalHits, timeToExpire }
		`.replaceAll(/^\s+/gm, "").trim(),
  get: `
      local totalHits = redis.call("GET", KEYS[1])
      local timeToExpire = redis.call("PTTL", KEYS[1])

      return { totalHits, timeToExpire }
		`.replaceAll(/^\s+/gm, "").trim()
};

const toInt = (input) => {
  if (typeof input === "number")
    return input;
  return Number.parseInt((input ?? "").toString(), 10);
};
const parseScriptResponse = (results) => {
  if (!Array.isArray(results))
    throw new TypeError("Expected result to be array of values");
  if (results.length !== 2)
    throw new Error(`Expected 2 replies, got ${results.length}`);
  const totalHits = results[0] === false ? 0 : toInt(results[0]);
  const timeToExpire = toInt(results[1]);
  const resetTime = new Date(Date.now() + timeToExpire);
  return { totalHits, resetTime };
};
class RedisStore {
  /**
   * The function used to send raw commands to Redis.
   */
  sendCommand;
  /**
   * The text to prepend to the key in Redis.
   */
  prefix;
  /**
   * Whether to reset the expiry for a particular key whenever its hit count
   * changes.
   */
  resetExpiryOnChange;
  /**
   * Stores the loaded SHA1s of the LUA scripts used for executing the increment
   * and get key operations.
   */
  incrementScriptSha;
  getScriptSha;
  /**
   * The number of milliseconds to remember that user's requests.
   */
  windowMs;
  /**
   * @constructor for `RedisStore`.
   *
   * @param options {Options} - The configuration options for the store.
   */
  constructor(options) {
    this.sendCommand = options.sendCommand;
    this.prefix = options.prefix ?? "rl:";
    this.resetExpiryOnChange = options.resetExpiryOnChange ?? false;
    this.incrementScriptSha = this.loadIncrementScript();
    this.getScriptSha = this.loadGetScript();
  }
  /**
   * Loads the script used to increment a client's hit count.
   */
  async loadIncrementScript() {
    const result = await this.sendCommand("SCRIPT", "LOAD", scripts.increment);
    if (typeof result !== "string") {
      throw new TypeError("unexpected reply from redis client");
    }
    return result;
  }
  /**
   * Loads the script used to fetch a client's hit count and expiry time.
   */
  async loadGetScript() {
    const result = await this.sendCommand("SCRIPT", "LOAD", scripts.get);
    if (typeof result !== "string") {
      throw new TypeError("unexpected reply from redis client");
    }
    return result;
  }
  /**
   * Runs the increment command, and retries it if the script is not loaded.
   */
  async retryableIncrement(key) {
    const evalCommand = async () => this.sendCommand(
      "EVALSHA",
      await this.incrementScriptSha,
      "1",
      this.prefixKey(key),
      this.resetExpiryOnChange ? "1" : "0",
      this.windowMs.toString()
    );
    try {
      const result = await evalCommand();
      return result;
    } catch {
      this.incrementScriptSha = this.loadIncrementScript();
      return evalCommand();
    }
  }
  /**
   * Method to prefix the keys with the given text.
   *
   * @param key {string} - The key.
   *
   * @returns {string} - The text + the key.
   */
  prefixKey(key) {
    return `${this.prefix}${key}`;
  }
  /**
   * Method that actually initializes the store.
   *
   * @param options {RateLimitConfiguration} - The options used to setup the middleware.
   */
  init(options) {
    this.windowMs = options.windowMs;
  }
  /**
   * Method to fetch a client's hit count and reset time.
   *
   * @param key {string} - The identifier for a client.
   *
   * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
   */
  async get(key) {
    const results = await this.sendCommand(
      "EVALSHA",
      await this.getScriptSha,
      "1",
      this.prefixKey(key)
    );
    return parseScriptResponse(results);
  }
  /**
   * Method to increment a client's hit counter.
   *
   * @param key {string} - The identifier for a client
   *
   * @returns {IncrementResponse} - The number of hits and reset time for that client
   */
  async increment(key) {
    const results = await this.retryableIncrement(key);
    return parseScriptResponse(results);
  }
  /**
   * Method to decrement a client's hit counter.
   *
   * @param key {string} - The identifier for a client
   */
  async decrement(key) {
    await this.sendCommand("DECR", this.prefixKey(key));
  }
  /**
   * Method to reset a client's hit counter.
   *
   * @param key {string} - The identifier for a client
   */
  async resetKey(key) {
    await this.sendCommand("DEL", this.prefixKey(key));
  }
}

exports.RedisStore = RedisStore;
exports["default"] = RedisStore;