"use strict"
var __createBinding =
  (this && this.__createBinding) ||
  (Object.create
    ? function (o, m, k, k2) {
        if (k2 === undefined) k2 = k
        Object.defineProperty(o, k2, {
          enumerable: true,
          get: function () {
            return m[k]
          }
        })
      }
    : function (o, m, k, k2) {
        if (k2 === undefined) k2 = k
        o[k2] = m[k]
      })
var __setModuleDefault =
  (this && this.__setModuleDefault) ||
  (Object.create
    ? function (o, v) {
        Object.defineProperty(o, "default", { enumerable: true, value: v })
      }
    : function (o, v) {
        o["default"] = v
      })
var __importStar =
  (this && this.__importStar) ||
  function (mod) {
    if (mod && mod.__esModule) return mod
    var result = {}
    if (mod != null)
      for (var k in mod)
        if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k))
          __createBinding(result, mod, k)
    __setModuleDefault(result, mod)
    return result
  }
var _a
Object.defineProperty(exports, "__esModule", { value: true })
exports.Empty =
  exports.MemoMap =
  exports.makeMemoMap =
  exports.build =
  exports.scope =
  exports.LayerZipWithSeq =
  exports.LayerAllSeq =
  exports.LayerAllPar =
  exports.LayerZipWithPar =
  exports.LayerSuspend =
  exports.LayerManaged =
  exports.LayerFresh =
  exports.LayerChain =
  exports.LayerMap =
  exports.LayerFold =
  exports.provideLayer =
  exports.provideLayer_ =
  exports.provideSomeLayer_ =
  exports.provideSomeLayer =
  exports.Layer =
  exports.hashSym =
  exports.compose =
  exports.compose_ =
  exports.usingAnd =
  exports.using =
  exports.fold =
  exports.and =
  exports.and_ =
  exports.identity =
  exports.fromRawManaged =
  exports.fromRawFunctionM =
  exports.fromRawFunction =
  exports.fromRawEffect =
    void 0
const Chunk = __importStar(require("../Collections/Immutable/Chunk"))
const Map_1 = require("../Collections/Immutable/Map")
const Tp = __importStar(require("../Collections/Immutable/Tuple"))
const commons_1 = require("../Effect/commons")
const ExecutionStrategy_1 = require("../Effect/ExecutionStrategy")
const Function_1 = require("../Function")
const environment_1 = require("../Managed/methods/environment")
const add = __importStar(require("../Managed/ReleaseMap/add"))
const Finalizer = __importStar(require("../Managed/ReleaseMap/finalizer"))
const makeReleaseMap = __importStar(require("../Managed/ReleaseMap/makeReleaseMap"))
const releaseAll = __importStar(require("../Managed/ReleaseMap/releaseAll"))
const P = __importStar(require("../Promise"))
const R = __importStar(require("../Ref"))
const RM = __importStar(require("../RefM"))
const AtomicReference_1 = require("../Support/AtomicReference")
const T = __importStar(require("./deps-effect"))
const M = __importStar(require("./deps-managed"))
/**
 * Creates a layer from an effect
 */
function fromRawEffect(resource) {
  return new LayerManaged(M.fromEffect(resource))
}
exports.fromRawEffect = fromRawEffect
/**
 * Creates a layer from a function
 */
function fromRawFunction(f) {
  return fromRawEffect(T.access(f))
}
exports.fromRawFunction = fromRawFunction
/**
 * Creates a layer from an effectful function
 */
function fromRawFunctionM(f) {
  return fromRawEffect(T.accessM(f))
}
exports.fromRawFunctionM = fromRawFunctionM
/**
 * Creates a layer from a managed environment
 */
function fromRawManaged(resource) {
  return new LayerManaged(resource)
}
exports.fromRawManaged = fromRawManaged
/**
 * Constructs a layer that passes along the specified environment as an
 * output.
 */
function identity() {
  return fromRawManaged((0, environment_1.environment)())
}
exports.identity = identity
/**
 * Merge two Layers in parallel without providing any data to each other
 *
 * @param self - first Layer to combine
 * @param that - second Layer to combine
 */
function and_(self, that) {
  return new LayerZipWithPar(self, that, (l, r) => ({ ...l, ...r }))
}
exports.and_ = and_
/**
 * Merge two Layers in parallel without providing any data to each other
 *
 * @param that - second Layer to combine
 * @param self - first Layer to combine
 */
