"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, _b, _c, _d, _e, _f, _g
Object.defineProperty(exports, "__esModule", { value: true })
exports.equals =
  exports.isEmpty =
  exports.both =
  exports.then =
  exports.interrupt =
  exports.die =
  exports.traced =
  exports.fail =
  exports.Both =
  exports.Then =
  exports.Traced =
  exports.Interrupt =
  exports.Die =
  exports.Fail =
  exports.empty =
  exports.Empty =
  exports.isCause =
  exports.CauseSym =
    void 0
// tracing: off
const HS = __importStar(require("../Collections/Immutable/HashSet"))
const L = __importStar(require("../Collections/Immutable/List/core"))
const Tp = __importStar(require("../Collections/Immutable/Tuple"))
const id_1 = require("../Fiber/id")
const Function_1 = require("../Function")
const IO = __importStar(require("../IO"))
const O = __importStar(require("../Option"))
const Stack_1 = require("../Stack")
const St = __importStar(require("../Structural"))
exports.CauseSym = Symbol()
function isCause(self) {
  return typeof self === "object" && self != null && exports.CauseSym in self
}
exports.isCause = isCause
const _emptyHash = St.opt(St.randomInt())
class Empty {
  constructor() {
    this._tag = "Empty"
    this[_a] = exports.CauseSym
  }
  [((_a = exports.CauseSym), St.equalsSym)](that) {
    return isCause(that) && IO.run(this.equalsSafe(that))
  }
  get [St.hashSym]() {
    return _emptyHash
  }
  equalsSafe(that) {
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this
    return IO.gen(function* (_) {
      if (that._tag === "Empty") {
        return true
      } else if (that._tag === "Then") {
        return (
          (yield* _(self.equalsSafe(that.left))) &&
          (yield* _(self.equalsSafe(that.right)))
        )
      } else if (that._tag === "Both") {
        return (
          (yield* _(self.equalsSafe(that.left))) &&
          (yield* _(self.equalsSafe(that.right)))
        )
      } else {
        return false
      }
    })
  }
}
exports.Empty = Empty
exports.empty = new Empty()
class Fail {
  constructor(value) {
    this.value = value
    this._tag = "Fail"
    this[_b] = exports.CauseSym
  }
  [((_b = exports.CauseSym), St.equalsSym)](that) {
    return isCause(that) && IO.run(this.equalsSafe(that))
  }
  get [St.hashSym]() {
    return St.combineHash(St.hashString(this._tag), St.hash(this.value))
  }
  equalsSafe(that) {
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this
    return IO.gen(function* (_) {
      switch (that._tag) {
        case "Fail": {
          return St.equals(self.value, that.value)
        }
        case "Then": {
          return yield* _(sym(zero)(self, that))
        }
        case "Both": {
          return yield* _(sym(zero)(self, that))
        }
        case "Traced": {
          return yield* _(self.equalsSafe(that.cause))
        }
      }
      return false
    })
  }
}
exports.Fail = Fail
class Die {
  constructor(value) {
    this.value = value
    this._tag = "Die"
    this[_c] = exports.CauseSym
  }
  [((_c = exports.CauseSym), St.equalsSym)](that) {
    return isCause(that) && IO.run(this.equalsSafe(that))
  }
  get [St.hashSym]() {
    return St.combineHash(St.hashString(this._tag), St.hash(this.value))
  }
  equalsSafe(that) {
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this
    return IO.gen(function* (_) {
      switch (that._tag) {
        case "Die": {
          return St.equals(self.value, that.value)
        }
        case "Then": {
          return yield* _(sym(zero)(self, that))
        }
        case "Both": {
          return yield* _(sym(zero)(self, that))
        }
        case "Traced": {
          return yield* _(self.equalsSafe(that.cause))
        }
      }
      return false
    })
  }
}
exports.Die = Die
class Interrupt {
  constructor(fiberId) {
    this.fiberId = fiberId
    this._tag = "Interrupt"
    this[_d] = exports.CauseSym
  }
  [((_d = exports.CauseSym), St.equalsSym)](that) {
    return isCause(that) && IO.run(this.equalsSafe(that))
  }
  get [St.hashSym]() {
    return St.combineHash(St.hashString(this._tag), St.hash(this.fiberId))
  }
  equalsSafe(that) {
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this
    return IO.gen(function* (_) {
      switch (that._tag) {
        case "Interrupt": {
          return (0, id_1.equalsFiberID)(self.fiberId, that.fiberId)
        }
        case "Then": {
          return yield* _(sym(zero)(self, that))
        }
        case "Both": {
          return yield* _(sym(zero)(self, that))
        }
        case "Traced": {
          return yield* _(self.equalsSafe(that.cause))
        }
      }
      return false
    })
  }
}
exports.Interrupt = Interrupt
class Traced {
  constructor(cause, trace) {
    this.cause = cause
    this.trace = trace
    this._tag = "Traced"
    this[_e] = exports.CauseSym
  }
  [((_e = exports.CauseSym), St.equalsSym)](that) {
    return isCause(that) && IO.run(this.equalsSafe(that))
  }
  get [St.hashSym]() {
    return this.cause[St.hashSym]
  }
  equalsSafe(that) {
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this
    return IO.gen(function* (_) {
      if (that._tag === "Traced") {
        return yield* _(self.cause.equalsSafe(that.cause))
      }
      return yield* _(self.cause.equalsSafe(that))
    })
  }
}
exports.Traced = Traced
class Then {
  constructor(left, right) {
    this.left = left
    this.right = right
    this._tag = "Then"
    this[_f] = exports.CauseSym
  }
  [((_f = exports.CauseSym), St.equalsSym)](that) {
    return isCause(that) && IO.run(this.equalsSafe(that))
  }
  get [St.hashSym]() {
    return hashCode(this)
  }
  equalsSafe(that) {
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this
    return IO.gen(function* (_) {
      switch (that._tag) {
        case "Traced": {
          return yield* _(self.equalsSafe(that.cause))
        }
      }
      return (
        (yield* _(self.eq(that))) ||
        (yield* _(sym(associativeThen)(self, that))) ||
        (yield* _(sym(distributiveThen)(self, that))) ||
        (yield* _(sym(zero)(self, that)))
      )
    })
  }
  eq(that) {
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this
    if (that._tag === "Then") {
      return IO.gen(function* (_) {
        return (
          (yield* _(self.left.equalsSafe(that.left))) &&
          (yield* _(self.right.equalsSafe(that.right)))
        )
      })
    }
    return IO.succeed(false)
  }
}
exports.Then = Then
class Both {
  constructor(left, right) {
    this.left = left
    this.right = right
    this._tag = "Both"
    this[_g] = exports.CauseSym
  }
  [((_g = exports.CauseSym), St.equalsSym)](that) {
    return isCause(that) && IO.run(this.equalsSafe(that))
  }
  get [St.hashSym]() {
    return hashCode(this)
  }
  equalsSafe(that) {
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this
    return IO.gen(function* (_) {
      switch (that._tag) {
        case "Traced": {
          return yield* _(self.equalsSafe(that.cause))
        }
      }
      return (
        (yield* _(self.eq(that))) ||
        (yield* _(sym(associativeBoth)(self, that))) ||
        (yield* _(sym(distributiveBoth)(self, that))) ||
        (yield* _(commutativeBoth(self, that))) ||
        (yield* _(sym(zero)(self, that)))
      )
    })
  }
  eq(that) {
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this
    if (that._tag === "Both") {
      return IO.gen(function* (_) {
        return (
          (yield* _(self.left.equalsSafe(that.left))) &&
          (yield* _(self.right.equalsSafe(that.right)))
        )
      })
    }
    return IO.succeed(false)
  }
}
exports.Both = Both
function fail(value) {
  return new Fail(value)
}
exports.fail = fail
function traced(cause, trace) {
  if (
    L.isEmpty(trace.executionTrace) &&
    L.isEmpty(trace.stackTrace) &&
    O.isNone(trace.parentTrace)
  ) {
    return cause
  }
  return new Traced(cause, trace)
}
exports.traced = traced
function die(value) {
  return new Die(value)
}
exports.die = die
function interrupt(fiberId) {
  return new Interrupt(fiberId)
}
exports.interrupt = interrupt
function then(left, right) {
  return isEmpty(left) ? right : isEmpty(right) ? left : new Then(left, right)
}
exports.then = then
function both(left, right) {
  return isEmpty(left) ? right : isEmpty(right) ? left : new Both(left, right)
}
exports.both = both
/**
 * Determines if the `Cause` is empty.
 */
