var _a;

import * as Chunk from "../Collections/Immutable/Chunk";
import { insert } from "../Collections/Immutable/Map";
import * as Tp from "../Collections/Immutable/Tuple";
import { _E, _RIn, _ROut } from "../Effect/commons";
import { sequential } from "../Effect/ExecutionStrategy";
import { pipe } from "../Function";
import { environment } from "../Managed/methods/environment";
import * as add from "../Managed/ReleaseMap/add";
import * as Finalizer from "../Managed/ReleaseMap/finalizer";
import * as makeReleaseMap from "../Managed/ReleaseMap/makeReleaseMap";
import * as releaseAll from "../Managed/ReleaseMap/releaseAll";
import * as P from "../Promise";
import * as R from "../Ref";
import * as RM from "../RefM";
import { AtomicReference } from "../Support/AtomicReference";
import * as T from "./deps-effect";
import * as M from "./deps-managed";
/**
 * Creates a layer from an effect
 */

export function fromRawEffect(resource) {
  return new LayerManaged(M.fromEffect(resource));
}
/**
 * Creates a layer from a function
 */

export function fromRawFunction(f) {
  return fromRawEffect(T.access(f));
}
/**
 * Creates a layer from an effectful function
 */

export function fromRawFunctionM(f) {
  return fromRawEffect(T.accessM(f));
}
/**
 * Creates a layer from a managed environment
 */

export function fromRawManaged(resource) {
  return new LayerManaged(resource);
}
/**
 * Constructs a layer that passes along the specified environment as an
 * output.
 */

export function identity() {
  return fromRawManaged(environment());
}
/**
 * Merge two Layers in parallel without providing any data to each other
 *
 * @param self - first Layer to combine
 * @param that - second Layer to combine
 */

export function and_(self, that) {
  return new LayerZipWithPar(self, that, (l, r) => ({ ...l,
    ...r
  }));
}
/**
 * Merge two Layers in parallel without providing any data to each other
 *
 * @param that - second Layer to combine
 * @param self - first Layer to combine
 */

export function and(that) {
  return self => new LayerZipWithPar(self, that, (l, r) => ({ ...l,
    ...r
  }));
}
/**
 * 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.
 */

export function fold(self) {
  return failure => success => new LayerFold(self, failure, success);
}
/**
 * Use `from` to partially provide environment into `to`
 */

export function using(from) {
  return to => compose_(from["+++"](identity()), to);
}
/**
 * Use `from` to partially provide environment into `to` and merge both
 */

export function usingAnd(from) {
  return to => compose_(from["+++"](identity()), to["+++"](identity()));
}
/**
 * Compose layers
 */

export function compose_(from, to) {
  return fold(from)(fromRawFunctionM(_ => T.halt(_.get(1))))(to);
}
/**
 * Compose layers
 */

export function compose(to) {
  return from => compose_(from, to);
}
export const hashSym = /*#__PURE__*/Symbol();
export class Layer {
  constructor() {
    this[_a] = new AtomicReference(Symbol());
  }
  /**
   * Set the hash key for memoization
   */


  setKey(hash) {
    this[hashSym].set(hash);
    return this;
  }

  [(_a = 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);
  }

}
/**
 * Provides a layer to the given effect
 */

export function provideSomeLayer(layer) {
  return self => provideLayer_(self, layer["+++"](identity()));
}
/**
 * Provides a layer to the given effect
 */

export function provideSomeLayer_(self, layer) {
  return provideLayer_(self, layer["+++"](identity()));
}
/**
 * Provides a layer to the given effect
 */

export function provideLayer_(self, layer) {
  return M.use_(build(layer), p => T.provideAll_(self, p));
}
/**
 * Provides a layer to the given effect
 */

export function provideLayer(layer) {
  return self => provideLayer_(self, layer);
}
export class LayerFold extends Layer {
  constructor(self, failure, success) {
    super();
    this.self = self;
    this.failure = failure;
    this.success = success;
    this._tag = "LayerFold";
  }

}
export class LayerMap extends Layer {
  constructor(self, f) {
    super();
    this.self = self;
    this.f = f;
    this._tag = "LayerMap";
  }

}
export class LayerChain extends Layer {
  constructor(self, f) {
    super();
    this.self = self;
    this.f = f;
    this._tag = "LayerChain";
  }

}
export class LayerFresh extends Layer {
  constructor(self) {
    super();
    this.self = self;
    this._tag = "LayerFresh";
  }

}
export class LayerManaged extends Layer {
  constructor(self) {
    super();
    this.self = self;
    this._tag = "LayerManaged";
  }

}
export class LayerSuspend extends Layer {
  constructor(self) {
    super();
    this.self = self;
    this._tag = "LayerSuspend";
  }

}
export class LayerZipWithPar extends Layer {
  constructor(self, that, f) {
    super();
    this.self = self;
    this.that = that;
    this.f = f;
    this._tag = "LayerZipWithPar";
  }

}
export class LayerAllPar extends Layer {
  constructor(layers) {
    super();
    this.layers = layers;
    this._tag = "LayerAllPar";
  }

}
export class LayerAllSeq extends Layer {
  constructor(layers) {
    super();
    this.layers = layers;
    this._tag = "LayerAllSeq";
  }

}
export class LayerZipWithSeq extends Layer {
  constructor(self, that, f) {
    super();
    this.self = self;
    this.that = that;
    this.f = f;
    this._tag = "LayerZipWithSeq";
  }

}
export 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)));
      }
  }
}
/**
 * Builds a layer into a managed value.
 */

export 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);
}
/**
 * Creates a MemoMap
 */

export function makeMemoMap() {
  return T.chain_(RM.makeRefM(new Map()), r => T.succeedWith(() => new MemoMap(r)));
}
/**
 * A `MemoMap` memoizes dependencies.
 */

export 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[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, 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, 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, insert(layer[hashSym].get, memoized)(m)));
        }
      })(this.ref)));
    };
  }

}
/**
 * Empty layer, useful for init cases
 */

export const Empty = /*#__PURE__*/new LayerSuspend(() => identity());
//# sourceMappingURL=definitions.js.map