function and(that) {
  return (self) => new LayerZipWithPar(self, that, (l, r) => ({ ...l, ...r }))
}
exports.and = and
/**
 * Feeds the error or output services of this layer into the input of either
 * the specified `failure` or `success` layers, resulting in a new layer with
 * the inputs of this layer, and the error or outputs of the specified layer.
 */
function fold(self) {
  return (failure) => (success) => new LayerFold(self, failure, success)
}
exports.fold = fold
/**
 * Use `from` to partially provide environment into `to`
 */
function using(from) {
  return (to) => compose_(from["+++"](identity()), to)
}
exports.using = using
/**
 * Use `from` to partially provide environment into `to` and merge both
 */
function usingAnd(from) {
  return (to) => compose_(from["+++"](identity()), to["+++"](identity()))
}
exports.usingAnd = usingAnd
/**
 * Compose layers
 */
function compose_(from, to) {
  return fold(from)(fromRawFunctionM((_) => T.halt(_.get(1))))(to)
}
exports.compose_ = compose_
/**
 * Compose layers
 */
function compose(to) {
  return (from) => compose_(from, to)
}
exports.compose = compose
exports.hashSym = Symbol()
class Layer {
  constructor() {
    this[_a] = new AtomicReference_1.AtomicReference(Symbol())
  }
  /**
   * Set the hash key for memoization
   */
  setKey(hash) {
    this[exports.hashSym].set(hash)
    return this
  }
  [((_a = exports.hashSym), "_I")]() {
    return this
  }
  /**
   * Use that Layer to provide data to this
   */
  ["<=<"](that) {
    return that[">=>"](this)
  }
  /**
   * Use this Layer to provide data to that
   */
  [">=>"](that) {
    return compose_(this, that)
  }
  /**
   * Use that Layer to partially provide data to this
   */
  ["<<<"](that) {
    return that[">>>"](this)
  }
  [">>>"](that) {
    return this["+++"](identity())[">=>"](that)
  }
  /**
   * Create a Layer with the data from both Layers, while providing the data from this to that
   */
  [">+>"](that) {
    return this[">>>"](that["+++"](identity()))
  }
  /**
   * Create a Layer with the data from both Layers, while providing the data from that to this
   */
  ["<+<"](that) {
    return that[">+>"](this)
  }
  /**
   * Combine both layers in parallel
   */
  ["+++"](from) {
    return and_(from, this)
  }
  /**
   * Use the layer to provide partial environment to an effect
   */
  use(effect) {
    return provideSomeLayer(this)(effect)
  }
  /**
   * Use the layer to provide the full environment to an effect
   */
  useAll(effect) {
    return provideLayer(this)(effect)
  }
  /**
   * Use the layer to provide the full environment to an effect
   */
  get useForever() {
    return provideLayer(this)(T.never)
  }
}
exports.Layer = Layer
/**
 * Provides a layer to the given effect
 */
function provideSomeLayer(layer) {
  return (self) => provideLayer_(self, layer["+++"](identity()))
}
exports.provideSomeLayer = provideSomeLayer
/**
 * Provides a layer to the given effect
 */
function provideSomeLayer_(self, layer) {
  return provideLayer_(self, layer["+++"](identity()))
}
exports.provideSomeLayer_ = provideSomeLayer_
/**
 * Provides a layer to the given effect
 */
function provideLayer_(self, layer) {
  return M.use_(build(layer), (p) => T.provideAll_(self, p))
}
exports.provideLayer_ = provideLayer_
/**
 * Provides a layer to the given effect
 */