function isEmpty(cause) {
  if (
    cause._tag === "Empty" ||
    (cause._tag === "Traced" && cause.cause._tag === "Empty")
  ) {
    return true
  }
  let causes = undefined
  let current = cause
  while (current) {
    switch (current._tag) {
      case "Die": {
        return false
      }
      case "Fail": {
        return false
      }
      case "Interrupt": {
        return false
      }
      case "Then": {
        causes = new Stack_1.Stack(current.right, causes)
        current = current.left
        break
      }
      case "Both": {
        causes = new Stack_1.Stack(current.right, causes)
        current = current.left
        break
      }
      case "Traced": {
        current = current.cause
        break
      }
      default: {
        current = undefined
      }
    }
    if (!current && causes) {
      current = causes.value
      causes = causes.previous
    }
  }
  return true
}
exports.isEmpty = isEmpty
function associativeThen(self, that) {
  return IO.gen(function* (_) {
    if (
      self._tag === "Then" &&
      self.left._tag === "Then" &&
      that._tag === "Then" &&
      that.right._tag === "Then"
    ) {
      const al = self.left.left
      const bl = self.left.right
      const cl = self.right
      const ar = that.left
      const br = that.right.left
      const cr = that.right.right
      return (
        (yield* _(al.equalsSafe(ar))) &&
        (yield* _(bl.equalsSafe(br))) &&
        (yield* _(cl.equalsSafe(cr)))
      )
    }
    return false
  })
}
function distributiveThen(self, that) {
  return IO.gen(function* (_) {
    if (
      self._tag === "Then" &&
      self.right._tag === "Both" &&
      that._tag === "Both" &&
      that.left._tag === "Then" &&
      that.right._tag === "Then"
    ) {
      const al = self.left
      const bl = self.right.left
      const cl = self.right.right
      const ar1 = that.left.left
      const br = that.left.right
      const ar2 = that.right.left
      const cr = that.right.right
      if (
        (yield* _(ar1.equalsSafe(ar2))) &&
        (yield* _(al.equalsSafe(ar1))) &&
        (yield* _(bl.equalsSafe(br))) &&
        (yield* _(cl.equalsSafe(cr)))
      ) {
        return true
      }
    }
    if (
      self._tag === "Then" &&
      self.left._tag === "Both" &&
      that._tag === "Both" &&
      that.left._tag === "Then" &&
      that.right._tag === "Then"
    ) {
      const al = self.left.left
      const bl = self.left.right
      const cl = self.right
      const ar = that.left.left
      const cr1 = that.left.right
      const br = that.right.left
      const cr2 = that.right.right
      if (
        (yield* _(cr1.equalsSafe(cr2))) &&
        (yield* _(al.equalsSafe(ar))) &&
        (yield* _(bl.equalsSafe(br))) &&
        (yield* _(cl.equalsSafe(cr1)))
      ) {
        return true
      }
    }
    return false
  })
}
function associativeBoth(self, that) {
  return IO.gen(function* (_) {
    if (
      self._tag === "Both" &&
      self.left._tag === "Both" &&
      that._tag === "Both" &&
      that.right._tag === "Both"
    ) {
      const al = self.left.left
      const bl = self.left.right
      const cl = self.right
      const ar = that.left
      const br = that.right.left
      const cr = that.right.right
      return (
        (yield* _(al.equalsSafe(ar))) &&
        (yield* _(bl.equalsSafe(br))) &&
        (yield* _(cl.equalsSafe(cr)))
      )
    }
    return false
  })
}
function distributiveBoth(self, that) {
  return IO.gen(function* (_) {
    if (
      self._tag === "Both" &&
      self.left._tag === "Then" &&
      self.right._tag === "Then" &&
      that._tag === "Then" &&
      that.right._tag === "Both"
    ) {
      const al1 = self.left.left
      const bl = self.left.right
      const al2 = self.right.left
      const cl = self.right.right
      const ar = that.left
      const br = that.right.left
      const cr = that.right.right
      if (
        (yield* _(al1.equalsSafe(al2))) &&
        (yield* _(al1.equalsSafe(ar))) &&
        (yield* _(bl.equalsSafe(br))) &&
        (yield* _(cl.equalsSafe(cr)))
      ) {
        return true
      }
    }
    if (
      self._tag === "Both" &&
      self.left._tag === "Then" &&
      self.right._tag === "Then" &&
      that._tag === "Then" &&
      that.left._tag === "Both"
    ) {
      const al = self.left.left
      const cl1 = self.left.right
      const bl = self.right.left
      const cl2 = self.right.right
      const ar = that.left.left
      const br = that.left.right
      const cr = that.right
      if (
        (yield* _(cl1.equalsSafe(cl2))) &&
        (yield* _(al.equalsSafe(ar))) &&
        (yield* _(bl.equalsSafe(br))) &&
        (yield* _(cl1.equalsSafe(cr)))
      ) {
        return true
      }
    }
    return false
  })
}
function commutativeBoth(self, that) {
  return IO.gen(function* (_) {
    if (that._tag === "Both") {
      return (
        (yield* _(self.left.equalsSafe(that.right))) &&
        (yield* _(self.right.equalsSafe(that.left)))
      )
    }
    return false
  })
}
function zero(self, that) {
  if (self._tag === "Then" && self.right._tag === "Empty") {
    return self.left.equalsSafe(that)
  }
  if (self._tag === "Then" && self.left._tag === "Empty") {
    return self.right.equalsSafe(that)
  }
  if (self._tag === "Both" && self.right._tag === "Empty") {
    return self.left.equalsSafe(that)
  }
  if (self._tag === "Both" && self.left._tag === "Empty") {
    return self.right.equalsSafe(that)
  }
  return IO.succeed(false)
}
function sym(f) {
  return (l, r) =>
    IO.gen(function* (_) {
      return (yield* _(f(l, r))) || (yield* _(f(r, l)))
    })
}
function equals(self, that) {
  return IO.run(self.equalsSafe(that))
}
exports.equals = equals
function stepLoop(cause, stack, parallel, sequential) {
  // eslint-disable-next-line no-constant-condition
  while (1) {
    switch (cause._tag) {
      case "Empty": {
        if (L.isEmpty(stack)) {
          return Tp.tuple(parallel, sequential)
        } else {
          cause = L.unsafeFirst(stack)
          stack = L.tail(stack)
        }
        break
      }
      case "Traced": {
        cause = cause.cause
        break
      }
      case "Both": {
        stack = L.prepend_(stack, cause.right)
        cause = cause.left
        break
      }
      case "Then": {
        const left = cause.left
        const right = cause.right
        switch (left._tag) {
          case "Traced": {
            cause = then(left.cause, right)
            break
          }
          case "Empty": {
            cause = cause.right
            break
          }
          case "Then": {
            cause = then(left.left, then(left.right, right))
            break
          }
          case "Both": {
            cause = both(then(left.left, right), then(left.right, right))
            break
          }
          default: {
            cause = left
            sequential = L.prepend_(sequential, right)
          }
        }
        break
      }
      default: {
        if (L.isEmpty(stack)) {
          return Tp.tuple(HS.add_(parallel, cause), sequential)
        } else {
          parallel = HS.add_(parallel, cause)
          cause = L.unsafeFirst(stack)
          stack = L.tail(stack)
          break
        }
      }
    }
  }
  throw new Error("Bug")
}
function step(self) {
  return stepLoop(self, L.empty(), HS.make(), L.empty())
}
function flattenLoop(causes, flattened) {
  // eslint-disable-next-line no-constant-condition
  while (1) {
    const [parallel, sequential] = L.reduce_(
      causes,
      (0, Function_1.tuple)(HS.make(), L.empty()),
      ([parallel, sequential], cause) => {
        const {
          tuple: [set, seq]
        } = step(cause)
        return (0, Function_1.tuple)(
          HS.union_(parallel, set),
          L.concat_(sequential, seq)
        )
      }
    )
    const updated = HS.size(parallel) > 0 ? L.prepend_(flattened, parallel) : flattened
    if (L.isEmpty(sequential)) {
      return L.reverse(updated)
    } else {
      causes = sequential
      flattened = updated
    }
  }
  throw new Error("Bug")
}
function flatten(self) {
  return flattenLoop(L.of(self), L.empty())
}
function hashCode(self) {
  const flat = flatten(self)
  const size = L.size(flat)
  let head
  if (size === 0) {
    return _emptyHash
  } else if (size === 1 && (head = L.unsafeFirst(flat)) && HS.size(head) === 1) {
    return L.unsafeFirst(L.from(head))[St.hashSym]
  } else {
    return St.hashIterator(flat[Symbol.iterator]())
  }
}
//# sourceMappingURL=cause.js.map
