"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.toScoped = exports.releaseOwner = exports.refreshing = exports.pending = exports.makeWith = exports.makeCacheState = exports.make = exports.initialCacheState = exports.complete = exports.ScopedCacheTypeId = void 0;
var Context = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/data/Context"));
var Data = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/data/Data"));
var Debug = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/data/Debug"));
var Equal = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/data/Equal"));
var HashSet = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/data/HashSet"));
var MutableHashMap = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/data/MutableHashMap"));
var MutableQueue = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/data/MutableQueue"));
var MutableRef = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/data/MutableRef"));
var Option = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/data/Option"));
var Exit = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/io/Exit"));
var _cache = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/io/internal_effect_untraced/cache"));
var core = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/io/internal_effect_untraced/core"));
var effect = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/io/internal_effect_untraced/effect"));
var fiberRuntime = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/io/internal_effect_untraced/fiberRuntime"));
var Scope = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/io/Scope"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
var _a;
/** @internal */
const makeCacheState = (map, keys, accesses, updating, hits, misses) => ({
  map,
  keys,
  accesses,
  updating,
  hits,
  misses
});
/**
 * Constructs an initial cache state.
 *
 * @internal
 */
exports.makeCacheState = makeCacheState;
const initialCacheState = () => makeCacheState(MutableHashMap.empty(), _cache.makeKeySet(), MutableQueue.unbounded(), MutableRef.make(false), 0, 0);
/** @internal */
exports.initialCacheState = initialCacheState;
const complete = (key, exit, ownerCount, entryStats, timeToLive) => Data.struct({
  _tag: "Complete",
  key,
  exit,
  ownerCount,
  entryStats,
  timeToLive
});
/** @internal */
exports.complete = complete;
const pending = (key, scoped) => Data.struct({
  _tag: "Pending",
  key,
  scoped
});
/** @internal */
exports.pending = pending;
const refreshing = (scoped, complete) => Data.struct({
  _tag: "Refreshing",
  scoped,
  complete
});
/** @internal */
exports.refreshing = refreshing;
const toScoped = self => Exit.matchEffect(self.exit, cause => core.done(Exit.failCause(cause)), ([value]) => fiberRuntime.acquireRelease(core.as(core.sync(() => MutableRef.incrementAndGet(self.ownerCount)), value), () => releaseOwner(self)));
/** @internal */
exports.toScoped = toScoped;
const releaseOwner = self => Exit.matchEffect(self.exit, () => core.unit(), ([, finalizer]) => core.flatMap(core.sync(() => MutableRef.decrementAndGet(self.ownerCount)), numOwner => effect.when(finalizer(Exit.unit()), () => numOwner === 0)));
/** @internal */
exports.releaseOwner = releaseOwner;
const ScopedCacheSymbolKey = "@effect/io/ScopedCache";
/** @internal */
const ScopedCacheTypeId = /*#__PURE__*/Symbol.for(ScopedCacheSymbolKey);
exports.ScopedCacheTypeId = ScopedCacheTypeId;
const scopedCacheVariance = {
  _Key: _ => _,
  _Error: _ => _,
  _Value: _ => _
};
class ScopedCacheImpl {
  constructor(capacity, scopedLookup, clock, timeToLive, context) {
    this.capacity = capacity;
    this.scopedLookup = scopedLookup;
    this.clock = clock;
    this.timeToLive = timeToLive;
    this.context = context;
    this[_a] = scopedCacheVariance;
    this.cacheState = initialCacheState();
  }
  cacheStats() {
    return Debug.bodyWithTrace(trace => core.sync(() => _cache.makeCacheStats(this.cacheState.hits, this.cacheState.misses, MutableHashMap.size(this.cacheState.map))).traced(trace));
  }
  getOption(key) {
    return Debug.bodyWithTrace(trace => core.suspend(() => Option.match(MutableHashMap.get(this.cacheState.map, key), () => effect.succeedNone(), value => core.flatten(this.resolveMapValue(value)))).traced(trace));
  }
  getOptionComplete(key) {
    return Debug.bodyWithTrace(trace => core.suspend(() => Option.match(MutableHashMap.get(this.cacheState.map, key), () => effect.succeedNone(), value => core.flatten(this.resolveMapValue(value, true)))).traced(trace));
  }
  contains(key) {
    return Debug.bodyWithTrace(trace => core.sync(() => MutableHashMap.has(this.cacheState.map, key)).traced(trace));
  }
  entryStats(key) {
    return Debug.bodyWithTrace(trace => core.sync(() => {
      const value = Option.getOrUndefined(MutableHashMap.get(this.cacheState.map, key));
      if (value === undefined) {
        return Option.none();
      }
      switch (value._tag) {
        case "Complete":
          {
            return Option.some(_cache.makeEntryStats(value.entryStats.loadedMillis));
          }
        case "Pending":
          {
            return Option.none();
          }
        case "Refreshing":
          {
            return Option.some(_cache.makeEntryStats(value.complete.entryStats.loadedMillis));
          }
      }
    }).traced(trace));
  }
  get(key) {
    return Debug.bodyWithTrace(trace => core.flatten(core.flatMap(lookupValue => core.suspend(() => {
      let k = undefined;
      let value = Option.getOrUndefined(MutableHashMap.get(this.cacheState.map, key));
      if (value === undefined) {
        k = _cache.makeMapKey(key);
        if (MutableHashMap.has(this.cacheState.map, key)) {
          value = Option.getOrUndefined(MutableHashMap.get(this.cacheState.map, key));
        } else {
          MutableHashMap.set(this.cacheState.map, key, pending(k, lookupValue));
        }
      }
      if (value === undefined) {
        this.trackMiss();
        return core.zipRight(this.ensureMapSizeNotExceeded(k), lookupValue);
      }
      return core.map(this.resolveMapValue(value), effect.someOrElseEffect(() => {
        const val = value;
        const current = Option.getOrUndefined(MutableHashMap.get(this.cacheState.map, key));
        if (Equal.equals(current, value)) {
          MutableHashMap.remove(this.cacheState.map, key);
        }
        return core.zipRight(this.get(key))(core.zipRight(releaseOwner(val))(this.ensureMapSizeNotExceeded(val.key)));
      }));
    }))(effect.memoize(this.lookupValueOf(key)))).traced(trace));
  }
  invalidate(key) {
    return Debug.bodyWithTrace(trace => core.suspend(() => {
      if (MutableHashMap.has(this.cacheState.map, key)) {
        const mapValue = Option.getOrUndefined(MutableHashMap.get(this.cacheState.map, key));
        MutableHashMap.remove(this.cacheState.map, key);
        switch (mapValue._tag) {
          case "Complete":
            {
              return releaseOwner(mapValue);
            }
          case "Pending":
            {
              return core.unit();
            }
          case "Refreshing":
            {
              return releaseOwner(mapValue.complete);
            }
        }
      }
      return core.unit();
    }).traced(trace));
  }
  invalidateAll() {
    return Debug.bodyWithTrace(trace => fiberRuntime.forEachParDiscard(HashSet.fromIterable(Array.from(this.cacheState.map).map(([key]) => key)), key => this.invalidate(key)).traced(trace));
  }
  refresh(key) {
    return Debug.bodyWithTrace(trace => core.flatMap(scoped => {
      let value = Option.getOrUndefined(MutableHashMap.get(this.cacheState.map, key));
      let newKey = undefined;
      if (value === undefined) {
        newKey = _cache.makeMapKey(key);
        if (MutableHashMap.has(this.cacheState.map, key)) {
          value = Option.getOrUndefined(MutableHashMap.get(this.cacheState.map, key));
        } else {
          MutableHashMap.set(this.cacheState.map, key, pending(newKey, scoped));
        }
      }
      let finalScoped;
      if (value === undefined) {
        finalScoped = core.zipRight(this.ensureMapSizeNotExceeded(newKey), scoped);
      } else {
        switch (value._tag) {
          case "Complete":
            {
              if (this.hasExpired(value.timeToLive)) {
                finalScoped = core.succeed(this.get(key));
              } else {
                const current = Option.getOrUndefined(MutableHashMap.get(this.cacheState.map, key));
                if (Equal.equals(current, value)) {
                  const mapValue = refreshing(scoped, value);
                  MutableHashMap.set(this.cacheState.map, key, mapValue);
                  finalScoped = scoped;
                } else {
                  finalScoped = core.succeed(this.get(key));
                }
              }
              break;
            }
          case "Pending":
            {
              finalScoped = value.scoped;
              break;
            }
          case "Refreshing":
            {
              finalScoped = value.scoped;
              break;
            }
        }
      }
      return core.flatMap(finalScoped, s => fiberRuntime.scopedEffect(core.asUnit(s)));
    })(effect.memoize(this.lookupValueOf(key))).traced(trace));
  }
  size() {
    return Debug.bodyWithTrace(trace => core.sync(() => MutableHashMap.size(this.cacheState.map)).traced(trace));
  }
  resolveMapValue(value, ignorePending = false) {
    switch (value._tag) {
      case "Complete":
        {
          this.trackHit();
          if (this.hasExpired(value.timeToLive)) {
            return core.succeed(effect.succeedNone());
          }
          return core.as(this.ensureMapSizeNotExceeded(value.key), effect.asSome(toScoped(value)));
        }
      case "Pending":
        {
          this.trackHit();
          if (ignorePending) {
            return core.succeed(effect.succeedNone());
          }
          return core.zipRight(this.ensureMapSizeNotExceeded(value.key), core.map(value.scoped, effect.asSome));
        }
      case "Refreshing":
        {
          this.trackHit();
          if (this.hasExpired(value.complete.timeToLive)) {
            if (ignorePending) {
              return core.succeed(effect.succeedNone());
            }
            return core.zipRight(this.ensureMapSizeNotExceeded(value.complete.key), core.map(value.scoped, effect.asSome));
          }
          return core.as(this.ensureMapSizeNotExceeded(value.complete.key), effect.asSome(toScoped(value.complete)));
        }
    }
  }
  lookupValueOf(key) {
    return core.flatten(effect.memoize(core.flatMap(([exit, release]) => {
      const now = this.clock.unsafeCurrentTimeMillis();
      const expiredAt = now + this.timeToLive(exit).millis;
      switch (exit._tag) {
        case "Success":
          {
            const exitWithFinalizer = Exit.succeed([exit.value, release]);
            const completedResult = complete(_cache.makeMapKey(key), exitWithFinalizer, MutableRef.make(1), _cache.makeEntryStats(now), expiredAt);
            let previousValue = undefined;
            if (MutableHashMap.has(this.cacheState.map, key)) {
              previousValue = Option.getOrUndefined(MutableHashMap.get(this.cacheState.map, key));
            }
            MutableHashMap.set(this.cacheState.map, key, completedResult);
            return core.sync(() => core.flatten(core.as(this.cleanMapValue(previousValue), toScoped(completedResult))));
          }
        case "Failure":
          {
            const completedResult = complete(_cache.makeMapKey(key), exit, MutableRef.make(0), _cache.makeEntryStats(now), expiredAt);
            let previousValue = undefined;
            if (MutableHashMap.has(this.cacheState.map, key)) {
              previousValue = Option.getOrUndefined(MutableHashMap.get(this.cacheState.map, key));
            }
            MutableHashMap.set(this.cacheState.map, key, completedResult);
            return core.zipRight(release(exit), core.sync(() => core.flatten(core.as(this.cleanMapValue(previousValue), toScoped(completedResult)))));
          }
      }
    })(core.onInterrupt(core.flatMap(Scope.make(), scope => core.map(exit => [exit, exit => Scope.close(scope, exit)])(core.exit(core.provideContext(Context.add(Scope.Scope, scope)(this.context))(this.scopedLookup(key))))), () => core.sync(() => MutableHashMap.remove(this.cacheState.map, key))))));
  }
  hasExpired(timeToLive) {
    return this.clock.unsafeCurrentTimeMillis() > timeToLive;
  }
  trackHit() {
    this.cacheState.hits = this.cacheState.hits + 1;
  }
  trackMiss() {
    this.cacheState.misses = this.cacheState.misses + 1;
  }
  trackAccess(key) {
    const cleanedKeys = [];
    MutableQueue.offer(this.cacheState.accesses, key);
    if (MutableRef.compareAndSet(this.cacheState.updating, false, true)) {
      let loop = true;
      while (loop) {
        const key = MutableQueue.poll(this.cacheState.accesses, MutableQueue.EmptyMutableQueue);
        if (key === MutableQueue.EmptyMutableQueue) {
          loop = false;
        } else {
          this.cacheState.keys.add(key);
        }
      }
      let size = MutableHashMap.size(this.cacheState.map);
      loop = size > this.capacity;
      while (loop) {
        const key = this.cacheState.keys.remove();
        if (key === undefined) {
          loop = false;
        } else {
          if (MutableHashMap.has(this.cacheState.map, key.current)) {
            const removed = Option.getOrUndefined(MutableHashMap.get(this.cacheState.map, key.current));
            MutableHashMap.remove(this.cacheState.map, key.current);
            size = size - 1;
            cleanedKeys.push(removed);
            loop = size > this.capacity;
          }
        }
      }
      MutableRef.set(this.cacheState.updating, false);
    }
    return cleanedKeys;
  }
  cleanMapValue(mapValue) {
    if (mapValue === undefined) return core.unit();
    switch (mapValue._tag) {
      case "Complete":
        {
          return releaseOwner(mapValue);
        }
      case "Pending":
        {
          return core.unit();
        }
      case "Refreshing":
        {
          return releaseOwner(mapValue.complete);
        }
    }
  }
  ensureMapSizeNotExceeded(key) {
    return fiberRuntime.forEachParDiscard(this.trackAccess(key), cleanedMapValue => this.cleanMapValue(cleanedMapValue));
  }
}
_a = ScopedCacheTypeId;
/** @internal */
const make = /*#__PURE__*/Debug.methodWithTrace((trace, restore) => (capacity, timeToLive, lookup) => makeWith(capacity, restore(lookup), () => timeToLive).traced(trace));
/** @internal */
exports.make = make;
const makeWith = /*#__PURE__*/Debug.methodWithTrace((trace, restore) => (capacity, lookup, timeToLive) => core.flatMap(effect.clock(), clock => buildWith(capacity, restore(lookup), clock, restore(timeToLive))).traced(trace));
exports.makeWith = makeWith;
const buildWith = (capacity, scopedLookup, clock, timeToLive) => fiberRuntime.acquireRelease(core.flatMap(core.context(), context => core.sync(() => new ScopedCacheImpl(capacity, scopedLookup, clock, timeToLive, context))), cache => cache.invalidateAll());
//# sourceMappingURL=scopedCache.js.map