function provideLayer(layer) {
  return (self) => provideLayer_(self, layer)
}
exports.provideLayer = provideLayer
class LayerFold extends Layer {
  constructor(self, failure, success) {
    super()
    this.self = self
    this.failure = failure
    this.success = success
    this._tag = "LayerFold"
  }
}
exports.LayerFold = LayerFold
class LayerMap extends Layer {
  constructor(self, f) {
    super()
    this.self = self
    this.f = f
    this._tag = "LayerMap"
  }
}
exports.LayerMap = LayerMap
class LayerChain extends Layer {
  constructor(self, f) {
    super()
    this.self = self
    this.f = f
    this._tag = "LayerChain"
  }
}
exports.LayerChain = LayerChain
class LayerFresh extends Layer {
  constructor(self) {
    super()
    this.self = self
    this._tag = "LayerFresh"
  }
}
exports.LayerFresh = LayerFresh
class LayerManaged extends Layer {
  constructor(self) {
    super()
    this.self = self
    this._tag = "LayerManaged"
  }
}
exports.LayerManaged = LayerManaged
class LayerSuspend extends Layer {
  constructor(self) {
    super()
    this.self = self
    this._tag = "LayerSuspend"
  }
}
exports.LayerSuspend = LayerSuspend
class LayerZipWithPar extends Layer {
  constructor(self, that, f) {
    super()
    this.self = self
    this.that = that
    this.f = f
    this._tag = "LayerZipWithPar"
  }
}
exports.LayerZipWithPar = LayerZipWithPar
class LayerAllPar extends Layer {
  constructor(layers) {
    super()
    this.layers = layers
    this._tag = "LayerAllPar"
  }
}
exports.LayerAllPar = LayerAllPar
class LayerAllSeq extends Layer {
  constructor(layers) {
    super()
    this.layers = layers
    this._tag = "LayerAllSeq"
  }
}
exports.LayerAllSeq = LayerAllSeq
class LayerZipWithSeq extends Layer {
  constructor(self, that, f) {
    super()
    this.self = self
    this.that = that
    this.f = f
    this._tag = "LayerZipWithSeq"
  }
}
exports.LayerZipWithSeq = LayerZipWithSeq
function scope(_) {
  const I = _._I()
  switch (I._tag) {
    case "LayerFresh": {
      return M.succeed(() => build(I.self))
    }
    case "LayerManaged": {
      return M.succeed(() => I.self)
    }
    case "LayerSuspend": {
      return M.succeed((memo) => memo.getOrElseMemoize(I.self()))
    }
    case "LayerMap": {
      return M.succeed((memo) => M.map_(memo.getOrElseMemoize(I.self), I.f))
    }
    case "LayerChain": {
      return M.succeed((memo) =>
        M.chain_(memo.getOrElseMemoize(I.self), (a) => memo.getOrElseMemoize(I.f(a)))
      )
    }
    case "LayerZipWithPar": {
      return M.succeed((memo) =>
        M.zipWithPar_(memo.getOrElseMemoize(I.self), memo.getOrElseMemoize(I.that), I.f)
      )
    }
    case "LayerZipWithSeq": {
      return M.succeed((memo) =>
        M.zipWith_(memo.getOrElseMemoize(I.self), memo.getOrElseMemoize(I.that), I.f)
      )
    }
    case "LayerAllPar": {
      return M.succeed((memo) => {
        return M.map_(
          M.forEachPar_(I.layers, memo.getOrElseMemoize),
          Chunk.reduce({}, (b, a) => ({ ...b, ...a }))
        )
      })
    }
    case "LayerAllSeq": {
      return M.succeed((memo) => {
        return M.map_(
          M.forEach_(I.layers, memo.getOrElseMemoize),
          Chunk.reduce({}, (b, a) => ({ ...b, ...a }))
        )
      })
    }
    case "LayerFold": {
      return M.succeed((memo) =>
        M.foldCauseM_(
          memo.getOrElseMemoize(I.self),
          (e) =>
            M.chain_(M.fromEffect(T.environment()), (r) =>
              M.provideSome_(memo.getOrElseMemoize(I.failure), () => Tp.tuple(r, e))
            ),
          (r) => M.provideAll_(memo.getOrElseMemoize(I.success), r)
        )
      )
    }
  }
}
exports.scope = scope
/**
 * Builds a layer into a managed value.
 */
function build(_) {
  return M.map_(
    M.bind_(
      M.bind_(
        M.bind_(M.do, "memoMap", () => M.fromEffect(makeMemoMap())),
        "run",
        () => scope(_)
      ),
      "value",
      ({ memoMap, run }) => run(memoMap)
    ),
    ({ value }) => value
  )
}
exports.build = build
/**
 * Creates a MemoMap
 */
function makeMemoMap() {
  return T.chain_(RM.makeRefM(new Map()), (r) => T.succeedWith(() => new MemoMap(r)))
}
exports.makeMemoMap = makeMemoMap
/**
 * A `MemoMap` memoizes dependencies.
 */
