"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.touch = exports.make = exports.keys = exports.invalidate = exports.get = exports.TypeId = void 0;
var Context = _interopRequireWildcard(require("../Context.js"));
var Duration = _interopRequireWildcard(require("../Duration.js"));
var _Function = require("../Function.js");
var MutableHashMap = _interopRequireWildcard(require("../MutableHashMap.js"));
var _Pipeable = require("../Pipeable.js");
var coreEffect = _interopRequireWildcard(require("./core-effect.js"));
var core = _interopRequireWildcard(require("./core.js"));
var circular = _interopRequireWildcard(require("./effect/circular.js"));
var fiberRuntime = _interopRequireWildcard(require("./fiberRuntime.js"));
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
/** @internal */
const TypeId = exports.TypeId = /*#__PURE__*/Symbol.for("effect/RcMap");
const variance = {
  _K: _Function.identity,
  _A: _Function.identity,
  _E: _Function.identity
};
class RcMapImpl {
  lookup;
  context;
  scope;
  idleTimeToLive;
  capacity;
  [TypeId];
  state = {
    _tag: "Open",
    map: /*#__PURE__*/MutableHashMap.empty()
  };
  semaphore = /*#__PURE__*/circular.unsafeMakeSemaphore(1);
  constructor(lookup, context, scope, idleTimeToLive, capacity) {
    this.lookup = lookup;
    this.context = context;
    this.scope = scope;
    this.idleTimeToLive = idleTimeToLive;
    this.capacity = capacity;
    this[TypeId] = variance;
  }
  pipe() {
    return (0, _Pipeable.pipeArguments)(this, arguments);
  }
}
/** @internal */
const make = options => core.withFiberRuntime(fiber => {
  const context = fiber.getFiberRef(core.currentContext);
  const scope = Context.get(context, fiberRuntime.scopeTag);
  const self = new RcMapImpl(options.lookup, context, scope, options.idleTimeToLive ? Duration.decode(options.idleTimeToLive) : undefined, Math.max(options.capacity ?? Number.POSITIVE_INFINITY, 0));
  return core.as(scope.addFinalizer(() => core.suspend(() => {
    if (self.state._tag === "Closed") {
      return core.void;
    }
    const map = self.state.map;
    self.state = {
      _tag: "Closed"
    };
    return core.forEachSequentialDiscard(map, ([, entry]) => core.scopeClose(entry.scope, core.exitVoid)).pipe(core.tap(() => {
      MutableHashMap.clear(map);
    }), self.semaphore.withPermits(1));
  })), self);
});
/** @internal */
exports.make = make;
const get = exports.get = /*#__PURE__*/(0, _Function.dual)(2, (self_, key) => {
  const self = self_;
  return core.uninterruptibleMask(restore => getImpl(self, key, restore));
});
const getImpl = /*#__PURE__*/core.fnUntraced(function* (self, key, restore) {
  if (self.state._tag === "Closed") {
    return yield* core.interrupt;
  }
  const state = self.state;
  const o = MutableHashMap.get(state.map, key);
  let entry;
  if (o._tag === "Some") {
    entry = o.value;
    entry.refCount++;
  } else if (Number.isFinite(self.capacity) && MutableHashMap.size(self.state.map) >= self.capacity) {
    return yield* core.fail(new core.ExceededCapacityException(`RcMap attempted to exceed capacity of ${self.capacity}`));
  } else {
    entry = yield* self.semaphore.withPermits(1)(acquire(self, key, restore));
  }
  const scope = yield* fiberRuntime.scopeTag;
  yield* scope.addFinalizer(() => entry.finalizer);
  return yield* restore(core.deferredAwait(entry.deferred));
});
const acquire = /*#__PURE__*/core.fnUntraced(function* (self, key, restore) {
  const scope = yield* fiberRuntime.scopeMake();
  const deferred = yield* core.deferredMake();
  const acquire = self.lookup(key);
  const contextMap = new Map(self.context.unsafeMap);
  yield* restore(core.mapInputContext(acquire, inputContext => {
    inputContext.unsafeMap.forEach((value, key) => {
      contextMap.set(key, value);
    });
    contextMap.set(fiberRuntime.scopeTag.key, scope);
    return Context.unsafeMake(contextMap);
  })).pipe(core.exit, core.flatMap(exit => core.deferredDone(deferred, exit)), circular.forkIn(scope));
  const entry = {
    deferred,
    scope,
    finalizer: undefined,
    fiber: undefined,
    expiresAt: 0,
    refCount: 1
  };
  entry.finalizer = release(self, key, entry);
  if (self.state._tag === "Open") {
    MutableHashMap.set(self.state.map, key, entry);
  }
  return entry;
});
const release = (self, key, entry) => coreEffect.clockWith(clock => {
  entry.refCount--;
  if (entry.refCount > 0) {
    return core.void;
  } else if (self.state._tag === "Closed" || !MutableHashMap.has(self.state.map, key) || self.idleTimeToLive === undefined) {
    if (self.state._tag === "Open") {
      MutableHashMap.remove(self.state.map, key);
    }
    return core.scopeClose(entry.scope, core.exitVoid);
  }
  if (!Duration.isFinite(self.idleTimeToLive)) {
    return core.void;
  }
  entry.expiresAt = clock.unsafeCurrentTimeMillis() + Duration.toMillis(self.idleTimeToLive);
  if (entry.fiber) return core.void;
  return core.interruptibleMask(function loop(restore) {
    const now = clock.unsafeCurrentTimeMillis();
    const remaining = entry.expiresAt - now;
    if (remaining <= 0) {
      if (self.state._tag === "Closed" || entry.refCount > 0) return core.void;
      MutableHashMap.remove(self.state.map, key);
      return restore(core.scopeClose(entry.scope, core.exitVoid));
    }
    return core.flatMap(clock.sleep(Duration.millis(remaining)), () => loop(restore));
  }).pipe(fiberRuntime.ensuring(core.sync(() => {
    entry.fiber = undefined;
  })), circular.forkIn(self.scope), core.tap(fiber => {
    entry.fiber = fiber;
  }), self.semaphore.withPermits(1));
});
/** @internal */
const keys = self => {
  const impl = self;
  return core.suspend(() => impl.state._tag === "Closed" ? core.interrupt : core.succeed(MutableHashMap.keys(impl.state.map)));
};
/** @internal */
exports.keys = keys;
const invalidate = exports.invalidate = /*#__PURE__*/(0, _Function.dual)(2, /*#__PURE__*/core.fnUntraced(function* (self_, key) {
  const self = self_;
  if (self.state._tag === "Closed") return;
  const o = MutableHashMap.get(self.state.map, key);
  if (o._tag === "None") return;
  const entry = o.value;
  MutableHashMap.remove(self.state.map, key);
  if (entry.refCount > 0) return;
  yield* core.scopeClose(entry.scope, core.exitVoid);
  if (entry.fiber) yield* core.interruptFiber(entry.fiber);
}));
/** @internal */
const touch = exports.touch = /*#__PURE__*/(0, _Function.dual)(2, (self_, key) => coreEffect.clockWith(clock => {
  const self = self_;
  if (!self.idleTimeToLive || self.state._tag === "Closed") return core.void;
  const o = MutableHashMap.get(self.state.map, key);
  if (o._tag === "None") return core.void;
  o.value.expiresAt = clock.unsafeCurrentTimeMillis() + Duration.toMillis(self.idleTimeToLive);
  return core.void;
}));
//# sourceMappingURL=rcMap.js.map