"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
  }
Object.defineProperty(exports, "__esModule", { value: true })
exports.makeScope =
  exports.unsafeMakeScope =
  exports.Open =
  exports.globalScope =
  exports.Local =
  exports.OrderedFinalizer =
  exports.Global =
  exports.Key =
    void 0
// tracing: off
require("../Operator")
const cause_1 = require("../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.
 */
const A = __importStar(require("../Collections/Immutable/Array"))
const cause_2 = require("../Effect/cause")
const core_1 = require("../Effect/core")
const map_1 = require("../Effect/map")
const uncause_1 = require("../Effect/uncause")
const zipWith_1 = require("../Effect/zipWith")
const E = __importStar(require("../Either"))
const AtomicNumber_1 = require("../Support/AtomicNumber")
const AtomicReference_1 = require("../Support/AtomicReference")
/**
 * Represents a key in a scope, which is associated with a single finalizer.
 */
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 = (0, core_1.succeed)(false)
    if (remove) {
      this.remove = remove
    }
  }
  setRemove(remove) {
    this.remove = remove
  }
}
exports.Key = Key
/**
 * The global scope, which is entirely stateless. Finalizers added to the
 * global scope will never be executed (nor kept in memory).
 */
class Global {
  constructor() {
    this._tag = "Global"
    this.unsafeEnsureResult = E.right(new Key((0, core_1.succeedWith)(() => true)))
    this.ensureResult = (0, core_1.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 (0, core_1.succeed)(false)
  }
  deny(_key) {
    return (0, core_1.succeed)(true)
  }
  get empty() {
    return (0, core_1.succeed)(false)
  }
  ensure(_finalizer) {
    return this.ensureResult
  }
  extend(that) {
    return (0, core_1.succeedWith)(() => this.unsafeExtend(that))
  }
  get open() {
    return (0, map_1.map_)(this.closed, (c) => !c)
  }
  get released() {
    return (0, core_1.succeed)(false)
  }
  unsafeEnsure(_finalizer) {
    return this.unsafeEnsureResult
  }
  unsafeExtend(that) {
    switch (that._tag) {
      case "Global":
        return true
      case "Local":
        return that.unsafeAddRef()
    }
  }
  unsafeDeny() {
    return true
  }
}
exports.Global = Global
class OrderedFinalizer {
  constructor(order, finalizer) {
    this.order = order
    this.finalizer = finalizer
  }
}
exports.OrderedFinalizer = OrderedFinalizer
const noCause = cause_1.empty
const noCauseEffect = (0, core_1.succeed)(noCause)
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 (0, core_1.succeedWith)(() => this.unsafeClosed)
  }
  get open() {
    return (0, map_1.map_)(this.closed, (c) => !c)
  }
  deny(key) {
    return (0, core_1.succeedWith)(() => this.unsafeDeny(key))
  }
  get empty() {
    return (0, core_1.succeedWith)(() => this.finalizers.size === 0)
  }
  ensure(finalizer) {
    return (0, core_1.succeedWith)(() => this.unsafeEnsure(finalizer))
  }
  extend(that) {
    return (0, core_1.succeedWith)(() => this.unsafeExtend(that))
  }
  get released() {
    return (0, core_1.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 (0, core_1.suspend)(() => {
      const result = this.unsafeRelease()
      if (result != null) {
        return result
      } else {
        return (0, core_1.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 (0, uncause_1.uncause)(
        A.reduce_(sorted, noCauseEffect, (acc, o) =>
          o != null
            ? (0, zipWith_1.zipWith_)(acc, (0, cause_2.cause)(o.finalizer(a)), (a, b) =>
                (0, cause_1.then)(a, b)
              )
            : acc
        )
      )
    } else {
      return null
    }
  }
  get unsafeEmpty() {
    return this.finalizers.size === 0
  }
}
exports.Local = Local
/**
 * The global scope, which is entirely stateless. Finalizers added to the
 * global scope will never be executed (nor kept in memory).
 */
exports.globalScope = new Global()
/**
 * A tuple that contains an open scope, together with a function that closes
 * the scope.
 */
class Open {
  constructor(close, scope) {
    this.close = close
    this.scope = scope
  }
}
exports.Open = Open
function unsafeMakeScope() {
  const exitValue = new AtomicReference_1.AtomicReference(null)
  const finalizers = new Map()
  const scope = new Local(
    new AtomicNumber_1.AtomicNumber(Number.MIN_SAFE_INTEGER),
    exitValue,
    new AtomicNumber_1.AtomicNumber(1),
    finalizers
  )
  return new Open((a) => {
    return (0, core_1.suspend)(() => {
      const result = scope.unsafeClose(a)
      if (result != null) {
        return (0, map_1.map_)(result, () => true)
      } else {
        return (0, core_1.succeed)(false)
      }
    })
  }, scope)
}
exports.unsafeMakeScope = unsafeMakeScope
function makeScope() {
  return (0, core_1.succeedWith)(() => unsafeMakeScope())
}
exports.makeScope = makeScope
//# sourceMappingURL=index.js.map