class MemoMap {
  constructor(ref) {
    this.ref = ref
    /**
     * Checks the memo map to see if a dependency exists. If it is, immediately
     * returns it. Otherwise, obtains the dependency, stores it in the memo map,
     * and adds a finalizer to the outer `Managed`.
     */
    this.getOrElseMemoize = (layer) => {
      return new M.Managed(
        T.flatten(
          RM.modify((m) => {
            const inMap = m.get(layer[exports.hashSym].get)
            if (inMap) {
              const {
                tuple: [acquire, release]
              } = inMap
              const cached = T.accessM(({ tuple: [_, rm] }) =>
                T.map_(
                  T.onExit_(acquire, (ex) => {
                    switch (ex._tag) {
                      case "Success": {
                        return add.add(release)(rm)
                      }
                      case "Failure": {
                        return T.unit
                      }
                    }
                  }),
                  (x) => Tp.tuple(release, x)
                )
              )
              return T.succeed(Tp.tuple(cached, m))
            } else {
              return T.map_(
                T.let_(
                  T.let_(
                    T.bind_(
                      T.bind_(
                        T.bind_(T.do, "observers", () => R.makeRef(0)),
                        "promise",
                        () => P.make()
                      ),
                      "finalizerRef",
                      () => R.makeRef(Finalizer.noopFinalizer)
                    ),
                    "resource",
                    ({ finalizerRef, observers, promise }) =>
                      T.uninterruptibleMask(({ restore }) =>
                        T.map_(
                          T.bind_(
                            T.bind_(
                              T.let_(
                                T.let_(
                                  T.bind_(T.do, "env", () => T.environment()),
                                  "a",
                                  ({
                                    env: {
                                      tuple: [a]
                                    }
                                  }) => a
                                ),
                                "outerReleaseMap",
                                ({
                                  env: {
                                    tuple: [_, outerReleaseMap]
                                  }
                                }) => outerReleaseMap
                              ),
                              "innerReleaseMap",
                              () => makeReleaseMap.makeReleaseMap
                            ),
                            "tp",
                            ({ a, innerReleaseMap, outerReleaseMap }) =>
                              restore(
                                T.chain_(
                                  T.result(
                                    T.provideAll_(
                                      M.chain_(scope(layer), (_) => _(this)).effect,
                                      Tp.tuple(a, innerReleaseMap)
                                    )
                                  ),
                                  (e) => {
                                    switch (e._tag) {
                                      case "Failure": {
                                        return T.chain_(
                                          T.chain_(P.halt(e.cause)(promise), () =>
                                            releaseAll.releaseAll(
                                              e,
                                              ExecutionStrategy_1.sequential
                                            )(innerReleaseMap)
                                          ),
                                          () => T.halt(e.cause)
                                        )
                                      }
                                      case "Success": {
                                        return T.map_(
                                          T.tap_(
                                            T.bind_(
                                              T.tap_(
                                                T.tap_(T.do, () =>
                                                  finalizerRef.set((e) =>
                                                    T.whenM_(
                                                      releaseAll.releaseAll(
                                                        e,
                                                        ExecutionStrategy_1.sequential
                                                      )(innerReleaseMap),
                                                      R.modify_(observers, (n) =>
                                                        Tp.tuple(n === 1, n - 1)
                                                      )
                                                    )
                                                  )
                                                ),
                                                () => R.update_(observers, (n) => n + 1)
                                              ),
                                              "outerFinalizer",
                                              () =>
                                                add.add((e) =>
                                                  T.chain_(finalizerRef.get, (f) =>
                                                    f(e)
                                                  )
                                                )(outerReleaseMap)
                                            ),
                                            () => P.succeed(e.value.get(1))(promise)
                                          ),
                                          ({ outerFinalizer }) =>
                                            Tp.tuple(outerFinalizer, e.value.get(1))
                                        )
                                      }
                                    }
                                  }
                                )
                              )
                          ),
                          ({ tp }) => tp
                        )
                      )
                  ),
                  "memoized",
                  ({ finalizerRef, observers, promise }) =>
                    Tp.tuple(
                      T.onExit_(P.await(promise), (e) => {
                        switch (e._tag) {
                          case "Failure": {
                            return T.unit
                          }
                          case "Success": {
                            return R.update_(observers, (n) => n + 1)
                          }
                        }
                      }),
                      (e) => T.chain_(finalizerRef.get, (f) => f(e))
                    )
                ),
                ({ memoized, resource }) =>
                  Tp.tuple(
                    resource,
                    (0, Map_1.insert)(layer[exports.hashSym].get, memoized)(m)
                  )
              )
            }
          })(this.ref)
        )
      )
    }
  }
}
exports.MemoMap = MemoMap
/**
 * Empty layer, useful for init cases
 */
exports.Empty = new LayerSuspend(() => identity())
//# sourceMappingURL=definitions.js.map
