// ets_tracing: off
import "../Operator";
import { empty, then } from "../Cause/cause";
/**
 * Ported from https://github.com/zio/zio/blob/master/core/shared/src/main/scala/zio/Scope.scala
 *
 * Copyright 2020 Michael Arnaldi and the Matechs Garage Contributors.
 */

import * as A from "../Collections/Immutable/Array";
import { cause } from "../Effect/cause";
import { succeed, succeedWith, suspend } from "../Effect/core";
import { map_ } from "../Effect/map";
import { uncause } from "../Effect/uncause";
import { zipWith_ } from "../Effect/zipWith";
import * as E from "../Either";
import { AtomicNumber } from "../Support/AtomicNumber";
import { AtomicReference } from "../Support/AtomicReference";
/**
 * Represents a key in a scope, which is associated with a single finalizer.
 */

export class Key {
  constructor(remove) {
    /**
     * Attempts to remove the finalizer associated with this key from the
     * scope. The returned effect will succeed with a boolean, which indicates
     * whether the attempt was successful. A value of `true` indicates the
     * finalizer will not be executed, while a value of `false` indicates the
     * finalizer was already executed.
     */
    this.remove = succeed(false);

    if (remove) {
      this.remove = remove;
    }
  }

  setRemove(remove) {
    this.remove = remove;
  }

}
/**
 * The global scope, which is entirely stateless. Finalizers added to the
 * global scope will never be executed (nor kept in memory).
 */

export class Global {
  constructor() {
    this._tag = "Global";
    this.unsafeEnsureResult = E.right(new Key(succeedWith(() => true)));
    this.ensureResult = succeedWith(() => this.unsafeEnsureResult);
    this.deny = this.deny.bind(this);
    this.ensure = this.ensure.bind(this);
    this.extend = this.extend.bind(this);
    this.unsafeEnsure = this.unsafeEnsure.bind(this);
    this.unsafeExtend = this.unsafeExtend.bind(this);
  }

  get closed() {
    return succeed(false);
  }

  deny(_key) {
    return succeed(true);
  }

  get empty() {
    return succeed(false);
  }

  ensure(_finalizer) {
    return this.ensureResult;
  }

  extend(that) {
    return succeedWith(() => this.unsafeExtend(that));
  }

  get open() {
    return map_(this.closed, c => !c);
  }

  get released() {
    return succeed(false);
  }

  unsafeEnsure(_finalizer) {
    return this.unsafeEnsureResult;
  }

  unsafeExtend(that) {
    switch (that._tag) {
      case "Global":
        return true;

      case "Local":
        return that.unsafeAddRef();
    }
  }

  unsafeDeny() {
    return true;
  }

}
export class OrderedFinalizer {
  constructor(order, finalizer) {
    this.order = order;
    this.finalizer = finalizer;
  }

}
const noCause = empty;
const noCauseEffect = /*#__PURE__*/succeed(noCause);
export class Local {
  constructor(finalizerCount, exitValue, references, finalizers) {
    this.finalizerCount = finalizerCount;
    this.exitValue = exitValue;
    this.references = references;
    this.finalizers = finalizers;
    this._tag = "Local";
  }

  get closed() {
    return succeedWith(() => this.unsafeClosed);
  }

  get open() {
    return map_(this.closed, c => !c);
  }

  deny(key) {
    return succeedWith(() => this.unsafeDeny(key));
  }

  get empty() {
    return succeedWith(() => this.finalizers.size === 0);
  }

  ensure(finalizer) {
    return succeedWith(() => this.unsafeEnsure(finalizer));
  }

  extend(that) {
    return succeedWith(() => this.unsafeExtend(that));
  }

  get released() {
    return succeedWith(() => this.unsafeReleased());
  }

  unsafeExtend(that) {
    if (this === that) {
      return true;
    }

    switch (that._tag) {
      case "Global":
        return true;

      case "Local":
        if (this.unsafeClosed && that.unsafeClosed) {
          that.unsafeAddRef();
          this.unsafeEnsure(_ => that.release);
          return true;
        } else {
          return false;
        }

    }
  }

  get release() {
    return suspend(() => {
      const result = this.unsafeRelease();

      if (result != null) {
        return result;
      } else {
        return succeed(false);
      }
    });
  }

  unsafeReleased() {
    return this.references.get <= 0;
  }

  unsafeEnsure(finalizer) {
    if (this.unsafeClosed) {
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      return E.left(this.exitValue.get);
    }

    const key = new Key();
    key.setRemove(this.deny(key));
    this.finalizers.set(key, new OrderedFinalizer(this.finalizerCount.incrementAndGet(), finalizer));
    return E.right(key);
  }

  unsafeAddRef() {
    if (this.unsafeClosed) {
      return false;
    }

    this.references.incrementAndGet();
    return true;
  }

  get unsafeClosed() {
    return this.exitValue.get != null;
  }

  unsafeDeny(key) {
    if (this.unsafeClosed) {
      return false;
    } else {
      return this.finalizers.delete(key);
    }
  }

  unsafeClose(a) {
    this.exitValue.compareAndSet(null, a);
    return this.unsafeRelease();
  }

  unsafeRelease() {
    if (this.references.decrementAndGet() === 0) {
      const totalSize = this.finalizers.size;

      if (totalSize === 0) {
        return null;
      }

      const array = Array.from(this.finalizers.values());
      const sorted = array.sort((l, r) => l == null ? -1 : r == null ? 1 : l.order - r.order);
      const a = this.exitValue.get;
      return uncause(A.reduce_(sorted, noCauseEffect, (acc, o) => o != null ? zipWith_(acc, cause(o.finalizer(a)), (a, b) => then(a, b)) : acc));
    } else {
      return null;
    }
  }

  get unsafeEmpty() {
    return this.finalizers.size === 0;
  }

}
/**
 * The global scope, which is entirely stateless. Finalizers added to the
 * global scope will never be executed (nor kept in memory).
 */

export const globalScope = /*#__PURE__*/new Global();
/**
 * A tuple that contains an open scope, together with a function that closes
 * the scope.
 */

export class Open {
  constructor(close, scope) {
    this.close = close;
    this.scope = scope;
  }

}
export function unsafeMakeScope() {
  const exitValue = new AtomicReference(null);
  const finalizers = new Map();
  const scope = new Local(new AtomicNumber(Number.MIN_SAFE_INTEGER), exitValue, new AtomicNumber(1), finalizers);
  return new Open(a => {
    return suspend(() => {
      const result = scope.unsafeClose(a);

      if (result != null) {
        return map_(result, () => true);
      } else {
        return succeed(false);
      }
    });
  }, scope);
}
export function makeScope() {
  return succeedWith(() => unsafeMakeScope());
}
//# sourceMappingURL=index.js.map