import * as Arr from "../Array.ts"
import type * as Cause from "../Cause.ts"
import type * as Clock from "../Clock.ts"
import type * as Console from "../Console.ts"
import * as Context from "../Context.ts"
import * as Duration from "../Duration.ts"
import type * as Effect from "../Effect.ts"
import * as Equal from "../Equal.ts"
import type * as Exit from "../Exit.ts"
import type * as Fiber from "../Fiber.ts"
import * as Filter from "../Filter.ts"
import { formatJson } from "../Formatter.ts"
import type { LazyArg } from "../Function.ts"
import { constant, constFalse, constTrue, constUndefined, constVoid, dual, identity } from "../Function.ts"
import * as Hash from "../Hash.ts"
import { toJson, toStringUnknown } from "../Inspectable.ts"
import * as Iterable from "../Iterable.ts"
import type * as _Latch from "../Latch.ts"
import type * as Logger from "../Logger.ts"
import type * as LogLevel from "../LogLevel.ts"
import type * as Metric from "../Metric.ts"
import * as Option from "../Option.ts"
import * as Order from "../Order.ts"
import { pipeArguments } from "../Pipeable.ts"
import type * as Predicate from "../Predicate.ts"
import { hasProperty, isIterable, isString, isTagged } from "../Predicate.ts"
import { currentFiberTypeId, redact } from "../Redactable.ts"
import type { StackFrame } from "../References.ts"
import * as Result from "../Result.ts"
import * as Scheduler from "../Scheduler.ts"
import type * as Scope from "../Scope.ts"
import * as Tracer from "../Tracer.ts"
import type {
  Concurrency,
  EqualsWith,
  ExcludeReason,
  ExcludeTag,
  ExtractReason,
  ExtractTag,
  NarrowReason,
  NoInfer,
  OmitReason,
  ReasonOf,
  ReasonTags,
  Simplify,
  Tags,
  unassigned
} from "../Types.ts"
import { internalCall } from "../Utils.ts"
import type { Primitive } from "./core.ts"
import {
  args,
  causeAnnotate,
  causeEmpty,
  causeFromReasons,
  CauseImpl,
  constEmptyAnnotations,
  contA,
  contAll,
  contE,
  evaluate,
  exitDie,
  exitFail,
  exitFailCause,
  exitSucceed,
  ExitTypeId,
  Fail,
  InterruptorStackTrace,
  isCause,
  isDieReason,
  isEffect,
  isFailReason,
  isInterruptReason,
  isNoSuchElementError,
  makePrimitive,
  makePrimitiveProto,
  NoSuchElementError,
  ReasonBase,
  StackTraceKey as CauseStackTrace,
  TaggedError,
  withFiber,
  Yield
} from "./core.ts"
import * as doNotation from "./doNotation.ts"
import * as InternalMetric from "./metric.ts"
import {
  CurrentConcurrency,
  CurrentErrorReporters,
  CurrentLogAnnotations,
  CurrentLogLevel,
  CurrentLogSpans,
  CurrentStackFrame,
  MinimumLogLevel,
  TracerEnabled,
  TracerSpanAnnotations,
  TracerSpanLinks,
  TracerTimingEnabled
} from "./references.ts"
import { addSpanStackTrace, type ErrorWithStackTraceLimit, makeStackCleaner } from "./tracer.ts"
import { version } from "./version.ts"

// ----------------------------------------------------------------------------
// Cause
// ----------------------------------------------------------------------------

/** @internal */
export class Interrupt extends ReasonBase<"Interrupt"> implements Cause.Interrupt {
  readonly fiberId: number | undefined
  constructor(
    fiberId: number | undefined,
    annotations = constEmptyAnnotations
  ) {
    super("Interrupt", annotations, "Interrupted")
    this.fiberId = fiberId
  }
  override toString() {
    return `Interrupt(${this.fiberId})`
  }
  toJSON(): unknown {
    return {
      _tag: "Interrupt",
      fiberId: this.fiberId
    }
  }
  [Equal.symbol](that: any): boolean {
    return (
      isInterruptReason(that) &&
      this.fiberId === that.fiberId &&
      this.annotations === that.annotations
    )
  }
  [Hash.symbol](): number {
    return Hash.combine(Hash.string(`${this._tag}:${this.fiberId}`))(
      Hash.random(this.annotations)
    )
  }
}

/** @internal */
export const makeInterruptReason = (fiberId?: number | undefined): Cause.Interrupt => new Interrupt(fiberId)

/** @internal */
export const causeInterrupt = (
  fiberId?: number | undefined
): Cause.Cause<never> => new CauseImpl([new Interrupt(fiberId)])

/** @internal */
export const hasFails = <E>(self: Cause.Cause<E>): boolean => self.reasons.some(isFailReason)

/** @internal */
export const findFail = <E>(self: Cause.Cause<E>): Result.Result<Cause.Fail<E>, Cause.Cause<never>> => {
  const reason = self.reasons.find(isFailReason)
  return reason ? Result.succeed(reason) : Result.fail(self as Cause.Cause<never>)
}

/** @internal */
export const findError = <E>(self: Cause.Cause<E>): Result.Result<E, Cause.Cause<never>> => {
  for (let i = 0; i < self.reasons.length; i++) {
    const reason = self.reasons[i]
    if (reason._tag === "Fail") {
      return Result.succeed(reason.error)
    }
  }
  return Result.fail(self as Cause.Cause<never>)
}

/** @internal */
export const findErrorOption = Filter.toOption(findError)

/** @internal */
export const hasDies = <E>(self: Cause.Cause<E>): boolean => self.reasons.some(isDieReason)

/** @internal */
export const findDie = <E>(self: Cause.Cause<E>): Result.Result<Cause.Die, Cause.Cause<E>> => {
  const reason = self.reasons.find(isDieReason)
  return reason ? Result.succeed(reason) : Result.fail(self)
}

/** @internal */
export const findDefect = <E>(self: Cause.Cause<E>): Result.Result<unknown, Cause.Cause<E>> => {
  const reason = self.reasons.find(isDieReason)
  return reason ? Result.succeed(reason.defect) : Result.fail(self)
}

/** @internal */
export const hasInterrupts = <E>(self: Cause.Cause<E>): boolean => self.reasons.some(isInterruptReason)

/** @internal */
export const findInterrupt = <E>(self: Cause.Cause<E>): Result.Result<Cause.Interrupt, Cause.Cause<E>> => {
  const reason = self.reasons.find(isInterruptReason)
  return reason ? Result.succeed(reason) : Result.fail(self)
}

/** @internal */
export const causeFilterInterruptors = <E>(
  self: Cause.Cause<E>
): Result.Result<Set<number>, Cause.Cause<E>> => {
  let interruptors: Set<number> | undefined
  for (let i = 0; i < self.reasons.length; i++) {
    const f = self.reasons[i]
    if (f._tag !== "Interrupt") continue
    interruptors ??= new Set()
    if (f.fiberId !== undefined) {
      interruptors.add(f.fiberId)
    }
  }
  return interruptors ? Result.succeed(interruptors) : Result.fail(self)
}

/** @internal */
export const causeInterruptors = <E>(self: Cause.Cause<E>): ReadonlySet<number> => {
  const result = causeFilterInterruptors(self)
  return Result.isFailure(result) ? emptySet : result.success
}
const emptySet = new Set<number>()

/** @internal */
export const hasInterruptsOnly = <E>(self: Cause.Cause<E>): boolean =>
  self.reasons.length > 0 && self.reasons.every(isInterruptReason)

/** @internal */
export const reasonAnnotations = <E>(
  self: Cause.Reason<E>
): Context.Context<never> => Context.makeUnsafe(self.annotations)

/** @internal */
export const causeAnnotations = <E>(
  self: Cause.Cause<E>
): Context.Context<never> => {
  const map = new Map<string, unknown>()
  for (const f of self.reasons) {
    if (f.annotations.size > 0) {
      for (const [key, value] of f.annotations) {
        map.set(key, value)
      }
    }
  }
  return Context.makeUnsafe(map)
}

/** @internal */
export const causeCombine: {
  <E2>(that: Cause.Cause<E2>): <E>(self: Cause.Cause<E>) => Cause.Cause<E | E2>
  <E, E2>(self: Cause.Cause<E>, that: Cause.Cause<E2>): Cause.Cause<E | E2>
} = dual(
  2,
  <E, E2>(self: Cause.Cause<E>, that: Cause.Cause<E2>): Cause.Cause<E | E2> => {
    if (self.reasons.length === 0) {
      return that as Cause.Cause<E | E2>
    } else if (that.reasons.length === 0) {
      return self as Cause.Cause<E | E2>
    }
    const newCause = new CauseImpl<E | E2>(
      Arr.union(self.reasons, that.reasons)
    )
    return Equal.equals(self, newCause) ? self : newCause
  }
)

/** @internal */
export const causeMap: {
  <E, E2>(f: (error: NoInfer<E>) => E2): (self: Cause.Cause<E>) => Cause.Cause<E2>
  <E, E2>(self: Cause.Cause<E>, f: (error: NoInfer<E>) => E2): Cause.Cause<E2>
} = dual(
  2,
  <E, E2>(self: Cause.Cause<E>, f: (error: NoInfer<E>) => E2): Cause.Cause<E2> => {
    let hasFail = false
    const failures = self.reasons.map((failure) => {
      if (isFailReason(failure)) {
        hasFail = true
        return new Fail(f(failure.error))
      }
      return failure
    })
    return hasFail ? causeFromReasons(failures) : self as any
  }
)

/** @internal */
export const causePartition = <E>(
  self: Cause.Cause<E>
): {
  readonly Fail: ReadonlyArray<Cause.Fail<E>>
  readonly Die: ReadonlyArray<Cause.Die>
  readonly Interrupt: ReadonlyArray<Cause.Interrupt>
} => {
  const obj = {
    Fail: [] as Array<Cause.Fail<E>>,
    Die: [] as Array<Cause.Die>,
    Interrupt: [] as Array<Cause.Interrupt>
  }
  for (let i = 0; i < self.reasons.length; i++) {
    obj[self.reasons[i]._tag].push(self.reasons[i] as any)
  }
  return obj
}

/** @internal */
export const causeSquash = <E>(self: Cause.Cause<E>): unknown => {
  const partitioned = causePartition(self)
  if (partitioned.Fail.length > 0) {
    return partitioned.Fail[0].error
  } else if (partitioned.Die.length > 0) {
    return partitioned.Die[0].defect
  } else if (partitioned.Interrupt.length > 0) {
    return new globalThis.Error("All fibers interrupted without error")
  }
  return new globalThis.Error("Empty cause")
}

/** @internal */
export const causePrettyErrors = <E>(self: Cause.Cause<E>): Array<Error> => {
  const errors: Array<Error> = []
  const interrupts: Array<Cause.Interrupt> = []
  if (self.reasons.length === 0) return errors

  const prevStackLimit = (Error as ErrorWithStackTraceLimit).stackTraceLimit
  ;(Error as ErrorWithStackTraceLimit)
    .stackTraceLimit = 1

  for (const failure of self.reasons) {
    if (failure._tag === "Interrupt") {
      interrupts.push(failure)
      continue
    }
    errors.push(
      causePrettyError(
        failure._tag === "Die" ? failure.defect : failure.error as any,
        failure.annotations
      )
    )
  }
  if (errors.length === 0) {
    const cause = new Error("The fiber was interrupted by:")
    cause.name = "InterruptCause"
    cause.stack = interruptCauseStack(cause, interrupts)
    const error = new globalThis.Error("All fibers interrupted without error", { cause })
    error.name = "InterruptError"
    error.stack = `${error.name}: ${error.message}`
    errors.push(causePrettyError(error, interrupts[0].annotations))
  }

  ;(Error as ErrorWithStackTraceLimit).stackTraceLimit = prevStackLimit
  return errors
}

/** @internal */
export const causePrettyError = (
  original: Record<string, unknown> | Error,
  annotations?: ReadonlyMap<string, unknown>
): Error => {
  const kind = typeof original
  let error: Error
  if (original && kind === "object") {
    error = new globalThis.Error(causePrettyMessage(original), {
      cause: original.cause ? causePrettyError(original.cause as any) : undefined
    })
    if (typeof original.name === "string") {
      error.name = original.name
    }
    if (typeof original.stack === "string") {
      error.stack = cleanErrorStack(original.stack, error, annotations)
    } else {
      const stack = `${error.name}: ${error.message}`
      error.stack = annotations ? addStackAnnotations(stack, annotations) : stack
    }
    for (const key of Object.keys(original)) {
      if (!(key in error)) {
        ;(error as any)[key] = (original as any)[key]
      }
    }
  } else {
    error = new globalThis.Error(
      !original ? `Unknown error: ${original}` : kind === "string" ? original as any : formatJson(original)
    )
  }
  return error
}

const causePrettyMessage = (u: Record<string, unknown> | Error): string => {
  if (typeof u.message === "string") {
    return u.message
  } else if (
    typeof u.toString === "function"
    && u.toString !== Object.prototype.toString
    && u.toString !== Array.prototype.toString
  ) {
    try {
      return u.toString()
    } catch {
      // something's off, rollback to json
    }
  }
  return formatJson(u)
}

const locationRegExp = /\((.*)\)/g

const cleanErrorStack = (
  stack: string,
  error: Error,
  annotations: ReadonlyMap<string, unknown> | undefined
): string => {
  const message = `${error.name}: ${error.message}`
  const lines = (stack.startsWith(message) ? stack.slice(message.length) : stack).split("\n")
  const out: Array<string> = [message]
  for (let i = 1; i < lines.length; i++) {
    if (/(?:Generator\.next|~effect\/Effect)/.test(lines[i])) {
      break
    }
    out.push(lines[i])
  }
  return annotations ? addStackAnnotations(out.join("\n"), annotations) : out.join("\n")
}

const addStackAnnotations = (stack: string, annotations: ReadonlyMap<string, unknown>) => {
  const frame = annotations?.get(CauseStackTrace.key) as StackFrame | undefined
  if (frame) {
    stack = `${stack}\n${currentStackTrace(frame)}`
  }
  return stack
}

const interruptCauseStack = (error: Error, interrupts: Array<Cause.Interrupt>): string => {
  const out: Array<string> = [`${error.name}: ${error.message}`]
  for (const current of interrupts) {
    const fiberId = current.fiberId !== undefined ? `#${current.fiberId}` : "unknown"
    const frame = current.annotations.get(InterruptorStackTrace.key) as StackFrame | undefined
    out.push(`    at fiber (${fiberId})`)
    if (frame) out.push(currentStackTrace(frame))
  }
  return out.join("\n")
}

const currentStackTrace = (frame: StackFrame): string => {
  const out: Array<string> = []
  let current: StackFrame | undefined = frame
  let i = 0
  while (current && i < 10) {
    const stack = current.stack()
    if (stack) {
      const locationMatchAll = stack.matchAll(locationRegExp)
      let match = false
      for (const [, location] of locationMatchAll) {
        match = true
        out.push(`    at ${current.name} (${location})`)
      }
      if (!match) {
        out.push(`    at ${current.name} (${stack.replace(/^at /, "")})`)
      }
    } else {
      out.push(`    at ${current.name}`)
    }
    current = current.parent
    i++
  }
  return out.join("\n")
}

/** @internal */
export const causePretty = <E>(cause: Cause.Cause<E>): string =>
  causePrettyErrors<E>(cause).map((e) =>
    e.cause ? `${e.stack} {\n${renderErrorCause(e.cause as Error, "  ")}\n}` : e.stack
  )
    .join("\n")

const renderErrorCause = (cause: Error, prefix: string) => {
  const lines = cause.stack!.split("\n")
  let stack = `${prefix}[cause]: ${lines[0]}`
  for (let i = 1, len = lines.length; i < len; i++) {
    stack += `\n${prefix}${lines[i]}`
  }
  if (cause.cause) {
    stack += ` {\n${renderErrorCause(cause.cause as Error, `${prefix}  `)}\n${prefix}}`
  }
  return stack
}

// ----------------------------------------------------------------------------
// Fiber
// ----------------------------------------------------------------------------

/** @internal */
export const FiberTypeId = `~effect/Fiber/${version}` as const

const fiberVariance = {
  _A: identity,
  _E: identity
}

const fiberIdStore = { id: 0 }

/** @internal */
export const getCurrentFiber = (): Fiber.Fiber<any, any> | undefined => (globalThis as any)[currentFiberTypeId]

/** @internal */
export class FiberImpl<A = any, E = any> implements Fiber.Fiber<A, E> {
  constructor(
    context: Context.Context<never>,
    interruptible: boolean = true
  ) {
    this[FiberTypeId] = fiberVariance as any
    this.setContext(context)
    this.id = ++fiberIdStore.id
    this.currentOpCount = 0
    this.currentLoopCount = 0
    this.interruptible = interruptible
    this._stack = []
    this._observers = []
    this._exit = undefined
    this._children = undefined
    this._interruptedCause = undefined
    this._yielded = undefined
  }

  readonly [FiberTypeId]: Fiber.Fiber.Variance<A, E>

  readonly id: number
  interruptible: boolean
  currentOpCount: number
  currentLoopCount: number
  readonly _stack: Array<Primitive>
  readonly _observers: Array<(exit: Exit.Exit<A, E>) => void>
  _exit: Exit.Exit<A, E> | undefined
  _currentExit: Exit.Exit<A, E> | undefined
  _children: Set<FiberImpl<any, any>> | undefined
  _interruptedCause: Cause.Cause<never> | undefined
  _yielded: Exit.Exit<any, any> | (() => void) | undefined

  // set in setContext
  context!: Context.Context<never>
  currentScheduler!: Scheduler.Scheduler
  currentTracerContext: Tracer.Tracer["context"]
  currentSpan: Tracer.AnySpan | undefined
  currentLogLevel!: LogLevel.LogLevel
  minimumLogLevel!: LogLevel.LogLevel
  currentStackFrame: StackFrame | undefined
  runtimeMetrics: Metric.FiberRuntimeMetricsService | undefined
  maxOpsBeforeYield!: number
  currentPreventYield!: boolean

  _dispatcher: Scheduler.SchedulerDispatcher | undefined = undefined
  get currentDispatcher(): Scheduler.SchedulerDispatcher {
    return this._dispatcher ??= this.currentScheduler.makeDispatcher()
  }

  getRef<X>(ref: Context.Reference<X>): X {
    return Context.getReferenceUnsafe(this.context, ref)
  }
  addObserver(cb: (exit: Exit.Exit<A, E>) => void): () => void {
    if (this._exit) {
      cb(this._exit)
      return constVoid
    }
    this._observers.push(cb)
    return () => {
      const index = this._observers.indexOf(cb)
      if (index >= 0) {
        this._observers.splice(index, 1)
      }
    }
  }
  interruptUnsafe(fiberId?: number | undefined, annotations?: Context.Context<never> | undefined): void {
    if (this._exit) {
      return
    }
    let cause = causeInterrupt(fiberId)
    if (this.currentStackFrame) {
      cause = causeAnnotate(cause, Context.make(CauseStackTrace, this.currentStackFrame))
    }
    if (annotations) {
      cause = causeAnnotate(cause, annotations)
    }
    this._interruptedCause = this._interruptedCause
      ? causeCombine(this._interruptedCause, cause)
      : cause
    if (this.interruptible) {
      this.evaluate(failCause(this._interruptedCause) as any)
    }
  }
  pollUnsafe(): Exit.Exit<A, E> | undefined {
    return this._exit
  }
  evaluate(effect: Primitive): void {
    this.runtimeMetrics?.recordFiberStart(this.context)
    if (this._exit) {
      return
    } else if (this._yielded !== undefined) {
      const yielded = this._yielded as () => void
      this._yielded = undefined
      yielded()
    }
    const exit = this.runLoop(effect)
    if (exit === Yield) {
      return
    }
    // the interruptChildren middleware is added in Effect.forkChild, so it can be
    // tree-shaken if not used
    const interruptChildren = fiberMiddleware.interruptChildren &&
      fiberMiddleware.interruptChildren(this)
    if (interruptChildren !== undefined) {
      return this.evaluate(flatMap(interruptChildren, () => exit) as any)
    }

    this._exit = exit
    this.runtimeMetrics?.recordFiberEnd(this.context, this._exit)
    for (let i = 0; i < this._observers.length; i++) {
      this._observers[i](exit)
    }
    this._observers.length = 0
  }
  runLoop(effect: Primitive): Exit.Exit<A, E> | Yield {
    const prevFiber = (globalThis as any)[currentFiberTypeId]
    ;(globalThis as any)[currentFiberTypeId] = this
    let yielding = false
    let current: Primitive | Yield = effect
    this.currentOpCount = 0
    const currentLoop = ++this.currentLoopCount
    try {
      while (true) {
        this.currentOpCount++
        if (
          !yielding &&
          !this.currentPreventYield &&
          this.currentScheduler.shouldYield(this as any)
        ) {
          yielding = true
          const prev = current
          current = flatMap(yieldNow, () => prev as any) as any
        }
        current = this.currentTracerContext
          ? this.currentTracerContext(current as any, this)
          : (current as any)[evaluate](this)
        if (currentLoop !== this.currentLoopCount) {
          // another effect has taken over the loop,
          return Yield
        } else if (current === Yield) {
          const yielded = this._yielded!
          if (ExitTypeId in yielded) {
            this._yielded = undefined
            return yielded
          }
          return Yield
        }
      }
    } catch (error) {
      if (!hasProperty(current, evaluate)) {
        return exitDie(`Fiber.runLoop: Not a valid effect: ${String(current)}`)
      }
      return this.runLoop(exitDie(error) as any)
    } finally {
      ;(globalThis as any)[currentFiberTypeId] = prevFiber
    }
  }
  getCont<S extends contA | contE>(symbol: S):
    | (Primitive & Record<S, (value: any, fiber: FiberImpl) => Primitive>)
    | undefined
  {
    while (true) {
      const op = this._stack.pop()
      if (!op) return undefined
      const cont = op[contAll] && op[contAll](this)
      if (cont) {
        ;(cont as any)[symbol] = cont
        return cont as any
      }
      if (op[symbol]) return op as any
    }
  }
  yieldWith(value: Exit.Exit<any, any> | (() => void)): Yield {
    this._yielded = value
    return Yield
  }
  children(): Set<Fiber.Fiber<any, any>> {
    return (this._children ??= new Set())
  }
  pipe() {
    return pipeArguments(this, arguments)
  }
  setContext(context: Context.Context<never>): void {
    this.context = context
    const scheduler = this.getRef(Scheduler.Scheduler)
    if (scheduler !== this.currentScheduler) {
      this.currentScheduler = scheduler
      this._dispatcher = undefined
    }
    this.currentSpan = context.mapUnsafe.get(Tracer.ParentSpanKey)
    this.currentLogLevel = this.getRef(CurrentLogLevel)
    this.minimumLogLevel = this.getRef(MinimumLogLevel)
    this.currentStackFrame = context.mapUnsafe.get(CurrentStackFrame.key)
    this.maxOpsBeforeYield = this.getRef(Scheduler.MaxOpsBeforeYield)
    this.currentPreventYield = this.getRef(Scheduler.PreventSchedulerYield)
    this.runtimeMetrics = context.mapUnsafe.get(InternalMetric.FiberRuntimeMetricsKey)
    const currentTracer = context.mapUnsafe.get(Tracer.TracerKey)
    this.currentTracerContext = currentTracer ? currentTracer["context"] : undefined
  }
  get currentSpanLocal(): Tracer.Span | undefined {
    return this.currentSpan?._tag === "Span" ? this.currentSpan : undefined
  }
}

const fiberMiddleware = {
  interruptChildren: undefined as
    | ((fiber: FiberImpl) => Effect.Effect<void> | undefined)
    | undefined
}

const fiberStackAnnotations = (fiber: Fiber.Fiber<any, any>) => {
  if (!fiber.currentStackFrame) return undefined
  const annotations = new Map<string, unknown>()
  annotations.set(CauseStackTrace.key, fiber.currentStackFrame)
  return Context.makeUnsafe(annotations)
}

const fiberInterruptChildren = (fiber: FiberImpl) => {
  if (fiber._children === undefined || fiber._children.size === 0) {
    return undefined
  }
  return fiberInterruptAll(fiber._children)
}

/** @internal */
export const fiberAwait = <A, E>(
  self: Fiber.Fiber<A, E>
): Effect.Effect<Exit.Exit<A, E>> => {
  const impl = self as FiberImpl<A, E>
  if (impl._exit) return succeed(impl._exit)
  return callback((resume) => {
    if (impl._exit) return resume(succeed(impl._exit))
    return sync(self.addObserver((exit) => resume(succeed(exit))))
  })
}

/** @internal */
export const fiberAwaitAll = <Fiber extends Fiber.Fiber<any, any>>(
  self: Iterable<Fiber>
): Effect.Effect<
  Array<
    Exit.Exit<
      Fiber extends Fiber.Fiber<infer _A, infer _E> ? _A : never,
      Fiber extends Fiber.Fiber<infer _A, infer _E> ? _E : never
    >
  >
> =>
  callback((resume) => {
    const iter = self[Symbol.iterator]() as Iterator<FiberImpl>
    const exits: Array<Exit.Exit<any, any>> = []
    let cancel: (() => void) | undefined = undefined
    function loop() {
      let result = iter.next()
      while (!result.done) {
        if (result.value._exit) {
          exits.push(result.value._exit)
          result = iter.next()
          continue
        }
        cancel = result.value.addObserver((exit) => {
          exits.push(exit)
          loop()
        })
        return
      }
      resume(succeed(exits))
    }
    loop()
    return sync(() => cancel?.())
  })

/** @internal */
export const fiberJoin = <A, E>(self: Fiber.Fiber<A, E>): Effect.Effect<A, E> => {
  const impl = self as FiberImpl<A, E>
  if (impl._exit) return impl._exit
  return callback((resume) => {
    if (impl._exit) return resume(impl._exit)
    return sync(self.addObserver(resume))
  })
}

/** @internal */
export const fiberJoinAll = <A extends Iterable<Fiber.Fiber<any, any>>>(self: A): Effect.Effect<
  Arr.ReadonlyArray.With<A, A extends Iterable<Fiber.Fiber<infer _A, infer _E>> ? _A : never>,
  A extends Fiber.Fiber<infer _A, infer _E> ? _E : never
> =>
  callback((resume) => {
    const fibers = Array.from(self)
    if (fibers.length === 0) return resume(succeed(Arr.empty() as any))
    const out = new Array<any>(fibers.length) as Arr.NonEmptyArray<any>
    const cancels = Arr.empty<() => void>()
    let done = 0
    let failed = false
    for (let i = 0; i < fibers.length; i++) {
      if (failed) break
      cancels.push(fibers[i].addObserver((exit) => {
        done++
        if (exit._tag === "Failure") {
          failed = true
          cancels.forEach((cancel) => cancel())
          return resume(exit as any)
        }
        out[i] = exit.value
        if (done === fibers.length) {
          resume(succeed(out))
        }
      }))
    }
  })

/** @internal */
export const fiberInterrupt = <A, E>(
  self: Fiber.Fiber<A, E>
): Effect.Effect<void> => withFiber((fiber) => fiberInterruptAs(self, fiber.id))

/** @internal */
export const fiberInterruptAs: {
  (
    fiberId: number | undefined,
    annotations?: Context.Context<never> | undefined
  ): <A, E>(self: Fiber.Fiber<A, E>) => Effect.Effect<void>
  <A, E>(
    self: Fiber.Fiber<A, E>,
    fiberId: number | undefined,
    annotations?: Context.Context<never> | undefined
  ): Effect.Effect<void>
} = dual(
  (args) => hasProperty(args[0], FiberTypeId),
  <A, E>(
    self: Fiber.Fiber<A, E>,
    fiberId: number | undefined,
    annotations?: Context.Context<never> | undefined
  ): Effect.Effect<void> =>
    withFiber((parent) => {
      let ann = fiberStackAnnotations(parent)
      ann = ann && annotations ? Context.merge(ann, annotations) : ann ?? annotations
      self.interruptUnsafe(fiberId, ann)
      return asVoid(fiberAwait(self))
    })
)

/** @internal */
export const fiberInterruptAll = <A extends Iterable<Fiber.Fiber<any, any>>>(
  fibers: A
): Effect.Effect<void> =>
  withFiber((parent) => {
    const annotations = fiberStackAnnotations(parent)
    for (const fiber of fibers) {
      fiber.interruptUnsafe(parent.id, annotations)
    }
    return asVoid(fiberAwaitAll(fibers))
  })

/** @internal */
export const fiberInterruptAllAs: {
  (fiberId: number): <A extends Iterable<Fiber.Fiber<any, any>>>(fibers: A) => Effect.Effect<void>
  <A extends Iterable<Fiber.Fiber<any, any>>>(fibers: A, fiberId: number): Effect.Effect<void>
} = dual(2, <A extends Iterable<Fiber.Fiber<any, any>>>(
  fibers: A,
  fiberId: number
): Effect.Effect<void> =>
  withFiber((parent) => {
    const annotations = fiberStackAnnotations(parent)
    for (const fiber of fibers) fiber.interruptUnsafe(fiberId, annotations)
    return asVoid(fiberAwaitAll(fibers))
  }))

/** @internal */
export const succeed: <A>(value: A) => Effect.Effect<A> = exitSucceed

/** @internal */
export const failCause: <E>(cause: Cause.Cause<E>) => Effect.Effect<never, E> = exitFailCause

/** @internal */
export const fail: <E>(error: E) => Effect.Effect<never, E> = exitFail

/** @internal */
export const sync: <A>(thunk: LazyArg<A>) => Effect.Effect<A> = makePrimitive({
  op: "Sync",
  [evaluate](fiber): Primitive | Yield {
    const value = this[args]()
    const cont = fiber.getCont(contA)
    return cont ? cont[contA](value, fiber) : fiber.yieldWith(exitSucceed(value))
  }
})

/** @internal */
export const suspend: <A, E, R>(
  evaluate: LazyArg<Effect.Effect<A, E, R>>
) => Effect.Effect<A, E, R> = makePrimitive({
  op: "Suspend",
  [evaluate](_fiber) {
    return this[args]()
  }
})

/** @internal */
export const fromYieldable = <Self extends Effect.Yieldable.Any, A, E, R>(
  yieldable: Effect.Yieldable<Self, A, E, R>
): Effect.Effect<A, E, R> => yieldable.asEffect()

/** @internal */
export const fromOption: <A>(option: Option.Option<A>) => Effect.Effect<A, Cause.NoSuchElementError> = fromYieldable

/** @internal */
export const fromResult: <A, E>(result: Result.Result<A, E>) => Effect.Effect<A, E> = fromYieldable

/** @internal */
export const fromNullishOr = <A>(value: A): Effect.Effect<NonNullable<A>, Cause.NoSuchElementError> =>
  value == null ? fail(new NoSuchElementError()) : succeed(value)

/** @internal */
export const yieldNowWith: (priority?: number) => Effect.Effect<void> = makePrimitive({
  op: "Yield",
  [evaluate](fiber) {
    let resumed = false
    fiber.currentDispatcher.scheduleTask(() => {
      if (resumed) return
      fiber.evaluate(exitVoid as any)
    }, this[args] ?? 0)
    return fiber.yieldWith(() => {
      resumed = true
    })
  }
})

/** @internal */
export const yieldNow: Effect.Effect<void> = yieldNowWith(0)

/** @internal */
export const succeedSome = <A>(a: A): Effect.Effect<Option.Option<A>> => succeed(Option.some(a))

/** @internal */
export const succeedNone: Effect.Effect<Option.Option<never>> = succeed(
  Option.none()
)

/** @internal */
export const failCauseSync = <E>(
  evaluate: LazyArg<Cause.Cause<E>>
): Effect.Effect<never, E> => suspend(() => failCause(internalCall(evaluate)))

/** @internal */
export const die = (defect: unknown): Effect.Effect<never> => exitDie(defect)

/** @internal */
export const failSync = <E>(error: LazyArg<E>): Effect.Effect<never, E> => suspend(() => fail(internalCall(error)))

/** @internal */
const void_: Effect.Effect<void> = succeed(void 0)
/** @internal */
export { void_ as void }

/** @internal */
const try_ = <A, E>(options: {
  try: LazyArg<A>
  catch: (error: unknown) => E
}): Effect.Effect<A, E> =>
  suspend(() => {
    try {
      return succeed(internalCall(options.try))
    } catch (err) {
      return fail(internalCall(() => options.catch(err)))
    }
  })
/** @internal */
export { try_ as try }

/** @internal */
export const promise = <A>(
  evaluate: (signal: AbortSignal) => PromiseLike<A>
): Effect.Effect<A> =>
  callbackOptions<A>(function(resume, signal) {
    internalCall(() => evaluate(signal!)).then(
      (a) => resume(succeed(a)),
      (e) => resume(die(e))
    )
  }, evaluate.length !== 0)

/** @internal */
export const tryPromise = <A, E = Cause.UnknownError>(
  options: {
    readonly try: (signal: AbortSignal) => PromiseLike<A>
    readonly catch: (error: unknown) => E
  } | ((signal: AbortSignal) => PromiseLike<A>)
): Effect.Effect<A, E> => {
  const f = typeof options === "function" ? options : options.try
  const catcher = typeof options === "function"
    ? ((cause: unknown) => new UnknownError(cause, "An error occurred in Effect.tryPromise"))
    : options.catch
  return callbackOptions<A, E>(function(resume, signal) {
    try {
      internalCall(() => f(signal!)).then(
        (a) => resume(succeed(a)),
        (e) => resume(fail(internalCall(() => catcher(e)) as E))
      )
    } catch (err) {
      resume(fail(internalCall(() => catcher(err)) as E))
    }
  }, eval.length !== 0)
}

/** @internal */
export const withFiberId = <A, E, R>(
  f: (fiberId: number) => Effect.Effect<A, E, R>
): Effect.Effect<A, E, R> => withFiber((fiber) => f(fiber.id))

/** @internal */
export const fiber = withFiber(succeed)

/** @internal */
export const fiberId = withFiberId(succeed)

const callbackOptions: <A, E = never, R = never>(
  register: (
    this: Scheduler.Scheduler,
    resume: (effect: Effect.Effect<A, E, R>) => void,
    signal?: AbortSignal
  ) => void | Effect.Effect<void, never, R>,
  withSignal: boolean
) => Effect.Effect<A, E, R> = makePrimitive({
  op: "Async",
  single: false,
  [evaluate](fiber) {
    const register = internalCall(() => this[args][0].bind(fiber.currentScheduler))
    let resumed = false
    let yielded: boolean | Primitive = false
    const controller = this[args][1] ? new AbortController() : undefined
    const onCancel = register((effect) => {
      if (resumed) return
      resumed = true
      if (yielded) {
        fiber.evaluate(effect as any)
      } else {
        yielded = effect as any
      }
    }, controller?.signal)
    if (yielded !== false) return yielded
    yielded = true
    fiber._yielded = () => {
      resumed = true
    }
    if (controller === undefined && onCancel === undefined) {
      return Yield
    }
    fiber._stack.push(
      asyncFinalizer(() => {
        resumed = true
        controller?.abort()
        return onCancel ?? exitVoid
      })
    )
    return Yield
  }
})

const asyncFinalizer: (
  onInterrupt: () => Effect.Effect<void, any, any>
) => Primitive = makePrimitive({
  op: "AsyncFinalizer",
  [contAll](fiber) {
    if (fiber.interruptible) {
      fiber.interruptible = false
      fiber._stack.push(setInterruptibleTrue)
    }
  },
  [contE](cause, _fiber) {
    return hasInterrupts(cause)
      ? flatMap(this[args](), () => failCause(cause))
      : failCause(cause)
  }
})

/** @internal */
export const callback = <A, E = never, R = never>(
  register: (
    this: Scheduler.Scheduler,
    resume: (effect: Effect.Effect<A, E, R>) => void,
    signal: AbortSignal
  ) => void | Effect.Effect<void, never, R>
): Effect.Effect<A, E, R> => callbackOptions(register as any, register.length >= 2)

/** @internal */
export const never: Effect.Effect<never> = callback<never>(constVoid)

/** @internal */
export const gen = <
  Self,
  Eff extends Effect.Yieldable<any, any, any, any>,
  AEff
>(
  ...args:
    | [options: { readonly self: Self }, body: (this: Self) => Generator<Eff, AEff, never>]
    | [body: () => Generator<Eff, AEff, never>]
): Effect.Effect<
  AEff,
  [Eff] extends [never] ? never
    : [Eff] extends [Effect.Yieldable<infer _A, infer E, infer _R>] ? E
    : never,
  [Eff] extends [never] ? never
    : [Eff] extends [Effect.Yieldable<infer _A, infer _E, infer R>] ? R
    : never
> =>
  suspend(() =>
    fromIteratorUnsafe(
      args.length === 1 ? args[0]() : (args[1].call(args[0].self) as any)
    )
  )

/** @internal */
export const fnUntraced: Effect.fn.Untraced = (
  body: Function,
  ...pipeables: Array<any>
) => {
  const fn = pipeables.length === 0
    ? function(this: any) {
      return suspend(() => fromIteratorUnsafe(body.apply(this, arguments)))
    }
    : function(this: any) {
      let effect = suspend(() => fromIteratorUnsafe(body.apply(this, arguments)))
      for (let i = 0; i < pipeables.length; i++) {
        effect = pipeables[i](effect, ...arguments)
      }
      return effect
    }
  return defineFunctionLength(body.length, fn)
}

const defineFunctionLength = <F extends Function>(length: number, fn: F): F =>
  Object.defineProperty(fn, "length", {
    value: length,
    configurable: true
  })

const fnStackCleaner = makeStackCleaner(2)

/** @internal */
export const fn: typeof Effect.fn = function() {
  const nameFirst = typeof arguments[0] === "string"
  const name = nameFirst ? arguments[0] : "Effect.fn"
  const spanOptions = nameFirst ? arguments[1] : undefined

  const prevLimit = globalThis.Error.stackTraceLimit
  globalThis.Error.stackTraceLimit = 2
  const defError = new globalThis.Error()
  globalThis.Error.stackTraceLimit = prevLimit

  if (nameFirst) {
    return (body: Function | { readonly self: any }, ...pipeables: Array<Function>) =>
      makeFn(name, body, defError, pipeables, nameFirst, spanOptions)
  }

  return makeFn(
    name,
    arguments[0],
    defError,
    Array.prototype.slice.call(arguments, 1),
    nameFirst,
    spanOptions
  )
} as any

const makeFn = (
  name: string,
  bodyOrOptions: Function | { readonly self: any },
  defError: Error,
  pipeables: Array<Function>,
  addSpan: boolean,
  spanOptions: Tracer.SpanOptionsNoTrace | undefined
) => {
  const body = typeof bodyOrOptions === "function"
    ? bodyOrOptions
    : (pipeables.pop()!).bind(bodyOrOptions.self)

  return defineFunctionLength(body.length, function(this: any, ...args: Array<any>) {
    let result = suspend(() => {
      const iter = body.apply(this, arguments)
      return isEffect(iter) ? iter : fromIteratorUnsafe(iter)
    })
    for (let i = 0; i < pipeables.length; i++) {
      result = pipeables[i](result, ...args)
    }
    if (!isEffect(result)) {
      return result
    }
    const prevLimit = globalThis.Error.stackTraceLimit
    globalThis.Error.stackTraceLimit = 2
    const callError = new globalThis.Error()
    globalThis.Error.stackTraceLimit = prevLimit
    return updateService(
      addSpan ?
        useSpan(name, spanOptions!, (span) => provideParentSpan(result, span)) :
        result,
      CurrentStackFrame,
      (prev) => ({
        name,
        stack: fnStackCleaner(() => callError.stack),
        parent: {
          name: `${name} (definition)`,
          stack: fnStackCleaner(() => defError.stack),
          parent: prev
        }
      })
    )
  })
}

/** @internal */
export const fnUntracedEager: Effect.fn.Untraced = (
  body: Function,
  ...pipeables: Array<any>
) =>
  defineFunctionLength(
    body.length,
    pipeables.length === 0
      ? function(this: any) {
        return fromIteratorEagerUnsafe(() => body.apply(this, arguments))
      }
      : function(this: any) {
        let effect = fromIteratorEagerUnsafe(() => body.apply(this, arguments))
        for (const pipeable of pipeables) {
          effect = pipeable(effect)
        }
        return effect
      }
  )

const fromIteratorEagerUnsafe = (
  evaluate: () => Iterator<Effect.Yieldable<any, any, any, any>>
): Effect.Effect<any, any, any> => {
  try {
    const iterator = evaluate()
    let value: any = undefined

    // Try to resolve synchronously in a loop
    while (true) {
      const state = iterator.next(value)

      if (state.done) {
        return succeed(state.value)
      }

      const yieldable = state.value
      const effect = yieldable.asEffect()
      const primitive = effect as any

      if (primitive && primitive._tag === "Success") {
        value = primitive.value
        continue
      } else if (primitive && primitive._tag === "Failure") {
        return effect
      } else {
        let isFirstExecution = true

        return suspend(() => {
          if (isFirstExecution) {
            isFirstExecution = false
            return flatMap(effect, (value) => fromIteratorUnsafe(iterator, value))
          } else {
            return suspend(() => fromIteratorUnsafe(evaluate()))
          }
        })
      }
    }
  } catch (error) {
    return die(error)
  }
}

const fromIteratorUnsafe: (
  iterator: Iterator<Effect.Yieldable<any, any, any, any>>,
  initial?: undefined
) => Effect.Effect<any, any, any> = makePrimitive({
  op: "Iterator",
  single: false,
  [contA](value, fiber) {
    const iter = this[args][0]
    while (true) {
      const state = iter.next(value)
      if (state.done) return succeed(state.value)
      const eff = state.value.asEffect()
      if (!effectIsExit(eff)) {
        fiber._stack.push(this)
        return eff
      } else if (eff._tag === "Failure") {
        return eff
      }
      value = eff.value
    }
  },
  [evaluate](this: any, fiber: FiberImpl) {
    return this[contA](this[args][1], fiber)
  }
})

// ----------------------------------------------------------------------------
// mapping & sequencing
// ----------------------------------------------------------------------------

/** @internal */
export const as: {
  <A, B>(
    value: B
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<B, E, R>
  <A, E, R, B>(self: Effect.Effect<A, E, R>, value: B): Effect.Effect<B, E, R>
} = dual(
  2,
  <A, E, R, B>(
    self: Effect.Effect<A, E, R>,
    value: B
  ): Effect.Effect<B, E, R> => {
    const b = succeed(value)
    return flatMap(self, (_) => b)
  }
)

/** @internal */
export const asSome = <A, E, R>(
  self: Effect.Effect<A, E, R>
): Effect.Effect<Option.Option<A>, E, R> => map(self, Option.some)

/** @internal */
export const flip = <A, E, R>(
  self: Effect.Effect<A, E, R>
): Effect.Effect<E, A, R> =>
  matchEffect(self, {
    onFailure: succeed,
    onSuccess: fail
  })

/** @internal */
export const andThen: {
  <A, B, E2, R2>(
    f: (a: A) => Effect.Effect<B, E2, R2>
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<B, E | E2, R | R2>
  <B, E2, R2>(
    f: Effect.Effect<B, E2, R2>
  ): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<B, E | E2, R | R2>
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (a: A) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<B, E | E2, R | R2>
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: Effect.Effect<B, E2, R2>
  ): Effect.Effect<B, E | E2, R | R2>
} = dual(
  2,
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: ((a: A) => Effect.Effect<B, E2, R2>) | Effect.Effect<B, E2, R2>
  ): Effect.Effect<B, E | E2, R | R2> =>
    flatMap(self, (a) => isEffect(f) ? f : internalCall(() => (f as (a: A) => Effect.Effect<B, E2, R2>)(a)))
)

/** @internal */
export const tap: {
  <A, B, E2, R2>(
    f: (a: NoInfer<A>) => Effect.Effect<B, E2, R2>
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | E2, R | R2>
  <B, E2, R2>(
    f: Effect.Effect<B, E2, R2>
  ): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | E2, R | R2>
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (a: NoInfer<A>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A, E | E2, R | R2>
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: Effect.Effect<B, E2, R2>
  ): Effect.Effect<A, E | E2, R | R2>
} = dual(
  2,
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: ((a: A) => Effect.Effect<B, E2, R2>) | Effect.Effect<B, E2, R2>
  ): Effect.Effect<A, E | E2, R | R2> =>
    flatMap(self, (a) => as(isEffect(f) ? f : internalCall(() => (f as (a: A) => Effect.Effect<B, E2, R2>)(a)), a))
)

/** @internal */
export const asVoid = <A, E, R>(
  self: Effect.Effect<A, E, R>
): Effect.Effect<void, E, R> => flatMap(self, (_) => exitVoid)

/** @internal */
export const sandbox = <A, E, R>(
  self: Effect.Effect<A, E, R>
): Effect.Effect<A, Cause.Cause<E>, R> => catchCause(self, fail)

/** @internal */
export const raceAll = <Eff extends Effect.Effect<any, any, any>>(
  all: Iterable<Eff>,
  options?: {
    readonly onWinner?: (options: {
      readonly fiber: Fiber.Fiber<any, any>
      readonly index: number
      readonly parentFiber: Fiber.Fiber<any, any>
    }) => void
  }
): Effect.Effect<
  Effect.Success<Eff>,
  Effect.Error<Eff>,
  Effect.Services<Eff>
> =>
  withFiber((parent) =>
    callback((resume) => {
      const effects = Arr.fromIterable(all)
      const len = effects.length
      let doneCount = 0
      let done = false
      const fibers = new Set<Fiber.Fiber<any, any>>()
      const failures: Array<Cause.Reason<any>> = []
      const onExit = (exit: Exit.Exit<any, any>, fiber: Fiber.Fiber<any, any>, i: number) => {
        doneCount++
        if (exit._tag === "Failure") {
          failures.push(...exit.cause.reasons)
          if (doneCount >= len) {
            resume(failCause(causeFromReasons(failures)))
          }
          return
        }
        const isWinner = !done
        done = true
        resume(
          fibers.size === 0
            ? exit
            : flatMap(uninterruptible(fiberInterruptAll(fibers)), () => exit)
        )
        if (isWinner && options?.onWinner) {
          options.onWinner({ fiber, index: i, parentFiber: parent })
        }
      }

      for (let i = 0; i < len; i++) {
        const fiber = forkUnsafe(parent, effects[i], true, true, false)
        fibers.add(fiber)
        fiber.addObserver((exit) => {
          fibers.delete(fiber)
          onExit(exit, fiber, i)
        })
        if (done) break
      }

      return fiberInterruptAll(fibers)
    })
  )

/** @internal */
export const raceAllFirst = <Eff extends Effect.Effect<any, any, any>>(
  all: Iterable<Eff>,
  options?: {
    readonly onWinner?: (options: {
      readonly fiber: Fiber.Fiber<any, any>
      readonly index: number
      readonly parentFiber: Fiber.Fiber<any, any>
    }) => void
  }
): Effect.Effect<
  Effect.Success<Eff>,
  Effect.Error<Eff>,
  Effect.Services<Eff>
> =>
  withFiber((parent) =>
    callback((resume) => {
      let done = false
      const fibers = new Set<Fiber.Fiber<any, any>>()
      const onExit = (exit: Exit.Exit<any, any>) => {
        done = true
        resume(
          fibers.size === 0
            ? exit
            : flatMap(uninterruptible(fiberInterruptAll(fibers)), () => exit)
        )
      }

      let i = 0
      for (const effect of all) {
        if (done) break
        const index = i++
        const fiber = forkUnsafe(parent, effect, true, true, false)
        fibers.add(fiber)
        fiber.addObserver((exit) => {
          fibers.delete(fiber)
          const isWinner = !done
          onExit(exit)
          if (isWinner && options?.onWinner) {
            options.onWinner({ fiber, index, parentFiber: parent })
          }
        })
      }

      return fiberInterruptAll(fibers)
    })
  )

/** @internal */
export const race: {
  <A2, E2, R2>(
    that: Effect.Effect<A2, E2, R2>,
    options?: {
      readonly onWinner?: (options: {
        readonly fiber: Fiber.Fiber<any, any>
        readonly index: number
        readonly parentFiber: Fiber.Fiber<any, any>
      }) => void
    }
  ): <A, E, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<A | A2, E | E2, R | R2>
  <A, E, R, A2, E2, R2>(
    self: Effect.Effect<A, E, R>,
    that: Effect.Effect<A2, E2, R2>,
    options?: {
      readonly onWinner?: (options: {
        readonly fiber: Fiber.Fiber<any, any>
        readonly index: number
        readonly parentFiber: Fiber.Fiber<any, any>
      }) => void
    }
  ): Effect.Effect<A | A2, E | E2, R | R2>
} = dual(
  (args) => isEffect(args[1]),
  <A, E, R, A2, E2, R2>(
    self: Effect.Effect<A, E, R>,
    that: Effect.Effect<A2, E2, R2>,
    options?: {
      readonly onWinner?: (options: {
        readonly fiber: Fiber.Fiber<any, any>
        readonly index: number
        readonly parentFiber: Fiber.Fiber<any, any>
      }) => void
    }
  ): Effect.Effect<A | A2, E | E2, R | R2> => raceAll([self, that], options)
)

/** @internal */
export const raceFirst: {
  <A2, E2, R2>(
    that: Effect.Effect<A2, E2, R2>,
    options?: {
      readonly onWinner?: (options: {
        readonly fiber: Fiber.Fiber<any, any>
        readonly index: number
        readonly parentFiber: Fiber.Fiber<any, any>
      }) => void
    }
  ): <A, E, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<A | A2, E | E2, R | R2>
  <A, E, R, A2, E2, R2>(
    self: Effect.Effect<A, E, R>,
    that: Effect.Effect<A2, E2, R2>,
    options?: {
      readonly onWinner?: (options: {
        readonly fiber: Fiber.Fiber<any, any>
        readonly index: number
        readonly parentFiber: Fiber.Fiber<any, any>
      }) => void
    }
  ): Effect.Effect<A | A2, E | E2, R | R2>
} = dual(
  (args) => isEffect(args[1]),
  <A, E, R, A2, E2, R2>(
    self: Effect.Effect<A, E, R>,
    that: Effect.Effect<A2, E2, R2>,
    options?: {
      readonly onWinner?: (options: {
        readonly fiber: Fiber.Fiber<any, any>
        readonly index: number
        readonly parentFiber: Fiber.Fiber<any, any>
      }) => void
    }
  ): Effect.Effect<A | A2, E | E2, R | R2> => raceAllFirst([self, that], options)
)

/** @internal */
export const flatMap: {
  <A, B, E2, R2>(
    f: (a: A) => Effect.Effect<B, E2, R2>
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<B, E | E2, R | R2>
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (a: A) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<B, E | E2, R | R2>
} = dual(
  2,
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (a: A) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<B, E | E2, R | R2> => {
    const onSuccess = Object.create(OnSuccessProto)
    onSuccess[args] = self
    onSuccess[contA] = f.length !== 1 ? (a: A) => f(a) : f
    return onSuccess
  }
)
const OnSuccessProto = makePrimitiveProto({
  op: "OnSuccess",
  [evaluate](this: any, fiber: FiberImpl): Primitive {
    fiber._stack.push(this)
    return this[args]
  }
})

/** @internal */
export const matchCauseEffectEager: {
  <E, A2, E2, R2, A, A3, E3, R3>(options: {
    readonly onFailure: (cause: Cause.Cause<E>) => Effect.Effect<A2, E2, R2>
    readonly onSuccess: (a: A) => Effect.Effect<A3, E3, R3>
  }): <R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<A2 | A3, E2 | E3, R2 | R3 | R>
  <A, E, R, A2, E2, R2, A3, E3, R3>(
    self: Effect.Effect<A, E, R>,
    options: {
      readonly onFailure: (cause: Cause.Cause<E>) => Effect.Effect<A2, E2, R2>
      readonly onSuccess: (a: A) => Effect.Effect<A3, E3, R3>
    }
  ): Effect.Effect<A2 | A3, E2 | E3, R2 | R3 | R>
} = dual(
  2,
  <A, E, R, A2, E2, R2, A3, E3, R3>(
    self: Effect.Effect<A, E, R>,
    options: {
      readonly onFailure: (cause: Cause.Cause<E>) => Effect.Effect<A2, E2, R2>
      readonly onSuccess: (a: A) => Effect.Effect<A3, E3, R3>
    }
  ): Effect.Effect<A2 | A3, E2 | E3, R2 | R3 | R> => {
    if (effectIsExit(self)) {
      return self._tag === "Success"
        ? options.onSuccess(self.value)
        : options.onFailure(self.cause)
    }
    return matchCauseEffect(self, options)
  }
)

/** @internal */
export const effectIsExit = <A, E, R>(effect: Effect.Effect<A, E, R>): effect is Exit.Exit<A, E> => ExitTypeId in effect

/** @internal */
export const flatMapEager: {
  <A, B, E2, R2>(
    f: (a: A) => Effect.Effect<B, E2, R2>
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<B, E | E2, R | R2>
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (a: A) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<B, E | E2, R | R2>
} = dual(
  2,
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (a: A) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<B, E | E2, R | R2> => {
    if (effectIsExit(self)) {
      return self._tag === "Success" ? f(self.value) : self as Exit.Exit<never, E>
    }
    return flatMap(self, f)
  }
)

// ----------------------------------------------------------------------------
// mapping & sequencing
// ----------------------------------------------------------------------------

/** @internal */
export const flatten = <A, E, R, E2, R2>(
  self: Effect.Effect<Effect.Effect<A, E, R>, E2, R2>
): Effect.Effect<A, E | E2, R | R2> => flatMap(self, identity)

/** @internal */
export const map: {
  <A, B>(
    f: (a: A) => B
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<B, E, R>
  <A, E, R, B>(
    self: Effect.Effect<A, E, R>,
    f: (a: A) => B
  ): Effect.Effect<B, E, R>
} = dual(
  2,
  <A, E, R, B>(
    self: Effect.Effect<A, E, R>,
    f: (a: A) => B
  ): Effect.Effect<B, E, R> => flatMap(self, (a) => succeed(internalCall(() => f(a))))
)

/** @internal */
export const mapEager: {
  <A, B>(
    f: (a: A) => B
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<B, E, R>
  <A, E, R, B>(
    self: Effect.Effect<A, E, R>,
    f: (a: A) => B
  ): Effect.Effect<B, E, R>
} = dual(
  2,
  <A, E, R, B>(
    self: Effect.Effect<A, E, R>,
    f: (a: A) => B
  ): Effect.Effect<B, E, R> => effectIsExit(self) ? exitMap(self, f) : map(self, f)
)

/** @internal */
export const mapErrorEager: {
  <E, E2>(
    f: (e: E) => E2
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E2, R>
  <A, E, R, E2>(
    self: Effect.Effect<A, E, R>,
    f: (e: E) => E2
  ): Effect.Effect<A, E2, R>
} = dual(
  2,
  <A, E, R, E2>(
    self: Effect.Effect<A, E, R>,
    f: (e: E) => E2
  ): Effect.Effect<A, E2, R> => effectIsExit(self) ? exitMapError(self, f) : mapError(self, f)
)

/** @internal */
export const mapBothEager: {
  <E, E2, A, A2>(
    options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 }
  ): <R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A2, E2, R>
  <A, E, R, E2, A2>(
    self: Effect.Effect<A, E, R>,
    options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 }
  ): Effect.Effect<A2, E2, R>
} = dual(
  2,
  <A, E, R, E2, A2>(
    self: Effect.Effect<A, E, R>,
    options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 }
  ): Effect.Effect<A2, E2, R> => effectIsExit(self) ? exitMapBoth(self, options) : mapBoth(self, options)
)

/** @internal */
export const catchEager: {
  <E, B, E2, R2>(
    f: (e: NoInfer<E>) => Effect.Effect<B, E2, R2>
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | B, E2, R | R2>
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (e: NoInfer<E>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A | B, E2, R | R2>
} = dual(
  2,
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (e: NoInfer<E>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A | B, E2, R | R2> => {
    if (effectIsExit(self)) {
      if (self._tag === "Success") return self as Exit.Exit<A>
      const error = findError(self.cause)
      if (Result.isFailure(error)) return self as Exit.Exit<never>
      return f(error.success)
    }
    return catch_(self, f)
  }
)

// ----------------------------------------------------------------------------
// Exit
// ----------------------------------------------------------------------------

/** @internal */
export const exitInterrupt = (fiberId?: number | undefined): Exit.Exit<never> => exitFailCause(causeInterrupt(fiberId))

/** @internal */
export const exitIsSuccess = <A, E>(
  self: Exit.Exit<A, E>
): self is Exit.Success<A, E> => self._tag === "Success"

/** @internal */
export const exitFilterSuccess = <A, E>(
  self: Exit.Exit<A, E>
): Result.Result<Exit.Success<A>, Exit.Failure<never, E>> =>
  self._tag === "Success" ? Result.succeed(self as any) : Result.fail(self as any)

/** @internal */
export const exitFilterValue = <A, E>(
  self: Exit.Exit<A, E>
): Result.Result<A, Exit.Failure<never, E>> =>
  self._tag === "Success" ? Result.succeed(self.value) : Result.fail(self as any)

/** @internal */
export const exitIsFailure = <A, E>(
  self: Exit.Exit<A, E>
): self is Exit.Failure<A, E> => self._tag === "Failure"

/** @internal */
export const exitFilterFailure = <A, E>(
  self: Exit.Exit<A, E>
): Result.Result<Exit.Failure<never, E>, Exit.Success<A>> =>
  self._tag === "Failure" ? Result.succeed(self as any) : Result.fail(self as any)

/** @internal */
export const exitFilterCause = <A, E>(
  self: Exit.Exit<A, E>
): Result.Result<Cause.Cause<E>, Exit.Success<A>> =>
  self._tag === "Failure" ? Result.succeed(self.cause) : Result.fail(self as any)

/** @internal */
export const exitFindError = Filter.composePassthrough(
  exitFilterCause,
  findError
)

/** @internal */
export const exitFindDefect = Filter.composePassthrough(
  exitFilterCause,
  findDefect
)

/** @internal */
export const exitHasInterrupts = <A, E>(
  self: Exit.Exit<A, E>
): self is Exit.Failure<A, E> => self._tag === "Failure" && hasInterrupts(self.cause)

/** @internal */
export const exitHasDies = <A, E>(
  self: Exit.Exit<A, E>
): self is Exit.Failure<A, E> => self._tag === "Failure" && hasDies(self.cause)

/** @internal */
export const exitHasFails = <A, E>(
  self: Exit.Exit<A, E>
): self is Exit.Failure<A, E> => self._tag === "Failure" && hasFails(self.cause)

/** @internal */
export const exitVoid: Exit.Exit<void> = exitSucceed(void 0)

/** @internal */
export const exitMap: {
  <A, B>(f: (a: A) => B): <E>(self: Exit.Exit<A, E>) => Exit.Exit<B, E>
  <A, E, B>(self: Exit.Exit<A, E>, f: (a: A) => B): Exit.Exit<B, E>
} = dual(
  2,
  <A, E, B>(self: Exit.Exit<A, E>, f: (a: A) => B): Exit.Exit<B, E> =>
    self._tag === "Success" ? exitSucceed(f(self.value)) : (self as any)
)

/** @internal */
export const exitMapError: {
  <E, E2>(f: (a: NoInfer<E>) => E2): <A>(self: Exit.Exit<A, E>) => Exit.Exit<A, E2>
  <A, E, E2>(self: Exit.Exit<A, E>, f: (a: NoInfer<E>) => E2): Exit.Exit<A, E2>
} = dual(
  2,
  <A, E, E2>(self: Exit.Exit<A, E>, f: (a: NoInfer<E>) => E2): Exit.Exit<A, E2> => {
    if (self._tag === "Success") return self as Exit.Exit<A>
    const error = findError(self.cause)
    if (Result.isFailure(error)) return self as Exit.Exit<never>
    return exitFail(f(error.success))
  }
)

/** @internal */
export const exitMapBoth: {
  <E, E2, A, A2>(
    options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 }
  ): (self: Exit.Exit<A, E>) => Exit.Exit<A2, E2>
  <A, E, E2, A2>(
    self: Exit.Exit<A, E>,
    options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 }
  ): Exit.Exit<A2, E2>
} = dual(
  2,
  <A, E, E2, A2>(
    self: Exit.Exit<A, E>,
    options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 }
  ): Exit.Exit<A2, E2> => {
    if (self._tag === "Success") return exitSucceed(options.onSuccess(self.value))
    const error = findError(self.cause)
    if (Result.isFailure(error)) return self as Exit.Exit<never>
    return exitFail(options.onFailure(error.success))
  }
)

/** @internal */
export const exitAs: {
  <B>(b: B): <A, E>(self: Exit.Exit<A, E>) => Exit.Exit<B, E>
  <A, E, B>(self: Exit.Exit<A, E>, b: B): Exit.Exit<B, E>
} = dual(
  2,
  <A, E, B>(self: Exit.Exit<A, E>, b: B): Exit.Exit<B, E> => exitIsSuccess(self) ? exitSucceed(b) : (self as any)
)

/** @internal */
export const exitZipRight: {
  <A2, E2>(
    that: Exit.Exit<A2, E2>
  ): <A, E>(self: Exit.Exit<A, E>) => Exit.Exit<A2, E | E2>
  <A, E, A2, E2>(
    self: Exit.Exit<A, E>,
    that: Exit.Exit<A2, E2>
  ): Exit.Exit<A2, E | E2>
} = dual(
  2,
  <A, E, A2, E2>(
    self: Exit.Exit<A, E>,
    that: Exit.Exit<A2, E2>
  ): Exit.Exit<A2, E | E2> => (exitIsSuccess(self) ? that : (self as any))
)

/** @internal */
export const exitMatch: {
  <A, E, X1, X2>(options: {
    readonly onSuccess: (a: NoInfer<A>) => X1
    readonly onFailure: (cause: Cause.Cause<NoInfer<E>>) => X2
  }): (self: Exit.Exit<A, E>) => X1 | X2
  <A, E, X1, X2>(
    self: Exit.Exit<A, E>,
    options: {
      readonly onSuccess: (a: A) => X1
      readonly onFailure: (cause: Cause.Cause<E>) => X2
    }
  ): X1 | X2
} = dual(
  2,
  <A, E, X1, X2>(
    self: Exit.Exit<A, E>,
    options: {
      readonly onSuccess: (a: A) => X1
      readonly onFailure: (cause: Cause.Cause<E>) => X2
    }
  ): X1 | X2 =>
    exitIsSuccess(self)
      ? options.onSuccess(self.value)
      : options.onFailure(self.cause)
)

/** @internal */
export const exitAsVoid: <A, E>(self: Exit.Exit<A, E>) => Exit.Exit<void, E> = exitAs(void 0)

/** @internal */
export const exitAsVoidAll = <I extends Iterable<Exit.Exit<any, any>>>(
  exits: I
): Exit.Exit<
  void,
  I extends Iterable<Exit.Exit<infer _A, infer _E>> ? _E : never
> => {
  const failures: Array<Cause.Reason<any>> = []
  for (const exit of exits) {
    if (exit._tag === "Failure") {
      failures.push(...exit.cause.reasons)
    }
  }
  return failures.length === 0 ? exitVoid : exitFailCause(causeFromReasons(failures))
}

/** @internal */
export const exitGetSuccess = <A, E>(self: Exit.Exit<A, E>): Option.Option<A> =>
  exitIsSuccess(self) ? Option.some(self.value) : Option.none()

/** @internal */
export const exitGetCause = <A, E>(self: Exit.Exit<A, E>): Option.Option<Cause.Cause<E>> =>
  exitIsFailure(self) ? Option.some(self.cause) : Option.none()

/** @internal */
export const exitFindErrorOption = <A, E>(self: Exit.Exit<A, E>): Option.Option<E> => {
  const error = exitFindError(self)
  return Result.isFailure(error) ? Option.none() : Option.some(error.success)
}

// ----------------------------------------------------------------------------
// environment
// ----------------------------------------------------------------------------

/** @internal */
export const service: {
  <I, S>(service: Context.Key<I, S>): Effect.Effect<S, never, I>
} = fromYieldable as any

/** @internal */
export const serviceOption = <I, S>(
  service: Context.Key<I, S>
): Effect.Effect<Option.Option<S>> => withFiber((fiber) => succeed(Context.getOption(fiber.context, service)))

/** @internal */
export const serviceOptional = <I, S>(
  service: Context.Key<I, S>
): Effect.Effect<S, Cause.NoSuchElementError> =>
  withFiber((fiber) =>
    fiber.context.mapUnsafe.has(service.key)
      ? succeed(Context.getUnsafe(fiber.context, service))
      : fail(new NoSuchElementError())
  )

/** @internal */
export const updateContext: {
  <R2, R>(
    f: (context: Context.Context<R2>) => Context.Context<NoInfer<R>>
  ): <A, E>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R2>
  <A, E, R, R2>(
    self: Effect.Effect<A, E, R>,
    f: (context: Context.Context<R2>) => Context.Context<NoInfer<R>>
  ): Effect.Effect<A, E, R2>
} = dual(
  2,
  <A, E, R, R2>(
    self: Effect.Effect<A, E, R>,
    f: (context: Context.Context<R2>) => Context.Context<NoInfer<R>>
  ): Effect.Effect<A, E, R2> =>
    withFiber<A, E, R2>((fiber) => {
      const prevContext = fiber.context as Context.Context<R2>
      const nextContext = f(prevContext)
      if (prevContext === nextContext) return self as any
      fiber.setContext(nextContext)
      return onExitPrimitive(self, () => {
        fiber.setContext(prevContext)
        return undefined
      })
    })
)

/** @internal */
export const updateService: {
  <I, A>(
    service: Context.Key<I, A>,
    f: (value: A) => A
  ): <XA, E, R>(self: Effect.Effect<XA, E, R>) => Effect.Effect<XA, E, R | I>
  <XA, E, R, I, A>(
    self: Effect.Effect<XA, E, R>,
    service: Context.Key<I, A>,
    f: (value: A) => A
  ): Effect.Effect<XA, E, R | I>
} = dual(
  3,
  <XA, E, R, I, A>(
    self: Effect.Effect<XA, E, R>,
    service: Context.Key<I, A>,
    f: (value: A) => A
  ): Effect.Effect<XA, E, R | I> =>
    updateContext(self, (s) => {
      const prev = Context.getUnsafe(s, service)
      const next = f(prev)
      if (prev === next) return s
      return Context.add(s, service, next)
    })
)

/** @internal */
export const context = <R = never>(): Effect.Effect<Context.Context<R>> => getContext as any
const getContext = withFiber((fiber) => succeed(fiber.context))

/** @internal */
export const contextWith = <R, A, E, R2>(
  f: (context: Context.Context<R>) => Effect.Effect<A, E, R2>
): Effect.Effect<A, E, R | R2> => withFiber((fiber) => f(fiber.context as Context.Context<R>))

/** @internal */
export const provideContext: {
  <XR>(
    context: Context.Context<XR>
  ): <A, E, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<A, E, Exclude<R, XR>>
  <A, E, R, XR>(
    self: Effect.Effect<A, E, R>,
    context: Context.Context<XR>
  ): Effect.Effect<A, E, Exclude<R, XR>>
} = dual(
  2,
  <A, E, R, XR>(
    self: Effect.Effect<A, E, R>,
    context: Context.Context<XR>
  ): Effect.Effect<A, E, Exclude<R, XR>> => {
    if (effectIsExit(self)) return self as any
    return updateContext(self, Context.merge(context)) as any
  }
)

/** @internal */
export const provideService: {
  <I, S>(
    service: Context.Key<I, S>
  ): {
    (implementation: S): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, I>>
    <A, E, R>(self: Effect.Effect<A, E, R>, implementation: S): Effect.Effect<A, E, Exclude<R, I>>
  }
  <I, S>(
    key: Context.Key<I, S>,
    implementation: S
  ): <A, E, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<A, E, Exclude<R, I>>
  <A, E, R, I, S>(
    self: Effect.Effect<A, E, R>,
    service: Context.Key<I, S>,
    implementation: S
  ): Effect.Effect<A, E, Exclude<R, I>>
} = function(this: any) {
  if (arguments.length === 1) {
    return dual(2, (self, impl) => provideServiceImpl(self, arguments[0], impl)) as any
  }
  return dual(3, (self, service, impl) => provideServiceImpl(self, service, impl))
    .apply(this, arguments as any) as any
}

const provideServiceImpl = <A, E, R, I, S>(
  self: Effect.Effect<A, E, R>,
  service: Context.Key<I, S>,
  implementation: S
): Effect.Effect<A, E, Exclude<R, I>> =>
  updateContext(self, (s) => {
    const prev = s.mapUnsafe.get(service.key)
    if (prev === implementation) return s
    return Context.add(s, service, implementation)
  }) as any

/** @internal */
export const provideServiceEffect: {
  <I, S, E2, R2>(
    service: Context.Key<I, S>,
    acquire: Effect.Effect<S, E2, R2>
  ): <A, E, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<A, E | E2, Exclude<R, I> | R2>
  <A, E, R, I, S, E2, R2>(
    self: Effect.Effect<A, E, R>,
    service: Context.Key<I, S>,
    acquire: Effect.Effect<S, E2, R2>
  ): Effect.Effect<A, E | E2, Exclude<R, I> | R2>
} = dual(
  3,
  <A, E, R, I, S, E2, R2>(
    self: Effect.Effect<A, E, R>,
    service: Context.Key<I, S>,
    acquire: Effect.Effect<S, E2, R2>
  ): Effect.Effect<A, E | E2, Exclude<R, I> | R2> =>
    flatMap(acquire, (implementation) => provideService(self, service, implementation))
)

/** @internal */
export const withConcurrency: {
  (
    concurrency: "unbounded" | number
  ): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    concurrency: "unbounded" | number
  ): Effect.Effect<A, E, R>
} = provideService(CurrentConcurrency)

// ----------------------------------------------------------------------------
// zipping
// ----------------------------------------------------------------------------

/** @internal */
export const zip: {
  <A2, E2, R2>(
    that: Effect.Effect<A2, E2, R2>,
    options?: { readonly concurrent?: boolean | undefined } | undefined
  ): <A, E, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<[A, A2], E2 | E, R2 | R>
  <A, E, R, A2, E2, R2>(
    self: Effect.Effect<A, E, R>,
    that: Effect.Effect<A2, E2, R2>,
    options?: { readonly concurrent?: boolean | undefined }
  ): Effect.Effect<[A, A2], E | E2, R | R2>
} = dual(
  (args) => isEffect(args[1]),
  <A, E, R, A2, E2, R2>(
    self: Effect.Effect<A, E, R>,
    that: Effect.Effect<A2, E2, R2>,
    options?: { readonly concurrent?: boolean | undefined }
  ): Effect.Effect<[A, A2], E | E2, R | R2> => zipWith(self, that, (a, a2) => [a, a2], options)
)

/** @internal */
export const zipWith: {
  <A2, E2, R2, A, B>(
    that: Effect.Effect<A2, E2, R2>,
    f: (a: A, b: A2) => B,
    options?: { readonly concurrent?: boolean | undefined }
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<B, E2 | E, R2 | R>
  <A, E, R, A2, E2, R2, B>(
    self: Effect.Effect<A, E, R>,
    that: Effect.Effect<A2, E2, R2>,
    f: (a: A, b: A2) => B,
    options?: { readonly concurrent?: boolean | undefined }
  ): Effect.Effect<B, E2 | E, R2 | R>
} = dual(
  (args) => isEffect(args[1]),
  <A, E, R, A2, E2, R2, B>(
    self: Effect.Effect<A, E, R>,
    that: Effect.Effect<A2, E2, R2>,
    f: (a: A, b: A2) => B,
    options?: { readonly concurrent?: boolean | undefined }
  ): Effect.Effect<B, E2 | E, R2 | R> =>
    options?.concurrent
      // Use `all` exclusively for concurrent cases, as it introduces additional overhead due to the management of concurrency
      ? map(all([self, that], { concurrency: 2 }), ([a, a2]) => internalCall(() => f(a, a2)))
      : flatMap(self, (a) => map(that, (a2) => internalCall(() => f(a, a2))))
)

// ----------------------------------------------------------------------------
// filtering & conditionals
// ----------------------------------------------------------------------------

/* @internal */
export const filterOrFail: {
  <A, E2, B extends A>(
    refinement: Predicate.Refinement<NoInfer<A>, B>,
    orFailWith: (a: NoInfer<A>) => E2
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<B, E2 | E, R>
  <A, E2>(
    predicate: Predicate.Predicate<NoInfer<A>>,
    orFailWith: (a: NoInfer<A>) => E2
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E2 | E, R>
  <A, B extends A>(
    refinement: Predicate.Refinement<NoInfer<A>, B>
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<B, Cause.NoSuchElementError | E, R>
  <A>(
    predicate: Predicate.Predicate<NoInfer<A>>
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, Cause.NoSuchElementError | E, R>
  <A, E, R, E2, B extends A>(
    self: Effect.Effect<A, E, R>,
    refinement: Predicate.Refinement<NoInfer<A>, B>,
    orFailWith: (a: NoInfer<A>) => E2
  ): Effect.Effect<B, E2 | E, R>
  <A, E, R, E2>(
    self: Effect.Effect<A, E, R>,
    predicate: Predicate.Predicate<NoInfer<A>>,
    orFailWith: (a: NoInfer<A>) => E2
  ): Effect.Effect<A, E2 | E, R>
  <A, E, R, B extends A>(
    self: Effect.Effect<A, E, R>,
    refinement: Predicate.Refinement<NoInfer<A>, B>
  ): Effect.Effect<B, E | Cause.NoSuchElementError, R>
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    predicate: Predicate.Predicate<NoInfer<A>>
  ): Effect.Effect<A, E | Cause.NoSuchElementError, R>
} = dual((args) => isEffect(args[0]), <A, E, R, E2>(
  self: Effect.Effect<A, E, R>,
  predicate: Predicate.Predicate<NoInfer<A>>,
  orFailWith?: (a: any) => E2
): Effect.Effect<any, E | E2 | Cause.NoSuchElementError, R> =>
  filterOrElse(
    self,
    predicate as any,
    orFailWith ? (a: any) => fail(orFailWith(a)) : () => fail(new NoSuchElementError() as E2)
  ))

/** @internal */
export const when: {
  <E2 = never, R2 = never>(
    condition: Effect.Effect<boolean, E2, R2>
  ): <A, E, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<Option.Option<A>, E | E2, R | R2>
  <A, E, R, E2 = never, R2 = never>(
    self: Effect.Effect<A, E, R>,
    condition: Effect.Effect<boolean, E2, R2>
  ): Effect.Effect<Option.Option<A>, E | E2, R | R2>
} = dual(
  2,
  <A, E, R, E2 = never, R2 = never>(
    self: Effect.Effect<A, E, R>,
    condition: Effect.Effect<boolean, E2, R2>
  ): Effect.Effect<Option.Option<A>, E | E2, R | R2> => flatMap(condition, (pass) => pass ? asSome(self) : succeedNone)
)

// ----------------------------------------------------------------------------
// repetition
// ----------------------------------------------------------------------------

/** @internal */
export const replicate: {
  (
    n: number
  ): <A, E, R>(self: Effect.Effect<A, E, R>) => Array<Effect.Effect<A, E, R>>
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    n: number
  ): Array<Effect.Effect<A, E, R>>
} = dual(
  2,
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    n: number
  ): Array<Effect.Effect<A, E, R>> => Array.from({ length: n }, () => self)
)

/** @internal */
export const replicateEffect: {
  (
    n: number,
    options?: {
      readonly concurrency?: Concurrency | undefined
      readonly discard?: false | undefined
    }
  ): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<Array<A>, E, R>
  (
    n: number,
    options: {
      readonly concurrency?: Concurrency | undefined
      readonly discard: true
    }
  ): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<void, E, R>
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    n: number,
    options?: {
      readonly concurrency?: Concurrency | undefined
      readonly discard?: false | undefined
    }
  ): Effect.Effect<Array<A>, E, R>
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    n: number,
    options: {
      readonly concurrency?: Concurrency | undefined
      readonly discard: true
    }
  ): Effect.Effect<void, E, R>
} = dual(
  (args) => isEffect(args[0]),
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    n: number,
    options: {
      readonly concurrency?: Concurrency | undefined
      readonly discard: true
    }
  ): Effect.Effect<void, E, R> => all(replicate(self, n), options)
)

/** @internal */
export const forever: {
  <
    Arg extends Effect.Effect<any, any, any> | {
      readonly disableYield?: boolean | undefined
    } | undefined = {
      readonly disableYield?: boolean | undefined
    }
  >(
    effectOrOptions: Arg,
    options?: {
      readonly disableYield?: boolean | undefined
    } | undefined
  ): [Arg] extends [Effect.Effect<infer _A, infer _E, infer _R>] ? Effect.Effect<never, _E, _R>
    : <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<never, E, R>
} = dual((args) => isEffect(args[0]), <A, E, R>(
  self: Effect.Effect<A, E, R>,
  options?: {
    readonly disableYield?: boolean | undefined
  }
): Effect.Effect<never, E, R> =>
  whileLoop({
    while: constTrue,
    body: constant(options?.disableYield ? self : flatMap(self, (_) => yieldNow)),
    step: constVoid
  }) as any)

// ----------------------------------------------------------------------------
// error handling
// ----------------------------------------------------------------------------

/** @internal */
export const catchCause: {
  <E, B, E2, R2>(
    f: (cause: NoInfer<Cause.Cause<E>>) => Effect.Effect<B, E2, R2>
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | B, E2, R | R2>
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (cause: NoInfer<Cause.Cause<E>>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A | B, E2, R | R2>
} = dual(
  2,
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (cause: NoInfer<Cause.Cause<E>>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A | B, E2, R | R2> => {
    const onFailure = Object.create(OnFailureProto)
    onFailure[args] = self
    onFailure[contE] = f.length !== 1 ? (cause: Cause.Cause<E>) => f(cause) : f
    return onFailure
  }
)
const OnFailureProto = makePrimitiveProto({
  op: "OnFailure",
  [evaluate](this: any, fiber: FiberImpl): Primitive {
    fiber._stack.push(this as any)
    return this[args]
  }
})

/** @internal */
export const catchCauseIf: {
  <E, B, E2, R2>(
    predicate: Predicate.Predicate<Cause.Cause<E>>,
    f: (cause: Cause.Cause<E>) => Effect.Effect<B, E2, R2>
  ): <A, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<A | B, E | E2, R | R2>
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    predicate: Predicate.Predicate<Cause.Cause<E>>,
    f: (cause: Cause.Cause<E>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A | B, E | E2, R | R2>
} = dual(
  3,
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    predicate: Predicate.Predicate<Cause.Cause<E>>,
    f: (cause: Cause.Cause<E>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A | B, E | E2, R | R2> =>
    catchCause(self, (cause): Effect.Effect<B, E | E2, R2> => {
      if (!predicate(cause)) {
        return failCause(cause) as any
      }
      return internalCall(() => f(cause))
    })
)

/** @internal */
export const catchCauseFilter: {
  <E, B, E2, R2, EB, X extends Cause.Cause<any>>(
    filter: Filter.Filter<Cause.Cause<E>, EB, X>,
    f: (failure: EB, cause: Cause.Cause<E>) => Effect.Effect<B, E2, R2>
  ): <A, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<A | B, Cause.Cause.Error<X> | E2, R | R2>
  <A, E, R, B, E2, R2, EB, X extends Cause.Cause<any>>(
    self: Effect.Effect<A, E, R>,
    filter: Filter.Filter<Cause.Cause<E>, EB, X>,
    f: (failure: EB, cause: Cause.Cause<E>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A | B, Cause.Cause.Error<X> | E2, R | R2>
} = dual(
  3,
  <A, E, R, B, E2, R2, EB, X extends Cause.Cause<any>>(
    self: Effect.Effect<A, E, R>,
    filter: Filter.Filter<Cause.Cause<E>, EB, X>,
    f: (failure: EB, cause: Cause.Cause<E>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A | B, Cause.Cause.Error<X> | E2, R | R2> =>
    catchCause(self, (cause): Effect.Effect<B, Cause.Cause.Error<X> | E2, R2> => {
      const eb = filter(cause)
      return Result.isFailure(eb) ? failCause(eb.failure) : internalCall(() => f(eb.success, cause))
    })
)

/** @internal */
export const catch_: {
  <E, B, E2, R2>(
    f: (e: NoInfer<E>) => Effect.Effect<B, E2, R2>
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | B, E2, R | R2>
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (e: NoInfer<E>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A | B, E2, R | R2>
} = dual(
  2,
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (a: NoInfer<E>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A | B, E2, R | R2> => catchCauseFilter(self, findError as any, (e: any) => f(e)) as any
)

/** @internal */
export const catchNoSuchElement = <A, E, R>(
  self: Effect.Effect<A, E, R>
): Effect.Effect<Option.Option<A>, Exclude<E, Cause.NoSuchElementError>, R> =>
  matchEffect(self, {
    onFailure: (error) =>
      isNoSuchElementError(error)
        ? succeedNone
        : fail(error as Exclude<E, Cause.NoSuchElementError>),
    onSuccess: succeedSome
  })

/** @internal */
export const catchDefect: {
  <E, B, E2, R2>(
    f: (defect: unknown) => Effect.Effect<B, E2, R2>
  ): <A, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<A | B, E | E2, R | R2>
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (defect: unknown) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A | B, E | E2, R | R2>
} = dual(
  2,
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (defect: unknown) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A | B, E | E2, R | R2> => catchCauseFilter(self, findDefect as any, f as any) as any
)

/** @internal */
export const tapCause: {
  <E, B, E2, R2>(
    f: (cause: NoInfer<Cause.Cause<E>>) => Effect.Effect<B, E2, R2>
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | E2, R | R2>
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (cause: NoInfer<Cause.Cause<E>>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A, E | E2, R | R2>
} = dual(
  2,
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (cause: NoInfer<Cause.Cause<E>>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A, E | E2, R | R2> =>
    catchCause(self, (cause) => andThen(internalCall(() => f(cause)), failCause(cause)))
)

/** @internal */
export const tapCauseIf: {
  <E, B, E2, R2>(
    predicate: Predicate.Predicate<Cause.Cause<E>>,
    f: (cause: Cause.Cause<E>) => Effect.Effect<B, E2, R2>
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | E2, R | R2>
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    predicate: Predicate.Predicate<Cause.Cause<E>>,
    f: (cause: Cause.Cause<E>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A, E | E2, R | R2>
} = dual(
  3,
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    predicate: Predicate.Predicate<Cause.Cause<E>>,
    f: (cause: Cause.Cause<E>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A, E | E2, R | R2> =>
    catchCauseIf(
      self,
      predicate,
      (cause) => andThen(internalCall(() => f(cause)), failCause(cause))
    )
)

/** @internal */
export const tapCauseFilter: {
  <E, B, E2, R2, EB, X extends Cause.Cause<any>>(
    filter: Filter.Filter<Cause.Cause<E>, EB, X>,
    f: (a: EB, cause: Cause.Cause<E>) => Effect.Effect<B, E2, R2>
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | E2, R | R2>
  <A, E, R, B, E2, R2, EB, X extends Cause.Cause<any>>(
    self: Effect.Effect<A, E, R>,
    filter: Filter.Filter<Cause.Cause<E>, EB, X>,
    f: (a: EB, cause: Cause.Cause<E>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A, E | E2, R | R2>
} = dual(
  3,
  <A, E, R, B, E2, R2, EB, X extends Cause.Cause<any>>(
    self: Effect.Effect<A, E, R>,
    filter: Filter.Filter<Cause.Cause<E>, EB, X>,
    f: (a: EB, cause: Cause.Cause<E>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A, E | E2, R | R2> =>
    catchCause(self, (cause) => {
      const result = filter(cause)
      if (Result.isFailure(result)) {
        return failCause(cause)
      }
      return andThen(internalCall(() => f(result.success, cause)), failCause(cause))
    })
)

/** @internal */
export const tapError: {
  <E, B, E2, R2>(
    f: (e: NoInfer<E>) => Effect.Effect<B, E2, R2>
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | E2, R | R2>
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (e: NoInfer<E>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A, E | E2, R | R2>
} = dual(
  2,
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (e: NoInfer<E>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A, E | E2, R | R2> => tapCauseFilter(self, findError as any, (e: any) => f(e)) as any
)

/** @internal */
export const tapErrorTag: {
  <const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>, E, A1, E1, R1>(
    k: K,
    f: (
      e: ExtractTag<NoInfer<E>, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>
    ) => Effect.Effect<A1, E1, R1>
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | E1, R1 | R>
  <
    A,
    E,
    R,
    const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>,
    R1,
    E1,
    A1
  >(
    self: Effect.Effect<A, E, R>,
    k: K,
    f: (e: ExtractTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>) => Effect.Effect<A1, E1, R1>
  ): Effect.Effect<A, E | E1, R | R1>
} = dual(
  3,
  <
    A,
    E,
    R,
    const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>,
    R1,
    E1,
    A1
  >(
    self: Effect.Effect<A, E, R>,
    k: K,
    f: (e: ExtractTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>) => Effect.Effect<A1, E1, R1>
  ): Effect.Effect<A, E | E1, R | R1> => {
    const predicate = Array.isArray(k)
      ? ((e: E): e is ExtractTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K> =>
        hasProperty(e, "_tag") && k.includes(e._tag))
      : isTagged(k as string)
    return tapError(
      self,
      (error) =>
        predicate(error)
          ? f(error as ExtractTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>)
          : void_
    )
  }
)

/** @internal */
export const tapDefect: {
  <E, B, E2, R2>(
    f: (defect: unknown) => Effect.Effect<B, E2, R2>
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | E2, R | R2>
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (defect: unknown) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A, E | E2, R | R2>
} = dual(
  2,
  <A, E, R, B, E2, R2>(
    self: Effect.Effect<A, E, R>,
    f: (defect: unknown) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<A, E | E2, R | R2> => tapCauseFilter(self, findDefect as any, (_: any) => f(_)) as any
)

/** @internal */
export const catchIf: {
  <E, EB extends E, A2, E2, R2, A3 = never, E3 = Exclude<E, EB>, R3 = never>(
    refinement: Predicate.Refinement<NoInfer<E>, EB>,
    f: (e: EB) => Effect.Effect<A2, E2, R2>,
    orElse?: ((e: Exclude<E, EB>) => Effect.Effect<A3, E3, R3>) | undefined
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | A2 | A3, E2 | E3, R | R2 | R3>
  <E, A2, E2, R2, A3 = never, E3 = E, R3 = never>(
    predicate: Predicate.Predicate<NoInfer<E>>,
    f: (e: NoInfer<E>) => Effect.Effect<A2, E2, R2>,
    orElse?: ((e: NoInfer<E>) => Effect.Effect<A3, E3, R3>) | undefined
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | A2 | A3, E2 | E3, R | R2 | R3>
  <A, E, R, EB extends E, A2, E2, R2, A3 = never, E3 = Exclude<E, EB>, R3 = never>(
    self: Effect.Effect<A, E, R>,
    refinement: Predicate.Refinement<E, EB>,
    f: (e: EB) => Effect.Effect<A2, E2, R2>,
    orElse?: ((e: Exclude<E, EB>) => Effect.Effect<A3, E3, R3>) | undefined
  ): Effect.Effect<A | A2 | A3, E2 | E3, R | R2 | R3>
  <A, E, R, A2, E2, R2, A3 = never, E3 = E, R3 = never>(
    self: Effect.Effect<A, E, R>,
    predicate: Predicate.Predicate<E>,
    f: (e: E) => Effect.Effect<A2, E2, R2>,
    orElse?: ((e: E) => Effect.Effect<A3, E3, R3>) | undefined
  ): Effect.Effect<A | A2 | A3, E2 | E3, R | R2 | R3>
} = dual(
  (args) => isEffect(args[0]),
  <A, E, R, A2, E2, R2, A3 = never, E3 = E, R3 = never>(
    self: Effect.Effect<A, E, R>,
    predicate: Predicate.Predicate<E>,
    f: (e: E) => Effect.Effect<A2, E2, R2>,
    orElse?: ((e: E) => Effect.Effect<A3, E3, R3>) | undefined
  ): Effect.Effect<A | A2 | A3, E2 | E3, R | R2 | R3> =>
    catchCause(self, (cause): Effect.Effect<A2 | A3, E2 | E3, R2 | R3> => {
      const error = findError(cause)
      if (Result.isFailure(error)) return failCause(error.failure)
      if (!predicate(error.success)) {
        return orElse ? internalCall(() => orElse(error.success as any)) : failCause(cause as any as Cause.Cause<E3>)
      }
      return internalCall(() => f(error.success as any))
    })
)

/** @internal */
export const catchFilter: {
  <E, EB, A2, E2, R2, X, A3 = never, E3 = X, R3 = never>(
    filter: Filter.Filter<NoInfer<E>, EB, X>,
    f: (e: EB) => Effect.Effect<A2, E2, R2>,
    orElse?: ((e: X) => Effect.Effect<A3, E3, R3>) | undefined
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | A2 | A3, E2 | E3, R | R2 | R3>
  <A, E, R, EB, A2, E2, R2, X, A3 = never, E3 = X, R3 = never>(
    self: Effect.Effect<A, E, R>,
    filter: Filter.Filter<NoInfer<E>, EB, X>,
    f: (e: EB) => Effect.Effect<A2, E2, R2>,
    orElse?: ((e: X) => Effect.Effect<A3, E3, R3>) | undefined
  ): Effect.Effect<A | A2 | A3, E2 | E3, R | R2 | R3>
} = dual(
  (args) => isEffect(args[0]),
  <A, E, R, EB, A2, E2, R2, X, A3 = never, E3 = X, R3 = never>(
    self: Effect.Effect<A, E, R>,
    filter: Filter.Filter<NoInfer<E>, EB, X>,
    f: (e: EB) => Effect.Effect<A2, E2, R2>,
    orElse?: ((e: X) => Effect.Effect<A3, E3, R3>) | undefined
  ): Effect.Effect<A | A2 | A3, E2 | E3, R | R2 | R3> =>
    catchCause(self, (cause): Effect.Effect<A2 | A3, E2 | E3, R2 | R3> => {
      const error = findError(cause)
      if (Result.isFailure(error)) return failCause(error.failure)
      const result = filter(error.success)
      if (Result.isFailure(result)) {
        return orElse ? internalCall(() => orElse(result.failure as any)) : failCause(cause as any as Cause.Cause<E3>)
      }
      return internalCall(() => f(result.success))
    })
)

/** @internal */
export const catchTag: {
  <
    const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>,
    E,
    A1,
    E1,
    R1,
    A2 = never,
    E2 = ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
    R2 = never
  >(
    k: K,
    f: (
      e: ExtractTag<NoInfer<E>, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>
    ) => Effect.Effect<A1, E1, R1>,
    orElse?:
      | ((e: ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>) => Effect.Effect<A2, E2, R2>)
      | undefined
  ): <A, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<A | A1 | A2, E1 | E2, R | R1 | R2>
  <
    A,
    E,
    R,
    const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>,
    R1,
    E1,
    A1,
    A2 = never,
    E2 = ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
    R2 = never
  >(
    self: Effect.Effect<A, E, R>,
    k: K,
    f: (e: ExtractTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>) => Effect.Effect<A1, E1, R1>,
    orElse?:
      | ((e: ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>) => Effect.Effect<A2, E2, R2>)
      | undefined
  ): Effect.Effect<A | A1 | A2, E1 | E2, R | R1 | R2>
} = dual(
  (args) => isEffect(args[0]),
  <
    A,
    E,
    R,
    const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>,
    R1,
    E1,
    A1,
    A2 = never,
    E2 = ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
    R2 = never
  >(
    self: Effect.Effect<A, E, R>,
    k: K,
    f: (e: ExtractTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>) => Effect.Effect<A1, E1, R1>,
    orElse?:
      | ((e: ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>) => Effect.Effect<A2, E2, R2>)
      | undefined
  ): Effect.Effect<A | A1 | A2, E1 | E2, R | R1 | R2> => {
    const pred = Array.isArray(k)
      ? ((e: E): e is any => hasProperty(e, "_tag") && k.includes(e._tag))
      : isTagged(k as string)
    return catchIf(self, pred, f, orElse as any) as any
  }
)

/** @internal */
export const catchTags: {
  <
    E,
    Cases extends (E extends { _tag: string } ? {
        [K in E["_tag"]]+?: (error: Extract<E, { _tag: K }>) => Effect.Effect<any, any, any>
      } :
      {}),
    A2 = never,
    E2 = Exclude<E, { _tag: keyof Cases }>,
    R2 = never
  >(
    cases: Cases,
    orElse?: ((e: Exclude<E, { _tag: keyof Cases }>) => Effect.Effect<A2, E2, R2>) | undefined
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<
    | A
    | A2
    | {
      [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Effect.Effect<infer A, any, any>) ? A : never
    }[keyof Cases],
    | E2
    | {
      [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Effect.Effect<any, infer E, any>) ? E : never
    }[keyof Cases],
    | R
    | R2
    | {
      [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Effect.Effect<any, any, infer R>) ? R : never
    }[keyof Cases]
  >
  <
    R,
    E,
    A,
    Cases extends (E extends { _tag: string } ? {
        [K in E["_tag"]]+?: (error: Extract<E, { _tag: K }>) => Effect.Effect<any, any, any>
      } :
      {}),
    A2 = never,
    E2 = Exclude<E, { _tag: keyof Cases }>,
    R2 = never
  >(
    self: Effect.Effect<A, E, R>,
    cases: Cases,
    orElse?: ((e: Exclude<E, { _tag: keyof Cases }>) => Effect.Effect<A2, E2, R2>) | undefined
  ): Effect.Effect<
    | A
    | A2
    | {
      [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Effect.Effect<infer A, any, any>) ? A : never
    }[keyof Cases],
    | E2
    | {
      [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Effect.Effect<any, infer E, any>) ? E : never
    }[keyof Cases],
    | R
    | R2
    | {
      [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Effect.Effect<any, any, infer R>) ? R : never
    }[keyof Cases]
  >
} = dual((args) => isEffect(args[0]), (self: Effect.Effect<any, any, any>, cases: Record<string, any>, orElse: any) => {
  let keys: Array<string>
  return catchFilter(
    self,
    (e) => {
      keys ??= Object.keys(cases)
      return hasProperty(e, "_tag") && isString(e["_tag"]) && keys.includes(e["_tag"])
        ? Result.succeed(e)
        : Result.fail(e)
    },
    (e: any) => internalCall(() => cases[e["_tag"] as string](e)),
    orElse
  ) as any
})

/** @internal */
export const catchReason: {
  <
    K extends Tags<E>,
    E,
    RK extends ReasonTags<ExtractTag<NoInfer<E>, K>>,
    A2,
    E2,
    R2,
    A3 = unassigned,
    E3 = never,
    R3 = never
  >(
    errorTag: K,
    reasonTag: RK,
    f: (
      reason: ExtractReason<ExtractTag<NoInfer<E>, K>, RK>,
      error: NarrowReason<ExtractTag<NoInfer<E>, K>, RK>
    ) => Effect.Effect<A2, E2, R2>,
    orElse?:
      | ((
        reasons: ExcludeReason<ExtractTag<NoInfer<E>, K>, RK>,
        error: OmitReason<ExtractTag<NoInfer<E>, K>, RK>
      ) => Effect.Effect<A3, E3, R3>)
      | undefined
  ): <A, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<
    A | A2 | Exclude<A3, unassigned>,
    (A3 extends unassigned ? E : ExcludeTag<E, K>) | E2 | E3,
    R | R2 | R3
  >
  <
    A,
    E,
    R,
    K extends Tags<E>,
    RK extends ReasonTags<ExtractTag<E, K>>,
    A2,
    E2,
    R2,
    A3 = unassigned,
    E3 = never,
    R3 = never
  >(
    self: Effect.Effect<A, E, R>,
    errorTag: K,
    reasonTag: RK,
    f: (
      reason: ExtractReason<ExtractTag<E, K>, RK>,
      error: NarrowReason<ExtractTag<E, K>, RK>
    ) => Effect.Effect<A2, E2, R2>,
    orElse?:
      | ((
        reasons: ExcludeReason<ExtractTag<E, K>, RK>,
        error: OmitReason<ExtractTag<E, K>, RK>
      ) => Effect.Effect<A3, E3, R3>)
      | undefined
  ): Effect.Effect<
    A | A2 | Exclude<A3, unassigned>,
    (A3 extends unassigned ? E : ExcludeTag<E, K>) | E2 | E3,
    R | R2 | R3
  >
} = dual(
  (args) => isEffect(args[0]),
  <
    A,
    E,
    R,
    K extends Tags<E>,
    RK extends ReasonTags<ExtractTag<E, K>>,
    A2,
    E2,
    R2,
    A3 = unassigned,
    E3 = never,
    R3 = never
  >(
    self: Effect.Effect<A, E, R>,
    errorTag: K,
    reasonTag: RK,
    f: (reason: ExtractReason<ExtractTag<E, K>, RK>, error: ExtractTag<E, K>) => Effect.Effect<A2, E2, R2>,
    orElse?:
      | ((
        reasons: ExcludeReason<ExtractTag<E, K>, RK>,
        error: OmitReason<ExtractTag<E, K>, RK>
      ) => Effect.Effect<A3, E3, R3>)
      | undefined
  ): Effect.Effect<
    A | A2 | Exclude<A3, unassigned>,
    (A3 extends unassigned ? E : ExcludeTag<E, K>) | E2 | E3,
    R | R2 | R3
  > =>
    catchIf(
      self,
      ((e: any) => isTagged(e, errorTag) && hasProperty(e, "reason")) as any,
      (e: any): Effect.Effect<A2 | A3, E | E2 | E3, R2 | R3> => {
        const reason = e.reason as any
        if (isTagged(reason, reasonTag)) return f(reason as any, e)
        return orElse ? internalCall(() => orElse(reason, e)) : fail(e)
      }
    ) as any
)

/** @internal */
export const catchReasons: {
  <
    K extends Tags<E>,
    E,
    Cases extends {
      [RK in ReasonTags<ExtractTag<NoInfer<E>, K>>]+?: (
        reason: ExtractReason<ExtractTag<NoInfer<E>, K>, RK>,
        error: NarrowReason<ExtractTag<NoInfer<E>, K>, RK>
      ) => Effect.Effect<any, any, any>
    },
    A2 = unassigned,
    E2 = never,
    R2 = never
  >(
    errorTag: K,
    cases: Cases,
    orElse?:
      | ((
        reason: ExcludeReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>,
        error: OmitReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>
      ) => Effect.Effect<A2, E2, R2>)
      | undefined
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<
    | A
    | Exclude<A2, unassigned>
    | { [RK in keyof Cases]: Cases[RK] extends (...args: Array<any>) => Effect.Effect<infer A, any, any> ? A : never }[
      keyof Cases
    ],
    | (A2 extends unassigned ? E : ExcludeTag<E, K>)
    | E2
    | { [RK in keyof Cases]: Cases[RK] extends (...args: Array<any>) => Effect.Effect<any, infer E, any> ? E : never }[
      keyof Cases
    ],
    | R
    | R2
    | { [RK in keyof Cases]: Cases[RK] extends (...args: Array<any>) => Effect.Effect<any, any, infer R> ? R : never }[
      keyof Cases
    ]
  >
  <
    A,
    E,
    R,
    K extends Tags<E>,
    Cases extends {
      [RK in ReasonTags<ExtractTag<E, K>>]+?: (
        reason: ExtractReason<ExtractTag<E, K>, RK>,
        error: NarrowReason<ExtractTag<E, K>, RK>
      ) => Effect.Effect<any, any, any>
    },
    A2 = unassigned,
    E2 = never,
    R2 = never
  >(
    self: Effect.Effect<A, E, R>,
    errorTag: K,
    cases: Cases,
    orElse?:
      | ((
        reason: ExcludeReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>,
        error: OmitReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>
      ) => Effect.Effect<A2, E2, R2>)
      | undefined
  ): Effect.Effect<
    | A
    | Exclude<A2, unassigned>
    | { [RK in keyof Cases]: Cases[RK] extends (...args: Array<any>) => Effect.Effect<infer A, any, any> ? A : never }[
      keyof Cases
    ],
    | (A2 extends unassigned ? E : ExcludeTag<E, K>)
    | E2
    | { [RK in keyof Cases]: Cases[RK] extends (...args: Array<any>) => Effect.Effect<any, infer E, any> ? E : never }[
      keyof Cases
    ],
    | R
    | R2
    | { [RK in keyof Cases]: Cases[RK] extends (...args: Array<any>) => Effect.Effect<any, any, infer R> ? R : never }[
      keyof Cases
    ]
  >
} = dual((args) => isEffect(args[0]), (self, errorTag, cases, orElse) => {
  let keys: Array<string>
  return catchIf(
    self,
    ((e: any) =>
      isTagged(e, errorTag) &&
      hasProperty(e, "reason") &&
      hasProperty(e.reason, "_tag") &&
      isString(e.reason._tag)) as any,
    (e: any) => {
      const reason = e.reason
      keys ??= Object.keys(cases)
      if (keys.includes(reason._tag)) {
        return internalCall(() => (cases as any)[reason._tag](reason, e))
      }
      return orElse ? internalCall(() => orElse(reason, e)) : fail(e)
    }
  )
})

/** @internal */
export const unwrapReason: {
  <
    K extends Effect.TagsWithReason<E>,
    E
  >(
    errorTag: K
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, ExcludeTag<E, K> | ReasonOf<ExtractTag<E, K>>, R>
  <
    A,
    E,
    R,
    K extends Effect.TagsWithReason<E>
  >(
    self: Effect.Effect<A, E, R>,
    errorTag: K
  ): Effect.Effect<A, ExcludeTag<E, K> | ReasonOf<ExtractTag<E, K>>, R>
} = dual(
  2,
  <
    A,
    E,
    R,
    K extends Effect.TagsWithReason<E>
  >(
    self: Effect.Effect<A, E, R>,
    errorTag: K
  ): Effect.Effect<A, ExcludeTag<E, K> | ReasonOf<ExtractTag<E, K>>, R> =>
    catchFilter(
      self,
      (e: any) => {
        if (isTagged(e, errorTag) && hasProperty(e, "reason")) {
          return Result.succeed(e.reason)
        }
        return Result.fail(e)
      },
      fail as any
    ) as any
)

/** @internal */
export const mapErrorCause: {
  <E, E2>(
    f: (e: Cause.Cause<E>) => Cause.Cause<E2>
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E2, R>
  <A, E, R, E2>(
    self: Effect.Effect<A, E, R>,
    f: (e: Cause.Cause<E>) => Cause.Cause<E2>
  ): Effect.Effect<A, E2, R>
} = dual(
  2,
  <A, E, R, E2>(
    self: Effect.Effect<A, E, R>,
    f: (e: Cause.Cause<E>) => Cause.Cause<E2>
  ): Effect.Effect<A, E2, R> => catchCause(self, (cause) => failCauseSync(() => f(cause)))
)

/** @internal */
export const mapError: {
  <E, E2>(
    f: (e: E) => E2
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E2, R>
  <A, E, R, E2>(
    self: Effect.Effect<A, E, R>,
    f: (e: E) => E2
  ): Effect.Effect<A, E2, R>
} = dual(
  2,
  <A, E, R, E2>(
    self: Effect.Effect<A, E, R>,
    f: (e: E) => E2
  ): Effect.Effect<A, E2, R> => catch_(self, (error) => failSync(() => f(error)))
)

/* @internal */
export const mapBoth: {
  <E, E2, A, A2>(
    options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 }
  ): <R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A2, E2, R>
  <A, E, R, E2, A2>(
    self: Effect.Effect<A, E, R>,
    options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 }
  ): Effect.Effect<A2, E2, R>
} = dual(2, <A, E, R, E2, A2>(
  self: Effect.Effect<A, E, R>,
  options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 }
): Effect.Effect<A2, E2, R> =>
  matchEffect(self, {
    onFailure: (e) => failSync(() => options.onFailure(e)),
    onSuccess: (a) => sync(() => options.onSuccess(a))
  }))

/** @internal */
export const orDie = <A, E, R>(
  self: Effect.Effect<A, E, R>
): Effect.Effect<A, never, R> => catch_(self, die)

/** @internal */
export const orElseSucceed: {
  <B>(
    f: LazyArg<B>
  ): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | B, never, R>
  <A, E, R, B>(
    self: Effect.Effect<A, E, R>,
    f: LazyArg<B>
  ): Effect.Effect<A | B, never, R>
} = dual(
  2,
  <A, E, R, B>(
    self: Effect.Effect<A, E, R>,
    f: LazyArg<B>
  ): Effect.Effect<A | B, never, R> => catch_(self, (_) => sync(f))
)

/** @internal */
export const eventually = <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, never, R> =>
  catch_(self, (_) => flatMap(yieldNow, () => eventually(self)))

/** @internal */
export const ignore: <
  Arg extends Effect.Effect<any, any, any> | {
    readonly log?: boolean | LogLevel.Severity | undefined
    readonly message?: string | undefined
  } | undefined = {
    readonly log?: boolean | LogLevel.Severity | undefined
    readonly message?: string | undefined
  }
>(
  effectOrOptions: Arg,
  options?: {
    readonly log?: boolean | LogLevel.Severity | undefined
    readonly message?: string | undefined
  } | undefined
) => [Arg] extends [Effect.Effect<infer _A, infer _E, infer _R>] ? Effect.Effect<void, never, _R>
  : <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<void, never, R> = dual(
    (args) => isEffect(args[0]),
    <A, E, R>(
      self: Effect.Effect<A, E, R>,
      options?: {
        readonly log?: boolean | LogLevel.Severity | undefined
        readonly message?: string | undefined
      } | undefined
    ): Effect.Effect<void, never, R> => {
      if (!options?.log) {
        return matchEffect(self, { onFailure: (_) => void_, onSuccess: (_) => void_ })
      }
      const logEffect = logWithLevel(options.log === true ? undefined : options.log)
      return matchCauseEffect(self, {
        onFailure(cause) {
          const failure = findFail(cause)
          return Result.isFailure(failure)
            ? failCause(failure.failure)
            : options.message === undefined
            ? logEffect(cause)
            : logEffect(options.message, cause)
        },
        onSuccess: (_) => void_
      })
    }
  )

/** @internal */
export const ignoreCause: <
  Arg extends Effect.Effect<any, any, any> | {
    readonly log?: boolean | LogLevel.Severity | undefined
    readonly message?: string | undefined
  } | undefined = {
    readonly log?: boolean | LogLevel.Severity | undefined
    readonly message?: string | undefined
  }
>(
  effectOrOptions: Arg,
  options?: {
    readonly log?: boolean | LogLevel.Severity | undefined
    readonly message?: string | undefined
  } | undefined
) => [Arg] extends [Effect.Effect<infer _A, infer _E, infer _R>] ? Effect.Effect<void, never, _R>
  : <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<void, never, R> = dual(
    (args) => isEffect(args[0]),
    <A, E, R>(
      self: Effect.Effect<A, E, R>,
      options?: {
        readonly log?: boolean | LogLevel.Severity | undefined
        readonly message?: string | undefined
      } | undefined
    ): Effect.Effect<void, never, R> => {
      if (!options?.log) {
        return matchCauseEffect(self, { onFailure: (_) => void_, onSuccess: (_) => void_ })
      }
      const logEffect = logWithLevel(options.log === true ? undefined : options.log)
      return matchCauseEffect(self, {
        onFailure: (cause) => options.message === undefined ? logEffect(cause) : logEffect(options.message, cause),
        onSuccess: (_) => void_
      })
    }
  )

/** @internal */
export const option = <A, E, R>(
  self: Effect.Effect<A, E, R>
): Effect.Effect<Option.Option<A>, never, R> => match(self, { onFailure: Option.none, onSuccess: Option.some })

/** @internal */
export const result = <A, E, R>(
  self: Effect.Effect<A, E, R>
): Effect.Effect<Result.Result<A, E>, never, R> =>
  matchEager(self, { onFailure: Result.fail, onSuccess: Result.succeed })

// ----------------------------------------------------------------------------
// pattern matching
// ----------------------------------------------------------------------------

/** @internal */
export const matchCauseEffect: {
  <E, A2, E2, R2, A, A3, E3, R3>(options: {
    readonly onFailure: (cause: Cause.Cause<E>) => Effect.Effect<A2, E2, R2>
    readonly onSuccess: (a: A) => Effect.Effect<A3, E3, R3>
  }): <R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<A2 | A3, E2 | E3, R2 | R3 | R>
  <A, E, R, A2, E2, R2, A3, E3, R3>(
    self: Effect.Effect<A, E, R>,
    options: {
      readonly onFailure: (cause: Cause.Cause<E>) => Effect.Effect<A2, E2, R2>
      readonly onSuccess: (a: A) => Effect.Effect<A3, E3, R3>
    }
  ): Effect.Effect<A2 | A3, E2 | E3, R2 | R3 | R>
} = dual(
  2,
  <A, E, R, A2, E2, R2, A3, E3, R3>(
    self: Effect.Effect<A, E, R>,
    options: {
      readonly onFailure: (cause: Cause.Cause<E>) => Effect.Effect<A2, E2, R2>
      readonly onSuccess: (a: A) => Effect.Effect<A3, E3, R3>
    }
  ): Effect.Effect<A2 | A3, E2 | E3, R2 | R3 | R> => {
    const primitive = Object.create(OnSuccessAndFailureProto)
    primitive[args] = self
    primitive[contA] = options.onSuccess.length !== 1 ? (a: A) => options.onSuccess(a) : options.onSuccess
    primitive[contE] = options.onFailure.length !== 1
      ? (cause: Cause.Cause<E>) => options.onFailure(cause)
      : options.onFailure
    return primitive
  }
)
const OnSuccessAndFailureProto = makePrimitiveProto({
  op: "OnSuccessAndFailure",
  [evaluate](this: any, fiber: FiberImpl): Primitive {
    fiber._stack.push(this)
    return this[args]
  }
})

/** @internal */
export const matchCause: {
  <E, A2, A, A3>(options: {
    readonly onFailure: (cause: Cause.Cause<E>) => A2
    readonly onSuccess: (a: A) => A3
  }): <R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A2 | A3, never, R>
  <A, E, R, A2, A3>(
    self: Effect.Effect<A, E, R>,
    options: {
      readonly onFailure: (cause: Cause.Cause<E>) => A2
      readonly onSuccess: (a: A) => A3
    }
  ): Effect.Effect<A2 | A3, never, R>
} = dual(
  2,
  <A, E, R, A2, A3>(
    self: Effect.Effect<A, E, R>,
    options: {
      readonly onFailure: (cause: Cause.Cause<E>) => A2
      readonly onSuccess: (a: A) => A3
    }
  ): Effect.Effect<A2 | A3, never, R> =>
    matchCauseEffect(self, {
      onFailure: (cause) => sync(() => options.onFailure(cause)),
      onSuccess: (value) => sync(() => options.onSuccess(value))
    })
)

/** @internal */
export const matchEffect: {
  <E, A2, E2, R2, A, A3, E3, R3>(options: {
    readonly onFailure: (e: E) => Effect.Effect<A2, E2, R2>
    readonly onSuccess: (a: A) => Effect.Effect<A3, E3, R3>
  }): <R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<A2 | A3, E2 | E3, R2 | R3 | R>
  <A, E, R, A2, E2, R2, A3, E3, R3>(
    self: Effect.Effect<A, E, R>,
    options: {
      readonly onFailure: (e: E) => Effect.Effect<A2, E2, R2>
      readonly onSuccess: (a: A) => Effect.Effect<A3, E3, R3>
    }
  ): Effect.Effect<A2 | A3, E2 | E3, R2 | R3 | R>
} = dual(
  2,
  <A, E, R, A2, E2, R2, A3, E3, R3>(
    self: Effect.Effect<A, E, R>,
    options: {
      readonly onFailure: (e: E) => Effect.Effect<A2, E2, R2>
      readonly onSuccess: (a: A) => Effect.Effect<A3, E3, R3>
    }
  ): Effect.Effect<A2 | A3, E2 | E3, R2 | R3 | R> =>
    matchCauseEffect(self, {
      onFailure: (cause) => {
        const fail = cause.reasons.find(isFailReason)
        return fail
          ? internalCall(() => options.onFailure(fail.error))
          : failCause(cause as Cause.Cause<never>)
      },
      onSuccess: options.onSuccess
    })
)

/** @internal */
export const match: {
  <E, A2, A, A3>(options: {
    readonly onFailure: (error: E) => A2
    readonly onSuccess: (value: A) => A3
  }): <R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A2 | A3, never, R>
  <A, E, R, A2, A3>(
    self: Effect.Effect<A, E, R>,
    options: {
      readonly onFailure: (error: E) => A2
      readonly onSuccess: (value: A) => A3
    }
  ): Effect.Effect<A2 | A3, never, R>
} = dual(
  2,
  <A, E, R, A2, A3>(
    self: Effect.Effect<A, E, R>,
    options: {
      readonly onFailure: (error: E) => A2
      readonly onSuccess: (value: A) => A3
    }
  ): Effect.Effect<A2 | A3, never, R> =>
    matchEffect(self, {
      onFailure: (error) => sync(() => options.onFailure(error)),
      onSuccess: (value) => sync(() => options.onSuccess(value))
    })
)

/** @internal */
export const matchEager: {
  <E, A2, A, A3>(options: {
    readonly onFailure: (error: E) => A2
    readonly onSuccess: (value: A) => A3
  }): <R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A2 | A3, never, R>
  <A, E, R, A2, A3>(
    self: Effect.Effect<A, E, R>,
    options: {
      readonly onFailure: (error: E) => A2
      readonly onSuccess: (value: A) => A3
    }
  ): Effect.Effect<A2 | A3, never, R>
} = dual(
  2,
  <A, E, R, A2, A3>(
    self: Effect.Effect<A, E, R>,
    options: {
      readonly onFailure: (error: E) => A2
      readonly onSuccess: (value: A) => A3
    }
  ): Effect.Effect<A2 | A3, never, R> => {
    if (effectIsExit(self)) {
      if (self._tag === "Success") return exitSucceed(options.onSuccess(self.value))
      const error = findError(self.cause)
      if (Result.isFailure(error)) return self as Exit.Exit<never>
      return exitSucceed(options.onFailure(error.success))
    }
    return match(self, options)
  }
)

/** @internal */
export const matchCauseEager: {
  <E, A2, A, A3>(options: {
    readonly onFailure: (cause: Cause.Cause<E>) => A2
    readonly onSuccess: (value: A) => A3
  }): <R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A2 | A3, never, R>
  <A, E, R, A2, A3>(
    self: Effect.Effect<A, E, R>,
    options: {
      readonly onFailure: (cause: Cause.Cause<E>) => A2
      readonly onSuccess: (value: A) => A3
    }
  ): Effect.Effect<A2 | A3, never, R>
} = dual(
  2,
  <A, E, R, A2, A3>(
    self: Effect.Effect<A, E, R>,
    options: {
      readonly onFailure: (cause: Cause.Cause<E>) => A2
      readonly onSuccess: (value: A) => A3
    }
  ): Effect.Effect<A2 | A3, never, R> => {
    if (effectIsExit(self)) {
      if (self._tag === "Success") return exitSucceed(options.onSuccess(self.value))
      return exitSucceed(options.onFailure(self.cause))
    }
    return matchCause(self, options)
  }
)

/** @internal */
export const exit = <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<Exit.Exit<A, E>, never, R> =>
  effectIsExit(self) ? exitSucceed(self) : exitPrimitive(self)

const exitPrimitive: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<Exit.Exit<A, E>, never, R> =
  makePrimitive({
    op: "Exit",
    [evaluate](fiber): Primitive {
      fiber._stack.push(this)
      return this[args] as any
    },
    [contA](value, _, exit) {
      return succeed(exit ?? exitSucceed(value))
    },
    [contE](cause, _, exit) {
      return succeed(exit ?? exitFailCause(cause))
    }
  })

// ----------------------------------------------------------------------------
// Condition checking
// ----------------------------------------------------------------------------

/** @internal */
export const isFailure: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<boolean, never, R> = matchEager({
  onFailure: () => true,
  onSuccess: () => false
})

/** @internal */
export const isSuccess: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<boolean, never, R> = matchEager({
  onFailure: () => false,
  onSuccess: () => true
})

// ----------------------------------------------------------------------------
// delays & timeouts
// ----------------------------------------------------------------------------

/** @internal */
export const delay: {
  (
    duration: Duration.Input
  ): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    duration: Duration.Input
  ): Effect.Effect<A, E, R>
} = dual(
  2,
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    duration: Duration.Input
  ): Effect.Effect<A, E, R> => andThen(sleep(duration), self)
)

/** @internal */
export const timeoutOrElse: {
  <A2, E2, R2>(options: {
    readonly duration: Duration.Input
    readonly orElse: LazyArg<Effect.Effect<A2, E2, R2>>
  }): <A, E, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<A | A2, E | E2, R | R2>
  <A, E, R, A2, E2, R2>(
    self: Effect.Effect<A, E, R>,
    options: {
      readonly duration: Duration.Input
      readonly orElse: LazyArg<Effect.Effect<A2, E2, R2>>
    }
  ): Effect.Effect<A | A2, E | E2, R | R2>
} = dual(
  2,
  <A, E, R, A2, E2, R2>(
    self: Effect.Effect<A, E, R>,
    options: {
      readonly duration: Duration.Input
      readonly orElse: LazyArg<Effect.Effect<A2, E2, R2>>
    }
  ): Effect.Effect<A | A2, E | E2, R | R2> =>
    raceFirst(
      self,
      flatMap(sleep(options.duration), options.orElse)
    )
)

/** @internal */
export const timeout: {
  (
    duration: Duration.Input
  ): <A, E, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<A, E | Cause.TimeoutError, R>
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    duration: Duration.Input
  ): Effect.Effect<A, E | Cause.TimeoutError, R>
} = dual(
  2,
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    duration: Duration.Input
  ): Effect.Effect<A, E | TimeoutError, R> =>
    timeoutOrElse(self, {
      duration,
      orElse: () => fail(new TimeoutError())
    })
)

/** @internal */
export const timeoutOption: {
  (
    duration: Duration.Input
  ): <A, E, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<Option.Option<A>, E, R>
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    duration: Duration.Input
  ): Effect.Effect<Option.Option<A>, E, R>
} = dual(
  2,
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    duration: Duration.Input
  ): Effect.Effect<Option.Option<A>, E, R> =>
    raceFirst(
      asSome(self),
      as(sleep(duration), Option.none())
    )
)

/** @internal */
export const timed = <A, E, R>(
  self: Effect.Effect<A, E, R>
): Effect.Effect<[duration: Duration.Duration, result: A], E, R> =>
  clockWith((clock) => {
    const start = clock.currentTimeNanosUnsafe()
    return map(self, (a) => [Duration.nanos(clock.currentTimeNanosUnsafe() - start), a])
  })

// ----------------------------------------------------------------------------
// resources & finalization
// ----------------------------------------------------------------------------

/** @internal */
export const ScopeTypeId = "~effect/Scope"

/** @internal */
export const ScopeCloseableTypeId = "~effect/Scope/Closeable"

/** @internal */
export const scopeTag: Context.Service<Scope.Scope, Scope.Scope> = Context.Service<Scope.Scope>("effect/Scope")

/** @internal */
export const scopeClose = <A, E>(self: Scope.Scope, exit_: Exit.Exit<A, E>) =>
  suspend(() => scopeCloseUnsafe(self, exit_) ?? void_)

/** @internal */
export const scopeCloseUnsafe = <A, E>(self: Scope.Scope, exit_: Exit.Exit<A, E>) => {
  if (self.state._tag === "Closed") return
  const closed: Scope.State.Closed = { _tag: "Closed", exit: exit_ }
  if (self.state._tag === "Empty") {
    self.state = closed
    return
  }
  const { finalizers } = self.state
  self.state = closed
  if (finalizers.size === 0) {
    return
  } else if (finalizers.size === 1) {
    return finalizers.values().next().value!(exit_)
  }
  return scopeCloseFinalizers(self, finalizers, exit_)
}

const scopeCloseFinalizers = fnUntraced(function*<A, E>(
  self: Scope.Scope,
  finalizers: Scope.State.Open["finalizers"],
  exit_: Exit.Exit<A, E>
) {
  let exits: Array<Exit.Exit<any, never>> = []
  const fibers: Array<Fiber.Fiber<any, never>> = []
  const arr = Array.from(finalizers.values())
  const parent = getCurrentFiber()!
  for (let i = arr.length - 1; i >= 0; i--) {
    const finalizer = arr[i]
    if (self.strategy === "sequential") {
      exits.push(yield* exit(finalizer(exit_)))
    } else {
      fibers.push(forkUnsafe(parent, finalizer(exit_), true, true, "inherit"))
    }
  }
  if (fibers.length > 0) {
    exits = yield* fiberAwaitAll(fibers)
  }
  return yield* exitAsVoidAll(exits)
})

/** @internal */
export const scopeFork = (scope: Scope.Scope, finalizerStrategy?: "sequential" | "parallel") =>
  sync(() => scopeForkUnsafe(scope, finalizerStrategy))

/** @internal */
export const scopeForkUnsafe = (scope: Scope.Scope, finalizerStrategy?: "sequential" | "parallel") => {
  const newScope = scopeMakeUnsafe(finalizerStrategy)
  if (scope.state._tag === "Closed") {
    newScope.state = scope.state
    return newScope
  }
  const key = {}
  scopeAddFinalizerUnsafe(scope, key, (exit) => scopeClose(newScope, exit))
  scopeAddFinalizerUnsafe(newScope, key, (_) => sync(() => scopeRemoveFinalizerUnsafe(scope, key)))
  return newScope
}

/** @internal */
export const scopeAddFinalizerExit = (
  scope: Scope.Scope,
  finalizer: (exit: Exit.Exit<any, any>) => Effect.Effect<unknown>
): Effect.Effect<void> => {
  return suspend(() => {
    if (scope.state._tag === "Closed") {
      return finalizer(scope.state.exit)
    }
    scopeAddFinalizerUnsafe(scope, {}, finalizer)
    return void_
  })
}

/** @internal */
export const scopeAddFinalizer = (
  scope: Scope.Scope,
  finalizer: Effect.Effect<unknown>
): Effect.Effect<void> => scopeAddFinalizerExit(scope, constant(finalizer))

/** @internal */
export const scopeAddFinalizerUnsafe = (
  scope: Scope.Scope,
  key: {},
  finalizer: (exit: Exit.Exit<any, any>) => Effect.Effect<unknown>
): void => {
  if (scope.state._tag === "Empty") {
    scope.state = { _tag: "Open", finalizers: new Map([[key, finalizer]]) }
  } else if (scope.state._tag === "Open") {
    scope.state.finalizers.set(key, finalizer)
  }
}

/** @internal */
export const scopeRemoveFinalizerUnsafe = (
  scope: Scope.Scope,
  key: {}
): void => {
  if (scope.state._tag === "Open") {
    scope.state.finalizers.delete(key)
  }
}

/** @internal */
export const scopeMakeUnsafe = (finalizerStrategy: "sequential" | "parallel" = "sequential"): Scope.Closeable => ({
  [ScopeCloseableTypeId]: ScopeCloseableTypeId,
  [ScopeTypeId]: ScopeTypeId,
  strategy: finalizerStrategy,
  state: constScopeEmpty
})

const constScopeEmpty = { _tag: "Empty" } as const

/** @internal */
export const scopeMake = (finalizerStrategy?: "sequential" | "parallel"): Effect.Effect<Scope.Closeable> =>
  sync(() => scopeMakeUnsafe(finalizerStrategy))

/** @internal */
export const scope: Effect.Effect<Scope.Scope, never, Scope.Scope> = scopeTag.asEffect()

/** @internal */
export const provideScope: {
  (value: Scope.Scope): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Scope.Scope>>
  <A, E, R>(self: Effect.Effect<A, E, R>, value: Scope.Scope): Effect.Effect<A, E, Exclude<R, Scope.Scope>>
} = provideService(scopeTag)

/** @internal */
export const scoped = <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, Exclude<R, Scope.Scope>> =>
  withFiber((fiber) => {
    const prev = fiber.context
    const scope = scopeMakeUnsafe()
    fiber.setContext(Context.add(fiber.context, scopeTag, scope))
    return onExitPrimitive(self, (exit) => {
      fiber.setContext(prev)
      return scopeCloseUnsafe(scope, exit)
    })
  }) as any

/** @internal */
export const scopeUse: {
  (
    scope: Scope.Closeable
  ): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Scope.Scope>>
  <A, E, R>(self: Effect.Effect<A, E, R>, scope: Scope.Closeable): Effect.Effect<A, E, Exclude<R, Scope.Scope>>
} = dual(
  2,
  <A, E, R>(self: Effect.Effect<A, E, R>, scope: Scope.Closeable): Effect.Effect<A, E, Exclude<R, Scope.Scope>> =>
    onExit(provideScope(self, scope), (exit) => suspend(() => scopeCloseUnsafe(scope, exit) ?? void_))
)

/** @internal */
export const scopedWith = <A, E, R>(
  f: (scope: Scope.Scope) => Effect.Effect<A, E, R>
): Effect.Effect<A, E, R> =>
  suspend(() => {
    const scope = scopeMakeUnsafe()
    return onExit(f(scope), (exit) => suspend(() => scopeCloseUnsafe(scope, exit) ?? void_))
  })

/** @internal */
export const acquireRelease = <A, E, R, R2>(
  acquire: Effect.Effect<A, E, R>,
  release: (a: A, exit: Exit.Exit<unknown, unknown>) => Effect.Effect<unknown, never, R2>,
  options?: { readonly interruptible?: boolean }
): Effect.Effect<A, E, R | R2 | Scope.Scope> =>
  contextWith((context: Context.Context<R2>) =>
    uninterruptibleMask((restore) =>
      flatMap(
        scope,
        (scope) =>
          tap(
            options?.interruptible ? restore(acquire) : acquire,
            (a) => scopeAddFinalizerExit(scope, (exit) => provideContext(release(a, exit), context))
          )
      )
    )
  )

/** @internal */
export const addFinalizer = <R>(
  finalizer: (exit: Exit.Exit<unknown, unknown>) => Effect.Effect<void, never, R>
): Effect.Effect<void, never, R | Scope.Scope> =>
  flatMap(
    scope,
    (scope) =>
      contextWith((context: Context.Context<R>) =>
        scopeAddFinalizerExit(scope, (exit) => provideContext(finalizer(exit), context))
      )
  )

/** @internal */
export const onExitPrimitive: <A, E, R, XE = never, XR = never>(
  self: Effect.Effect<A, E, R>,
  f: (exit: Exit.Exit<A, E>) => Effect.Effect<void, XE, XR> | undefined,
  interruptible?: boolean
) => Effect.Effect<A, E | XE, R | XR> = makePrimitive({
  op: "OnExit",
  single: false,
  [evaluate](fiber: FiberImpl) {
    fiber._stack.push(this)
    return this[args][0]
  },
  [contAll](fiber) {
    if (fiber.interruptible && this[args][2] !== true) {
      fiber._stack.push(setInterruptibleTrue)
      fiber.interruptible = false
    }
  },
  [contA](value, _, exit) {
    exit ??= exitSucceed(value)
    const eff = this[args][1](exit)
    return eff ? flatMap(eff, (_) => exit) : exit
  },
  [contE](cause, _, exit) {
    exit ??= exitFailCause(cause)
    const eff = this[args][1](exit)
    return eff ? flatMap(eff, (_) => exit) : exit
  }
})

/** @internal */
export const onExit: {
  <A, E, XE = never, XR = never>(
    f: (exit: Exit.Exit<A, E>) => Effect.Effect<void, XE, XR>
  ): <R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | XE, R | XR>
  <A, E, R, XE = never, XR = never>(
    self: Effect.Effect<A, E, R>,
    f: (exit: Exit.Exit<A, E>) => Effect.Effect<void, XE, XR>
  ): Effect.Effect<A, E | XE, R | XR>
} = dual(2, onExitPrimitive)

/** @internal */
export const ensuring: {
  <XE, XR>(
    finalizer: Effect.Effect<void, XE, XR>
  ): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | XE, R | XR>
  <A, E, R, XE, XR>(
    self: Effect.Effect<A, E, R>,
    finalizer: Effect.Effect<void, XE, XR>
  ): Effect.Effect<A, E | XE, R | XR>
} = dual(
  2,
  <A, E, R, XE, XR>(
    self: Effect.Effect<A, E, R>,
    finalizer: Effect.Effect<void, XE, XR>
  ): Effect.Effect<A, E | XE, R | XR> => onExit(self, (_) => finalizer)
)

/** @internal */
export const onExitIf: {
  <A, E, XE, XR>(
    predicate: Predicate.Predicate<Exit.Exit<NoInfer<A>, NoInfer<E>>>,
    f: (exit: Exit.Exit<NoInfer<A>, NoInfer<E>>) => Effect.Effect<void, XE, XR>
  ): <R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | XE, R | XR>
  <A, E, R, XE, XR>(
    self: Effect.Effect<A, E, R>,
    predicate: Predicate.Predicate<Exit.Exit<NoInfer<A>, NoInfer<E>>>,
    f: (exit: Exit.Exit<NoInfer<A>, NoInfer<E>>) => Effect.Effect<void, XE, XR>
  ): Effect.Effect<A, E | XE, R | XR>
} = dual(
  3,
  <A, E, R, XE, XR>(
    self: Effect.Effect<A, E, R>,
    predicate: Predicate.Predicate<Exit.Exit<NoInfer<A>, NoInfer<E>>>,
    f: (exit: Exit.Exit<NoInfer<A>, NoInfer<E>>) => Effect.Effect<void, XE, XR>
  ): Effect.Effect<A, E | XE, R | XR> =>
    onExit(self, (exit) => {
      if (!predicate(exit)) {
        return void_
      }
      return f(exit)
    })
)

/** @internal */
export const onExitFilter: {
  <A, E, XE, XR, B, X>(
    filter: Filter.Filter<Exit.Exit<NoInfer<A>, NoInfer<E>>, B, X>,
    f: (b: B, exit: Exit.Exit<NoInfer<A>, NoInfer<E>>) => Effect.Effect<void, XE, XR>
  ): <R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | XE, R | XR>
  <A, E, R, XE, XR, B, X>(
    self: Effect.Effect<A, E, R>,
    filter: Filter.Filter<Exit.Exit<NoInfer<A>, NoInfer<E>>, B, X>,
    f: (b: B, exit: Exit.Exit<NoInfer<A>, NoInfer<E>>) => Effect.Effect<void, XE, XR>
  ): Effect.Effect<A, E | XE, R | XR>
} = dual(
  3,
  <A, E, R, XE, XR, B, X>(
    self: Effect.Effect<A, E, R>,
    filter: Filter.Filter<Exit.Exit<NoInfer<A>, NoInfer<E>>, B, X>,
    f: (b: B, exit: Exit.Exit<NoInfer<A>, NoInfer<E>>) => Effect.Effect<void, XE, XR>
  ): Effect.Effect<A, E | XE, R | XR> =>
    onExit(self, (exit) => {
      const b = filter(exit)
      return Result.isFailure(b) ? void_ : f(b.success, exit)
    })
)

/** @internal */
export const onError: {
  <A, E, XE, XR>(
    f: (cause: Cause.Cause<NoInfer<E>>) => Effect.Effect<void, XE, XR>
  ): <R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | XE, R | XR>
  <A, E, R, XE, XR>(
    self: Effect.Effect<A, E, R>,
    f: (cause: Cause.Cause<NoInfer<E>>) => Effect.Effect<void, XE, XR>
  ): Effect.Effect<A, E | XE, R | XR>
} = dual(
  2,
  <A, E, R, XE, XR>(
    self: Effect.Effect<A, E, R>,
    f: (cause: Cause.Cause<NoInfer<E>>) => Effect.Effect<void, XE, XR>
  ): Effect.Effect<A, E | XE, R | XR> => onExitFilter(self, exitFilterCause as any, f as any) as any
)

/** @internal */
export const onErrorIf: {
  <E, XE, XR>(
    predicate: Predicate.Predicate<Cause.Cause<E>>,
    f: (cause: Cause.Cause<E>) => Effect.Effect<void, XE, XR>
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | XE, R | XR>
  <A, E, R, XE, XR>(
    self: Effect.Effect<A, E, R>,
    predicate: Predicate.Predicate<Cause.Cause<E>>,
    f: (cause: Cause.Cause<E>) => Effect.Effect<void, XE, XR>
  ): Effect.Effect<A, E | XE, R | XR>
} = dual(
  3,
  <A, E, R, XE, XR>(
    self: Effect.Effect<A, E, R>,
    predicate: Predicate.Predicate<Cause.Cause<E>>,
    f: (cause: Cause.Cause<E>) => Effect.Effect<void, XE, XR>
  ): Effect.Effect<A, E | XE, R | XR> =>
    onExitIf(
      self,
      (exit): exit is Exit.Failure<A, E> => {
        if (exit._tag !== "Failure") {
          return false
        }
        return predicate(exit.cause)
      },
      (exit) => f((exit as Exit.Failure<A, E>).cause)
    ) as any
)

/** @internal */
export const onErrorFilter: {
  <A, E, EB, X, XE, XR>(
    filter: Filter.Filter<Cause.Cause<E>, EB, X>,
    f: (failure: EB, cause: Cause.Cause<E>) => Effect.Effect<void, XE, XR>
  ): <R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | XE, R | XR>
  <A, E, R, EB, X, XE, XR>(
    self: Effect.Effect<A, E, R>,
    filter: Filter.Filter<Cause.Cause<E>, EB, X>,
    f: (failure: EB, cause: Cause.Cause<E>) => Effect.Effect<void, XE, XR>
  ): Effect.Effect<A, E | XE, R | XR>
} = dual(
  3,
  <A, E, R, EB, X, XE, XR>(
    self: Effect.Effect<A, E, R>,
    filter: Filter.Filter<Cause.Cause<E>, EB, X>,
    f: (failure: EB, cause: Cause.Cause<E>) => Effect.Effect<void, XE, XR>
  ): Effect.Effect<A, E | XE, R | XR> =>
    onExit(self, (exit) => {
      if (exit._tag !== "Failure") {
        return void_
      }
      const result = filter(exit.cause)
      return Result.isFailure(result) ? void_ : f(result.success, exit.cause)
    })
)

/** @internal */
export const onInterrupt: {
  <XE, XR>(
    finalizer: (interruptors: ReadonlySet<number>) => Effect.Effect<void, XE, XR>
  ): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | XE, R | XR>
  <A, E, R, XE, XR>(
    self: Effect.Effect<A, E, R>,
    finalizer: (interruptors: ReadonlySet<number>) => Effect.Effect<void, XE, XR>
  ): Effect.Effect<A, E | XE, R | XR>
} = dual(
  2,
  <A, E, R, XE, XR>(
    self: Effect.Effect<A, E, R>,
    finalizer: (interruptors: ReadonlySet<number>) => Effect.Effect<void, XE, XR>
  ): Effect.Effect<A, E | XE, R | XR> => onErrorFilter(causeFilterInterruptors as any, finalizer)(self) as any
)

/** @internal */
export const acquireUseRelease = <Resource, E, R, A, E2, R2, E3, R3>(
  acquire: Effect.Effect<Resource, E, R>,
  use: (a: Resource) => Effect.Effect<A, E2, R2>,
  release: (a: Resource, exit: Exit.Exit<A, E2>) => Effect.Effect<void, E3, R3>
): Effect.Effect<A, E | E2 | E3, R | R2 | R3> =>
  uninterruptibleMask((restore) =>
    flatMap(acquire, (a) =>
      onExitPrimitive(
        restore(use(a)),
        (exit) => release(a, exit),
        true
      ))
  )

// ----------------------------------------------------------------------------
// Caching
// ----------------------------------------------------------------------------

/** @internal */
export const cachedInvalidateWithTTL: {
  (timeToLive: Duration.Input): <A, E, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<[Effect.Effect<A, E, R>, Effect.Effect<void>]>
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    timeToLive: Duration.Input
  ): Effect.Effect<[Effect.Effect<A, E, R>, Effect.Effect<void>]>
} = dual(2, <A, E, R>(
  self: Effect.Effect<A, E, R>,
  ttl: Duration.Input
): Effect.Effect<[Effect.Effect<A, E, R>, Effect.Effect<void>]> =>
  sync(() => {
    const ttlMillis = Duration.toMillis(Duration.fromInputUnsafe(ttl))
    const isFinite = Number.isFinite(ttlMillis)
    const latch = makeLatchUnsafe(false)
    let expiresAt = 0
    let running = false
    let exit: Exit.Exit<A, E> | undefined
    const wait = flatMap(latch.await, () => exit!)
    return [
      withFiber((fiber) => {
        const clock = fiber.getRef(ClockRef)
        const now = isFinite ? clock.currentTimeMillisUnsafe() : 0
        if (running || now < expiresAt) return exit ?? wait
        running = true
        latch.closeUnsafe()
        exit = undefined
        return onExit(self, (exit_) =>
          sync(() => {
            running = false
            expiresAt = clock.currentTimeMillisUnsafe() + ttlMillis
            exit = exit_
            latch.openUnsafe()
          }))
      }),
      sync(() => {
        expiresAt = 0
        latch.closeUnsafe()
        exit = undefined
      })
    ]
  }))

/** @internal */
export const cachedWithTTL: {
  (
    timeToLive: Duration.Input
  ): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<Effect.Effect<A, E, R>>
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    timeToLive: Duration.Input
  ): Effect.Effect<Effect.Effect<A, E, R>>
} = dual(
  2,
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    timeToLive: Duration.Input
  ): Effect.Effect<Effect.Effect<A, E, R>> => map(cachedInvalidateWithTTL(self, timeToLive), (tuple) => tuple[0])
)

/** @internal */
export const cached = <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<Effect.Effect<A, E, R>> =>
  cachedWithTTL(self, Duration.infinity)

// ----------------------------------------------------------------------------
// interruption
// ----------------------------------------------------------------------------

/** @internal */
export const interrupt: Effect.Effect<never> = withFiber((fiber) => failCause(causeInterrupt(fiber.id)))

/** @internal */
export const uninterruptible = <A, E, R>(
  self: Effect.Effect<A, E, R>
): Effect.Effect<A, E, R> =>
  withFiber((fiber) => {
    if (!fiber.interruptible) return self
    fiber.interruptible = false
    fiber._stack.push(setInterruptibleTrue)
    return self
  })

const setInterruptible: (interruptible: boolean) => Primitive = makePrimitive({
  op: "SetInterruptible",
  [contAll](fiber) {
    fiber.interruptible = this[args]
    if (fiber._interruptedCause && fiber.interruptible) {
      return () => failCause(fiber._interruptedCause!)
    }
  }
})
const setInterruptibleTrue = setInterruptible(true)
const setInterruptibleFalse = setInterruptible(false)

/** @internal */
export const interruptible = <A, E, R>(
  self: Effect.Effect<A, E, R>
): Effect.Effect<A, E, R> =>
  withFiber((fiber) => {
    if (fiber.interruptible) return self
    fiber.interruptible = true
    fiber._stack.push(setInterruptibleFalse)
    if (fiber._interruptedCause) return failCause(fiber._interruptedCause)
    return self
  })

/** @internal */
export const uninterruptibleMask = <A, E, R>(
  f: (
    restore: <A, E, R>(
      effect: Effect.Effect<A, E, R>
    ) => Effect.Effect<A, E, R>
  ) => Effect.Effect<A, E, R>
): Effect.Effect<A, E, R> =>
  withFiber((fiber) => {
    if (!fiber.interruptible) return f(identity)
    fiber.interruptible = false
    fiber._stack.push(setInterruptibleTrue)
    return f(interruptible)
  })

/** @internal */
export const interruptibleMask = <A, E, R>(
  f: (
    restore: <A, E, R>(
      effect: Effect.Effect<A, E, R>
    ) => Effect.Effect<A, E, R>
  ) => Effect.Effect<A, E, R>
): Effect.Effect<A, E, R> =>
  withFiber((fiber) => {
    if (fiber.interruptible) return f(identity)
    fiber.interruptible = true
    fiber._stack.push(setInterruptibleFalse)
    return f(uninterruptible)
  })

/** @internal */
export const abortSignal: Effect.Effect<AbortSignal, never, Scope.Scope> = map(
  acquireRelease(
    sync(() => new AbortController()),
    (controller) => sync(() => controller.abort())
  ),
  (_) => _.signal
)

// ========================================================================
// collecting & elements
// ========================================================================

/** @internal */
export const all = <
  const Arg extends
    | Iterable<Effect.Effect<any, any, any>>
    | Record<string, Effect.Effect<any, any, any>>,
  O extends {
    readonly concurrency?: Concurrency | undefined
    readonly discard?: boolean | undefined
    readonly mode?: "default" | "result" | undefined
  }
>(
  arg: Arg,
  options?: O
): Effect.All.Return<Arg, O> => {
  if (isIterable(arg)) {
    return options?.mode === "result"
      ? (forEach as any)(arg, result, options)
      : (forEach as any)(arg, identity, options)
  } else if (options?.discard) {
    return options.mode === "result"
      ? (forEach as any)(Object.values(arg), result, options)
      : (forEach as any)(Object.values(arg), identity, options)
  }
  return suspend(() => {
    const out: Record<string, unknown> = {}
    return as(
      forEach(
        Object.entries(arg),
        ([key, effect]) =>
          map(options?.mode === "result" ? result(effect) : effect, (value) => {
            out[key] = value
          }),
        {
          discard: true,
          concurrency: options?.concurrency
        }
      ),
      out
    )
  }) as any
}

/** @internal */
export const partition: {
  <A, B, E, R>(
    f: (a: A, i: number) => Effect.Effect<B, E, R>,
    options?: { readonly concurrency?: Concurrency | undefined }
  ): (elements: Iterable<A>) => Effect.Effect<[excluded: Array<E>, satisfying: Array<B>], never, R>
  <A, B, E, R>(
    elements: Iterable<A>,
    f: (a: A, i: number) => Effect.Effect<B, E, R>,
    options?: { readonly concurrency?: Concurrency | undefined }
  ): Effect.Effect<[excluded: Array<E>, satisfying: Array<B>], never, R>
} = dual(
  (args) => isIterable(args[0]) && !isEffect(args[0]),
  <A, B, E, R>(
    elements: Iterable<A>,
    f: (a: A, i: number) => Effect.Effect<B, E, R>,
    options?: { readonly concurrency?: Concurrency | undefined }
  ): Effect.Effect<[excluded: Array<E>, satisfying: Array<B>], never, R> =>
    map(
      forEach(elements, (a, i) => result(f(a, i)), options),
      (results) => Arr.partition(results, identity)
    )
)

/** @internal */
export const validate: {
  <A, B, E, R>(
    f: (a: A, i: number) => Effect.Effect<B, E, R>,
    options?: {
      readonly concurrency?: Concurrency | undefined
      readonly discard?: false | undefined
    } | undefined
  ): (elements: Iterable<A>) => Effect.Effect<Array<B>, Arr.NonEmptyArray<E>, R>
  <A, B, E, R>(
    f: (a: A, i: number) => Effect.Effect<B, E, R>,
    options: {
      readonly concurrency?: Concurrency | undefined
      readonly discard: true
    }
  ): (elements: Iterable<A>) => Effect.Effect<void, Arr.NonEmptyArray<E>, R>
  <A, B, E, R>(
    elements: Iterable<A>,
    f: (a: A, i: number) => Effect.Effect<B, E, R>,
    options?: {
      readonly concurrency?: Concurrency | undefined
      readonly discard?: false | undefined
    } | undefined
  ): Effect.Effect<Array<B>, Arr.NonEmptyArray<E>, R>
  <A, B, E, R>(
    elements: Iterable<A>,
    f: (a: A, i: number) => Effect.Effect<B, E, R>,
    options: {
      readonly concurrency?: Concurrency | undefined
      readonly discard: true
    }
  ): Effect.Effect<void, Arr.NonEmptyArray<E>, R>
} = dual(
  (args) => isIterable(args[0]) && !isEffect(args[0]),
  <A, B, E, R>(
    elements: Iterable<A>,
    f: (a: A, i: number) => Effect.Effect<B, E, R>,
    options?: {
      readonly concurrency?: Concurrency | undefined
      readonly discard?: boolean | undefined
    } | undefined
  ): Effect.Effect<Array<B> | void, Arr.NonEmptyArray<E>, R> =>
    flatMap(
      partition(elements, f, { concurrency: options?.concurrency }),
      ([excluded, satisfying]) => {
        if (Arr.isArrayNonEmpty(excluded)) {
          return fail(excluded)
        }
        return options?.discard ? void_ : succeed(satisfying)
      }
    )
)

/** @internal */
export const findFirst: {
  <A, E, R>(
    predicate: (a: NoInfer<A>, i: number) => Effect.Effect<boolean, E, R>
  ): (elements: Iterable<A>) => Effect.Effect<Option.Option<A>, E, R>
  <A, E, R>(
    elements: Iterable<A>,
    predicate: (a: NoInfer<A>, i: number) => Effect.Effect<boolean, E, R>
  ): Effect.Effect<Option.Option<A>, E, R>
} = dual(
  (args) => isIterable(args[0]) && !isEffect(args[0]),
  <A, E, R>(
    elements: Iterable<A>,
    predicate: (a: A, i: number) => Effect.Effect<boolean, E, R>
  ): Effect.Effect<Option.Option<A>, E, R> =>
    suspend(() => {
      const iterator = elements[Symbol.iterator]()
      const next = iterator.next()
      if (!next.done) {
        return findFirstLoop(iterator, 0, predicate, next.value)
      }
      return succeed(Option.none())
    })
)

const findFirstLoop = <A, E, R>(
  iterator: Iterator<A>,
  index: number,
  predicate: (a: A, i: number) => Effect.Effect<boolean, E, R>,
  value: A
): Effect.Effect<Option.Option<A>, E, R> =>
  flatMap(predicate(value, index), (keep) => {
    if (keep) {
      return succeed(Option.some(value))
    }
    const next = iterator.next()
    if (!next.done) {
      return findFirstLoop(iterator, index + 1, predicate, next.value)
    }
    return succeed(Option.none())
  })

/** @internal */
export const findFirstFilter: {
  <A, B, X, E, R>(
    filter: (input: NoInfer<A>, i: number) => Effect.Effect<Result.Result<B, X>, E, R>
  ): (elements: Iterable<A>) => Effect.Effect<Option.Option<B>, E, R>
  <A, B, X, E, R>(
    elements: Iterable<A>,
    filter: (input: NoInfer<A>, i: number) => Effect.Effect<Result.Result<B, X>, E, R>
  ): Effect.Effect<Option.Option<B>, E, R>
} = dual(
  (args) => isIterable(args[0]) && !isEffect(args[0]),
  <A, B, X, E, R>(
    elements: Iterable<A>,
    filter: (input: A, i: number) => Effect.Effect<Result.Result<B, X>, E, R>
  ): Effect.Effect<Option.Option<B>, E, R> =>
    suspend(() => {
      const iterator = elements[Symbol.iterator]()
      const next = iterator.next()
      if (!next.done) {
        return findFirstFilterLoop(iterator, 0, filter, next.value)
      }
      return succeed(Option.none())
    })
)

const findFirstFilterLoop = <A, B, X, E, R>(
  iterator: Iterator<A>,
  index: number,
  filter: (input: A, i: number) => Effect.Effect<Result.Result<B, X>, E, R>,
  value: A
): Effect.Effect<Option.Option<B>, E, R> =>
  flatMap(filter(value, index), (result) => {
    if (Result.isSuccess(result)) {
      return succeed(Option.some(result.success))
    }
    const next = iterator.next()
    if (!next.done) {
      return findFirstFilterLoop(iterator, index + 1, filter, next.value)
    }
    return succeed(Option.none())
  })

/** @internal */
export const whileLoop: <A, E, R>(options: {
  readonly while: LazyArg<boolean>
  readonly body: LazyArg<Effect.Effect<A, E, R>>
  readonly step: (a: A) => void
}) => Effect.Effect<void, E, R> = makePrimitive({
  op: "While",
  [contA](value, fiber) {
    this[args].step(value)
    if (this[args].while()) {
      fiber._stack.push(this)
      return this[args].body()
    }
    return exitVoid
  },
  [evaluate](fiber) {
    if (this[args].while()) {
      fiber._stack.push(this)
      return this[args].body()
    }
    return exitVoid
  }
})

/** @internal */
export const forEach: {
  <B, E, R, S extends Iterable<any>, const Discard extends boolean = false>(
    f: (a: Arr.ReadonlyArray.Infer<S>, i: number) => Effect.Effect<B, E, R>,
    options?: {
      readonly concurrency?: Concurrency | undefined
      readonly discard?: Discard | undefined
    } | undefined
  ): (
    self: S
  ) => Effect.Effect<Discard extends false ? Arr.ReadonlyArray.With<S, B> : void, E, R>
  <B, E, R, S extends Iterable<any>, const Discard extends boolean = false>(
    self: S,
    f: (a: Arr.ReadonlyArray.Infer<S>, i: number) => Effect.Effect<B, E, R>,
    options?: {
      readonly concurrency?: Concurrency | undefined
      readonly discard?: Discard | undefined
    } | undefined
  ): Effect.Effect<Discard extends false ? Arr.ReadonlyArray.With<S, B> : void, E, R>
} = dual((args) => typeof args[1] === "function", <A, B, E, R>(
  iterable: Iterable<A>,
  f: (a: A, index: number) => Effect.Effect<B, E, R>,
  options?: {
    readonly concurrency?: Concurrency | undefined
    readonly discard?: boolean | undefined
  }
): Effect.Effect<any, E, R> =>
  withFiber((parent) => {
    const concurrencyOption = options?.concurrency === "inherit"
      ? parent.getRef(CurrentConcurrency)
      : (options?.concurrency ?? 1)
    const concurrency = concurrencyOption === "unbounded"
      ? Number.POSITIVE_INFINITY
      : Math.max(1, concurrencyOption)

    if (concurrency === 1) {
      return forEachSequential(iterable, f, options)
    }

    const items = Arr.fromIterable(iterable)
    let length = items.length
    if (length === 0) {
      return options?.discard ? void_ : succeed([])
    }

    const out: Array<B> | undefined = options?.discard
      ? undefined
      : new Array(length)
    const eff = forEachConcurrent({ f, out }, items, { concurrency })
    return eff ? as(eff, out as any) : succeed(out as any)
  }))

const forEachSequential = <A, B, E, R>(
  iterable: Iterable<A>,
  f: (a: A, index: number) => Effect.Effect<B, E, R>,
  options?: {
    readonly discard?: boolean | undefined
  }
) =>
  suspend(() => {
    const out: Array<B> | undefined = options?.discard ? undefined : []
    const iterator = iterable[Symbol.iterator]()
    let state = iterator.next()
    let index = 0
    return as(
      whileLoop({
        while: () => !state.done,
        body: () => f(state.value!, index++),
        step: (b) => {
          if (out) out.push(b)
          state = iterator.next()
        }
      }),
      out
    )
  })

const iterateEagerImpl = <S, A, X, E, R, E2>(options: {
  readonly onItem: (state: S, item: A, index: number) => Effect.Effect<X, E, R>
  readonly step: (state: NoInfer<S>, item: A, exit: Exit.Exit<X, E>, index: number) => Exit.Exit<void, E2> | void
}): (
  initialState: S,
  items: ReadonlyArray<A>,
  options?: {
    readonly concurrency?: number | undefined
    readonly start?: number | undefined
    readonly end?: number | undefined
  }
) => Effect.Effect<void, E | E2, R> | undefined => {
  const onItem = options.onItem
  const step = options.step

  return (
    state: S,
    items: ReadonlyArray<A>,
    opts: {
      readonly concurrency?: number | undefined
      readonly start?: number | undefined
      readonly end?: number | undefined
    } | undefined
  ): Effect.Effect<void, E | E2, R> | undefined => {
    let index = opts?.start ?? 0
    const end = opts?.end ?? items.length
    const concurrency = opts?.concurrency ?? 1
    let done = false
    let parentFiber: Fiber.Fiber<any, any> | undefined
    let fibers: Set<Fiber.Fiber<any, any>> | undefined
    let resume: ((effect: Effect.Effect<void, E | E2, R>) => void) | undefined
    let interrupted = false
    let terminal: Exit.Exit<void, E | E2> | void
    let effect: Effect.Effect<X, E, R> | undefined

    const go = (): Effect.Effect<void, E | E2, R> | undefined => {
      let paused = false
      for (; !terminal && index < end; index++) {
        const item = items[index]
        const eff = effect ?? onItem(state, item, index)

        // fast case (already an exit)
        if (effectIsExit(eff)) {
          terminal = step(state, item, eff, index)
          if (terminal) break

          // Use flatMap for concurrency of 1
        } else if (concurrency === 1) {
          return flatMap(exit(eff), (exit) => {
            terminal = step(state, item, exit, index)
            index++
            return terminal ?? go() ?? void_
          })

          // We have an effect, so enter "async" mode
        } else if (!parentFiber) {
          return callback((cb) => {
            parentFiber = getCurrentFiber()!
            effect = eff
            resume = cb
            const result = go()
            if (result) return cb(result)
            return suspend(() => {
              terminal = exitVoid
              interrupted = true
              return fibers ? fiberInterruptAll(fibers) : void_
            })
          })

          // Fork the effect with concurrency > 1
        } else {
          // Clear the temporary effect from capturing the parentFiber
          effect = undefined

          const fiber = forkUnsafe(parentFiber, eff, true, true, "inherit")
          if (fiber._exit) {
            terminal = step(state, item, fiber._exit, index)
            if (terminal) break
            continue
          }

          // Add the fiber to the Set
          if (fibers) fibers.add(fiber)
          else fibers = new Set([fiber])

          const currentIndex = index
          fiber.addObserver((exit) => {
            fibers!.delete(fiber)
            if (terminal) {
              if (!interrupted && exit._tag === "Failure") {
                for (const reason of exit.cause.reasons) {
                  if (reason._tag === "Interrupt") continue
                  else if (terminal._tag === "Failure") {
                    ;(terminal.cause.reasons as Array<any>).push(reason)
                  } else {
                    terminal = exitFailCause(causeFromReasons([reason]))
                  }
                }
              }
            } else {
              const result = step(state, item, exit, currentIndex)
              if (result) {
                terminal = result._tag === "Failure"
                  ? exitFailCause(causeFromReasons(result.cause.reasons.slice()))
                  : result
                go()
              }
            }

            if (paused) {
              const eff = go()
              if (eff) resume!(eff)
            } else if (done && fibers!.size === 0) {
              resume!(terminal ?? void_)
            }
          })

          // Check if we have reached the concurrency limit
          if (fibers.size < concurrency) continue
          paused = true
          index++
          return
        }
      }

      done = true

      if (terminal) {
        if (fibers && fibers.size > 0) {
          const annotations = fiberStackAnnotations(parentFiber!)
          fibers.forEach((f) => f.interruptUnsafe(parentFiber!.id, annotations))
          return
        }
        if (resume || terminal._tag === "Failure") {
          return terminal
        }
      } else if (resume) {
        if (!fibers) {
          return exitVoid
        } else if (fibers.size === 0) {
          resume(void_)
        }
      }
    }

    return go()
  }
}

/** @internal */
export const iterateEager = <S, A>(): <X, E, R, E2>(options: {
  readonly onItem: (state: S, item: A, index: number) => Effect.Effect<X, E, R>
  readonly step: (state: NoInfer<S>, item: A, exit: Exit.Exit<X, E>, index: number) => Exit.Exit<void, E2> | void
}) => (
  initialState: S,
  items: ReadonlyArray<A>,
  options?: {
    readonly concurrency?: number | undefined
    readonly start?: number | undefined
    readonly end?: number | undefined
  }
) => Effect.Effect<void, E | E2, R> | undefined => iterateEagerImpl

const forEachConcurrent = iterateEagerImpl({
  onItem(
    state: {
      readonly f: (a: any, i: number) => Effect.Effect<any, any, any>
      readonly out: Array<any> | undefined
    },
    item,
    index
  ) {
    return state.f(item, index)
  },
  step(state, _, exit, index) {
    if (exit._tag === "Failure") return exit
    else if (state.out) {
      state.out[index] = exit.value
    }
  }
})

/* @internal */
export const filterOrElse: {
  <A, C, E2, R2, B extends A>(
    refinement: Predicate.Refinement<NoInfer<A>, B>,
    orElse: (a: EqualsWith<A, B, NoInfer<A>, Exclude<NoInfer<A>, B>>) => Effect.Effect<C, E2, R2>
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<B | C, E2 | E, R2 | R>
  <A, C, E2, R2>(
    predicate: Predicate.Predicate<NoInfer<A>>,
    orElse: (a: NoInfer<A>) => Effect.Effect<C, E2, R2>
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | C, E2 | E, R2 | R>
  <A, E, R, C, E2, R2, B extends A>(
    self: Effect.Effect<A, E, R>,
    refinement: Predicate.Refinement<A, B>,
    orElse: (a: EqualsWith<A, B, A, Exclude<A, B>>) => Effect.Effect<C, E2, R2>
  ): Effect.Effect<B | C, E | E2, R | R2>
  <A, E, R, C, E2, R2>(
    self: Effect.Effect<A, E, R>,
    predicate: Predicate.Predicate<NoInfer<A>>,
    orElse: (a: NoInfer<A>) => Effect.Effect<C, E2, R2>
  ): Effect.Effect<A | C, E | E2, R | R2>
} = dual(3, <A, E, R, B, E2, R2>(
  self: Effect.Effect<A, E, R>,
  predicate: Predicate.Predicate<A>,
  orElse: (a: A) => Effect.Effect<B, E2, R2>
): Effect.Effect<A | B, E | E2, R | R2> =>
  flatMap(
    self,
    (a) => predicate(a) ? succeed<A | B>(a) : orElse(a)
  ))

/** @internal */
export const filterMapOrElse: {
  <A, B, X, C, E2, R2>(
    filter: Filter.Filter<NoInfer<A>, B, X>,
    orElse: (x: X) => Effect.Effect<C, E2, R2>
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<B | C, E2 | E, R2 | R>
  <A, E, R, B, X, C, E2, R2>(
    self: Effect.Effect<A, E, R>,
    filter: Filter.Filter<NoInfer<A>, B, X>,
    orElse: (x: X) => Effect.Effect<C, E2, R2>
  ): Effect.Effect<B | C, E | E2, R | R2>
} = dual(3, <A, E, R, B, X, C, E2, R2>(
  self: Effect.Effect<A, E, R>,
  filter: Filter.Filter<NoInfer<A>, B, X>,
  orElse: (x: X) => Effect.Effect<C, E2, R2>
): Effect.Effect<B | C, E | E2, R | R2> =>
  flatMap(
    self,
    (a) => {
      const result = filter(a)
      return (Result.isFailure(result)
        ? orElse(result.failure)
        : succeed(result.success)) as Effect.Effect<B | C, E2, R2>
    }
  ))

/* @internal */
export const filterMapOrFail: {
  <A, B, X, E2>(
    filter: Filter.Filter<NoInfer<A>, B, X>,
    orFailWith: (x: X) => E2
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<B, E2 | E, R>
  <A, B, X>(
    filter: Filter.Filter<NoInfer<A>, B, X>
  ): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<B, Cause.NoSuchElementError | E, R>
  <A, E, R, B, X, E2>(
    self: Effect.Effect<A, E, R>,
    filter: Filter.Filter<NoInfer<A>, B, X>,
    orFailWith: (x: X) => E2
  ): Effect.Effect<B, E2 | E, R>
  <A, E, R, B, X>(
    self: Effect.Effect<A, E, R>,
    filter: Filter.Filter<NoInfer<A>, B, X>
  ): Effect.Effect<B, Cause.NoSuchElementError | E, R>
} = dual((args) => isEffect(args[0]), <A, E, R, B, X, E2>(
  self: Effect.Effect<A, E, R>,
  filter: Filter.Filter<NoInfer<A>, B, X>,
  orFailWith?: (x: X) => E2
): Effect.Effect<B, E2 | Cause.NoSuchElementError | E, R> =>
  filterMapOrElse(
    self,
    filter,
    orFailWith ? (x: X) => fail(orFailWith(x)) : () => fail(new NoSuchElementError() as E2)
  ))

/** @internal */
export const filter: {
  <A, B extends A>(
    refinement: Predicate.Refinement<NoInfer<A>, B>
  ): (elements: Iterable<A>) => Effect.Effect<Array<B>>
  <A>(
    predicate: Predicate.Predicate<NoInfer<A>>
  ): (elements: Iterable<A>) => Effect.Effect<Array<A>>
  <A, E, R>(
    predicate: (a: NoInfer<A>, i: number) => Effect.Effect<boolean, E, R>,
    options?: { readonly concurrency?: Concurrency | undefined }
  ): (iterable: Iterable<A>) => Effect.Effect<Array<A>, E, R>
  <A, B extends A>(
    elements: Iterable<A>,
    refinement: Predicate.Refinement<A, B>
  ): Effect.Effect<Array<B>>
  <A>(
    elements: Iterable<A>,
    predicate: Predicate.Predicate<A>
  ): Effect.Effect<Array<A>>
  <A, E, R>(
    iterable: Iterable<A>,
    predicate: (a: NoInfer<A>, i: number) => Effect.Effect<boolean, E, R>,
    options?: { readonly concurrency?: Concurrency | undefined }
  ): Effect.Effect<Array<A>, E, R>
} = dual(
  (args) => isIterable(args[0]) && !isEffect(args[0]),
  <A, E, R>(
    elements: Iterable<A>,
    predicate:
      | Predicate.Predicate<A>
      | ((a: A, i: number) => Effect.Effect<boolean, E, R>),
    options?: { readonly concurrency?: Concurrency | undefined }
  ): Effect.Effect<Array<A>, E, R> =>
    suspend(() => {
      const out: Array<A> = []
      return as(
        forEach(
          elements,
          (a, i) => {
            const result = (predicate as Function)(a, i)
            if (typeof result === "boolean") {
              if (result) out.push(a)
              return void_ as any
            }
            return map(result, (keep) => {
              if (keep) {
                out.push(a)
              }
            })
          },
          {
            discard: true,
            concurrency: options?.concurrency
          }
        ),
        out
      )
    })
)

/** @internal */
export const filterMap: {
  <A, B, X>(
    filter: Filter.Filter<NoInfer<A>, B, X>
  ): (elements: Iterable<A>) => Effect.Effect<Array<B>>
  <A, B, X>(
    elements: Iterable<A>,
    filter: Filter.Filter<NoInfer<A>, B, X>
  ): Effect.Effect<Array<B>>
} = dual(
  (args) => isIterable(args[0]) && !isEffect(args[0]),
  <A, B, X>(
    elements: Iterable<A>,
    filter: Filter.Filter<A, B, X>
  ): Effect.Effect<Array<B>> =>
    suspend(() => {
      const out: Array<B> = []
      for (const a of elements) {
        const result = filter(a)
        if (Result.isSuccess(result)) {
          out.push(result.success)
        }
      }
      return succeed(out)
    })
)

/** @internal */
export const filterMapEffect: {
  <A, B, X, E, R>(
    filter: Filter.FilterEffect<NoInfer<A>, B, X, E, R>,
    options?: { readonly concurrency?: Concurrency | undefined }
  ): (elements: Iterable<A>) => Effect.Effect<Array<B>, E, R>
  <A, B, X, E, R>(
    elements: Iterable<A>,
    filter: Filter.FilterEffect<NoInfer<A>, B, X, E, R>,
    options?: { readonly concurrency?: Concurrency | undefined }
  ): Effect.Effect<Array<B>, E, R>
} = dual(
  (args) => isIterable(args[0]) && !isEffect(args[0]),
  <A, B, X, E, R>(
    elements: Iterable<A>,
    filter: Filter.FilterEffect<A, B, X, E, R>,
    options?: { readonly concurrency?: Concurrency | undefined }
  ): Effect.Effect<Array<B>, E, R> =>
    suspend(() => {
      const out: Array<B> = []
      return as(
        forEach(
          elements,
          (a) =>
            map(filter(a), (result) => {
              if (Result.isSuccess(result)) {
                out.push(result.success)
              }
            }),
          {
            discard: true,
            concurrency: options?.concurrency
          }
        ),
        out
      )
    })
)

// ----------------------------------------------------------------------------
// do notation
// ----------------------------------------------------------------------------

/** @internal */
export const Do: Effect.Effect<{}> = succeed({})

/** @internal */
export const bindTo: {
  <N extends string>(
    name: N
  ): <A, E, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<Record<N, A>, E, R>
  <A, E, R, N extends string>(
    self: Effect.Effect<A, E, R>,
    name: N
  ): Effect.Effect<Record<N, A>, E, R>
} = doNotation.bindTo<Effect.EffectTypeLambda>(map)

/** @internal */
export const bind: {
  <N extends string, A extends Record<string, any>, B, E2, R2>(
    name: N,
    f: (a: NoInfer<A>) => Effect.Effect<B, E2, R2>
  ): <E, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<Simplify<Omit<A, N> & Record<N, B>>, E | E2, R | R2>
  <A extends Record<string, any>, E, R, B, E2, R2, N extends string>(
    self: Effect.Effect<A, E, R>,
    name: N,
    f: (a: NoInfer<A>) => Effect.Effect<B, E2, R2>
  ): Effect.Effect<Simplify<Omit<A, N> & Record<N, B>>, E | E2, R | R2>
} = doNotation.bind<Effect.EffectTypeLambda>(map, flatMap)

/** @internal */
const let_: {
  <N extends string, A extends Record<string, any>, B>(
    name: N,
    f: (a: NoInfer<A>) => B
  ): <E, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<Simplify<Omit<A, N> & Record<N, B>>, E, R>
  <A extends Record<string, any>, E, R, B, N extends string>(
    self: Effect.Effect<A, E, R>,
    name: N,
    f: (a: NoInfer<A>) => B
  ): Effect.Effect<Simplify<Omit<A, N> & Record<N, B>>, E, R>
} = doNotation.let_<Effect.EffectTypeLambda>(map)

/** @internal */
export { let_ as let }

// ----------------------------------------------------------------------------
// fibers & forking
// ----------------------------------------------------------------------------

/** @internal */
export const forkChild: {
  <
    Arg extends Effect.Effect<any, any, any> | {
      readonly startImmediately?: boolean | undefined
      readonly uninterruptible?: boolean | "inherit" | undefined
    } | undefined = {
      readonly startImmediately?: boolean | undefined
      readonly uninterruptible?: boolean | "inherit" | undefined
    }
  >(
    effectOrOptions: Arg,
    options?: {
      readonly startImmediately?: boolean | undefined
      readonly uninterruptible?: boolean | "inherit" | undefined
    } | undefined
  ): [Arg] extends [Effect.Effect<infer _A, infer _E, infer _R>] ? Effect.Effect<Fiber.Fiber<_A, _E>, never, _R>
    : <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<Fiber.Fiber<A, E>, never, R>
} = dual((args) => isEffect(args[0]), <A, E, R>(
  self: Effect.Effect<A, E, R>,
  options?: {
    readonly startImmediately?: boolean
    readonly uninterruptible?: boolean | "inherit"
  }
): Effect.Effect<Fiber.Fiber<A, E>, never, R> =>
  withFiber((fiber) => {
    interruptChildrenPatch()
    return succeed(forkUnsafe(
      fiber,
      self,
      options?.startImmediately,
      false,
      options?.uninterruptible ?? false
    ))
  }))

/** @internal */
export const forkUnsafe = <FA, FE, A, E, R>(
  parent: Fiber.Fiber<FA, FE>,
  effect: Effect.Effect<A, E, R>,
  immediate = false,
  daemon = false,
  uninterruptible: boolean | "inherit" = false
): FiberImpl<A, E> => {
  const interruptible = uninterruptible === "inherit" ? parent.interruptible : !uninterruptible
  const child = new FiberImpl<A, E>(parent.context, interruptible)
  if (immediate) {
    child.evaluate(effect as any)
  } else {
    parent.currentDispatcher.scheduleTask(() => child.evaluate(effect as any), 0)
  }
  if (!daemon && !child._exit) {
    parent.children().add(child)
    child.addObserver(() => parent._children!.delete(child))
  }
  return child
}

/** @internal */
export const forkDetach: {
  <
    Arg extends Effect.Effect<any, any, any> | {
      readonly startImmediately?: boolean | undefined
      readonly uninterruptible?: boolean | "inherit" | undefined
    } | undefined = {
      readonly startImmediately?: boolean | undefined
      readonly uninterruptible?: boolean | "inherit" | undefined
    }
  >(
    effectOrOptions: Arg,
    options?: {
      readonly startImmediately?: boolean | undefined
      readonly uninterruptible?: boolean | "inherit" | undefined
    } | undefined
  ): [Arg] extends [Effect.Effect<infer _A, infer _E, infer _R>] ? Effect.Effect<Fiber.Fiber<_A, _E>, never, _R>
    : <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<Fiber.Fiber<A, E>, never, R>
} = dual((args) => isEffect(args[0]), <A, E, R>(
  self: Effect.Effect<A, E, R>,
  options?: {
    readonly startImmediately?: boolean
    readonly uninterruptible?: boolean | "inherit" | undefined
  }
): Effect.Effect<Fiber.Fiber<A, E>, never, R> =>
  withFiber((fiber) => succeed(forkUnsafe(fiber, self, options?.startImmediately, true, options?.uninterruptible))))

/** @internal */
export const awaitAllChildren = <A, E, R>(
  self: Effect.Effect<A, E, R>
): Effect.Effect<A, E, R> =>
  withFiber((fiber) => {
    const initialChildren = fiber._children && Arr.fromIterable(fiber._children)
    return onExit(
      self,
      (_) => {
        let children = fiber._children
        if (children === undefined || children.size === 0) {
          return void_
        } else if (initialChildren) {
          children = Iterable.filter(
            children,
            (child: FiberImpl<any, any>) => !initialChildren.includes(child)
          ) as Set<FiberImpl<any, any>>
        }
        return asVoid(fiberAwaitAll(children))
      }
    )
  })

/** @internal */
export const forkIn: {
  (
    scope: Scope.Scope,
    options?: {
      readonly startImmediately?: boolean | undefined
      readonly uninterruptible?: boolean | "inherit" | undefined
    }
  ): <A, E, R>(
    self: Effect.Effect<A, E, R>
  ) => Effect.Effect<Fiber.Fiber<A, E>, never, R>
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    scope: Scope.Scope,
    options?: {
      readonly startImmediately?: boolean | undefined
      readonly uninterruptible?: boolean | "inherit" | undefined
    }
  ): Effect.Effect<Fiber.Fiber<A, E>, never, R>
} = dual(
  (args) => isEffect(args[0]),
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    scope: Scope.Scope,
    options?: {
      readonly startImmediately?: boolean | undefined
      readonly uninterruptible?: boolean | "inherit" | undefined
    }
  ): Effect.Effect<Fiber.Fiber<A, E>, never, R> =>
    withFiber((parent) => {
      const fiber = forkUnsafe(parent, self, options?.startImmediately, true, options?.uninterruptible)
      if (!(fiber as FiberImpl<any, any>)._exit) {
        if (scope.state._tag !== "Closed") {
          const key = {}
          const finalizer = () => withFiberId((interruptor) => interruptor === fiber.id ? void_ : fiberInterrupt(fiber))
          scopeAddFinalizerUnsafe(scope, key, finalizer)
          fiber.addObserver(() => scopeRemoveFinalizerUnsafe(scope, key))
        } else {
          fiber.interruptUnsafe(parent.id, fiberStackAnnotations(parent))
        }
      }
      return succeed(fiber)
    })
)

/** @internal */
export const forkScoped: {
  <
    Arg extends Effect.Effect<any, any, any> | {
      readonly startImmediately?: boolean | undefined
      readonly uninterruptible?: boolean | "inherit" | undefined
    } | undefined = {
      readonly startImmediately?: boolean | undefined
      readonly uninterruptible?: boolean | "inherit" | undefined
    }
  >(
    effectOrOptions: Arg,
    options?: {
      readonly startImmediately?: boolean | undefined
      readonly uninterruptible?: boolean | "inherit" | undefined
    } | undefined
  ): [Arg] extends [Effect.Effect<infer _A, infer _E, infer _R>] ?
    Effect.Effect<Fiber.Fiber<_A, _E>, never, _R | Scope.Scope>
    : <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<Fiber.Fiber<A, E>, never, R | Scope.Scope>
} = dual((args) => isEffect(args[0]), <A, E, R>(
  self: Effect.Effect<A, E, R>,
  options?: {
    readonly startImmediately?: boolean
    readonly uninterruptible?: boolean | "inherit"
  }
): Effect.Effect<Fiber.Fiber<A, E>, never, R | Scope.Scope> => flatMap(scope, (scope) => forkIn(self, scope, options)))

// ----------------------------------------------------------------------------
// execution
// ----------------------------------------------------------------------------

/** @internal */
export const runForkWith = <R>(context: Context.Context<R>) =>
<A, E>(
  effect: Effect.Effect<A, E, R>,
  options?: Effect.RunOptions | undefined
): Fiber.Fiber<A, E> => {
  const fiber = new FiberImpl<A, E>(
    options?.scheduler ? Context.add(context, Scheduler.Scheduler, options.scheduler) : context,
    options?.uninterruptible !== true
  )
  fiber.evaluate(effect as any)
  if (fiber._exit) return fiber

  if (options?.signal) {
    if (options.signal.aborted) {
      fiber.interruptUnsafe()
    } else {
      const abort = () => fiber.interruptUnsafe()
      options.signal.addEventListener("abort", abort, { once: true })
      fiber.addObserver(() => options.signal!.removeEventListener("abort", abort))
    }
  }
  if (options?.onFiberStart) {
    options.onFiberStart(fiber)
  }
  return fiber
}

/** @internal */
export const fiberRunIn: {
  (scope: Scope.Scope): <A, E>(self: Fiber.Fiber<A, E>) => Fiber.Fiber<A, E>
  <A, E>(
    self: Fiber.Fiber<A, E>,
    scope: Scope.Scope
  ): Fiber.Fiber<A, E>
} = dual(2, <A, E>(
  self: FiberImpl<A, E>,
  scope: Scope.Scope
): Fiber.Fiber<A, E> => {
  if (self._exit) {
    return self
  } else if (scope.state._tag === "Closed") {
    self.interruptUnsafe(self.id)
    return self
  }
  const key = {}
  scopeAddFinalizerUnsafe(scope, key, () => fiberInterrupt(self))
  self.addObserver(() => scopeRemoveFinalizerUnsafe(scope, key))
  return self
})

/** @internal */
export const runFork: <A, E>(
  effect: Effect.Effect<A, E, never>,
  options?: Effect.RunOptions | undefined
) => Fiber.Fiber<A, E> = runForkWith(Context.empty())

/** @internal */
export const runCallbackWith = <R>(context: Context.Context<R>) => {
  const runFork = runForkWith(context)
  return <A, E>(
    effect: Effect.Effect<A, E, R>,
    options?:
      | Effect.RunOptions & {
        readonly onExit: (exit: Exit.Exit<A, E>) => void
      }
      | undefined
  ): (interruptor?: number | undefined) => void => {
    const fiber = runFork(effect, options)
    if (options?.onExit) {
      fiber.addObserver(options.onExit)
    }
    return (interruptor) => {
      return fiber.interruptUnsafe(interruptor)
    }
  }
}

/** @internal */
export const runCallback = runCallbackWith(Context.empty())

/** @internal */
export const runPromiseExitWith = <R>(context: Context.Context<R>) => {
  const runFork = runForkWith(context)
  return <A, E>(
    effect: Effect.Effect<A, E, R>,
    options?: Effect.RunOptions | undefined
  ): Promise<Exit.Exit<A, E>> => {
    const fiber = runFork(effect, options)
    return new Promise((resolve) => {
      fiber.addObserver((exit) => resolve(exit))
    })
  }
}

/** @internal */
export const runPromiseExit = runPromiseExitWith(Context.empty())

/** @internal */
export const runPromiseWith = <R>(context: Context.Context<R>) => {
  const runPromiseExit = runPromiseExitWith(context)
  return <A, E>(
    effect: Effect.Effect<A, E, R>,
    options?:
      | Effect.RunOptions
      | undefined
  ): Promise<A> =>
    runPromiseExit(effect, options).then((exit) => {
      if (exit._tag === "Failure") {
        throw causeSquash(exit.cause)
      }
      return exit.value
    })
}

/** @internal */
export const runPromise: <A, E>(
  effect: Effect.Effect<A, E>,
  options?:
    | Effect.RunOptions
    | undefined
) => Promise<A> = runPromiseWith(Context.empty())

/** @internal */
export const runSyncExitWith = <R>(context: Context.Context<R>) => {
  const runFork = runForkWith(context)
  return <A, E>(effect: Effect.Effect<A, E, R>): Exit.Exit<A, E> => {
    if (effectIsExit(effect)) return effect
    const scheduler = new Scheduler.MixedScheduler("sync")
    const fiber = runFork(effect, { scheduler })
    fiber.currentDispatcher?.flush()
    return (fiber as FiberImpl<A, E>)._exit ?? exitDie(new AsyncFiberError(fiber))
  }
}

/** @internal */
export const runSyncExit: <A, E>(effect: Effect.Effect<A, E>) => Exit.Exit<A, E> = runSyncExitWith(
  Context.empty()
)

/** @internal */
export const runSyncWith = <R>(context: Context.Context<R>) => {
  const runSyncExit = runSyncExitWith(context)
  return <A, E>(effect: Effect.Effect<A, E, R>): A => {
    const exit = runSyncExit(effect)
    if (exit._tag === "Failure") throw causeSquash(exit.cause)
    return exit.value
  }
}

/** @internal */
export const runSync: <A, E>(effect: Effect.Effect<A, E>) => A = runSyncWith(Context.empty())

const succeedTrue = succeed(true)
const succeedFalse = succeed(false)

class Latch implements _Latch.Latch {
  waiters: Array<(_: Effect.Effect<void>) => void> = []
  scheduled = false
  private isOpen: boolean

  constructor(isOpen: boolean) {
    this.isOpen = isOpen
  }

  private scheduleUnsafe(fiber: Fiber.Fiber<unknown, unknown>) {
    if (this.scheduled || this.waiters.length === 0) {
      return succeedTrue
    }
    this.scheduled = true
    fiber.currentDispatcher.scheduleTask(this.flushWaiters, 0)
    return succeedTrue
  }
  private flushWaiters = () => {
    this.scheduled = false
    const waiters = this.waiters
    this.waiters = []
    for (let i = 0; i < waiters.length; i++) {
      waiters[i](exitVoid)
    }
  }

  open = withFiber<boolean>((fiber) => {
    if (this.isOpen) return succeedFalse
    this.isOpen = true
    return this.scheduleUnsafe(fiber)
  })
  release = withFiber<boolean>((fiber) => this.isOpen ? succeedFalse : this.scheduleUnsafe(fiber))
  openUnsafe() {
    if (this.isOpen) return false
    this.isOpen = true
    this.flushWaiters()
    return true
  }
  await = callback<void>((resume) => {
    if (this.isOpen) {
      return resume(void_)
    }
    this.waiters.push(resume)
    return sync(() => {
      const index = this.waiters.indexOf(resume)
      if (index !== -1) {
        this.waiters.splice(index, 1)
      }
    })
  })
  closeUnsafe() {
    if (!this.isOpen) return false
    this.isOpen = false
    return true
  }
  close = sync(() => this.closeUnsafe())
  whenOpen = <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> => flatMap(this.await, () => self)
}

/** @internal */
export const makeLatchUnsafe = (open?: boolean | undefined): _Latch.Latch => new Latch(open ?? false)

/** @internal */
export const makeLatch = (open?: boolean | undefined) => sync(() => makeLatchUnsafe(open))

// ----------------------------------------------------------------------------
// Tracer
// ----------------------------------------------------------------------------

/** @internal */
export const tracer: Effect.Effect<Tracer.Tracer> = withFiber((fiber) => succeed(fiber.getRef(Tracer.Tracer)))

/** @internal */
export const withTracer: {
  (tracer: Tracer.Tracer): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
  <A, E, R>(effect: Effect.Effect<A, E, R>, tracer: Tracer.Tracer): Effect.Effect<A, E, R>
} = dual(
  2,
  <A, E, R>(effect: Effect.Effect<A, E, R>, tracer: Tracer.Tracer): Effect.Effect<A, E, R> =>
    provideService(effect, Tracer.Tracer, tracer)
)

/** @internal */
export const withTracerEnabled: {
  (enabled: boolean): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
  <A, E, R>(effect: Effect.Effect<A, E, R>, enabled: boolean): Effect.Effect<A, E, R>
} = provideService(TracerEnabled)

/** @internal */
export const withTracerTiming: {
  (enabled: boolean): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
  <A, E, R>(effect: Effect.Effect<A, E, R>, enabled: boolean): Effect.Effect<A, E, R>
} = provideService(TracerTimingEnabled)

const bigint0 = BigInt(0)

const NoopSpanProto: Omit<Tracer.Span, "parent" | "name" | "annotations" | "level"> = {
  _tag: "Span",
  spanId: "noop",
  traceId: "noop",
  sampled: false,
  status: {
    _tag: "Ended",
    startTime: bigint0,
    endTime: bigint0,
    exit: exitVoid
  },
  attributes: new Map(),
  links: [],
  kind: "internal",
  attribute() {},
  event() {},
  end() {},
  addLinks() {}
}

/** @internal */
export const noopSpan = (options: {
  readonly name: string
  readonly parent: Option.Option<Tracer.AnySpan>
  readonly annotations: Context.Context<never>
}): Tracer.Span => Object.assign(Object.create(NoopSpanProto), options)

const filterDisablePropagation = (span: Tracer.AnySpan | undefined): Option.Option<Tracer.AnySpan> => {
  if (!span) return Option.none()
  return Context.get(span.annotations, Tracer.DisablePropagation)
    ? span._tag === "Span" ? filterDisablePropagation(Option.getOrUndefined(span.parent)) : Option.none()
    : Option.some(span)
}

/** @internal */
export const makeSpanUnsafe = <XA, XE>(
  fiber: Fiber.Fiber<XA, XE>,
  name: string,
  options: Tracer.SpanOptionsNoTrace | undefined
) => {
  const disablePropagation = !fiber.getRef(TracerEnabled) ||
    (options?.annotations && Context.get(options.annotations, Tracer.DisablePropagation))
  const parent = options?.parent !== undefined
    ? Option.some(options.parent)
    : options?.root
    ? Option.none<Tracer.AnySpan>()
    : filterDisablePropagation(fiber.currentSpan)

  let span: Tracer.Span

  if (disablePropagation) {
    span = noopSpan({
      name,
      parent,
      annotations: Context.add(
        options?.annotations ?? Context.empty(),
        Tracer.DisablePropagation,
        true
      )
    })
  } else {
    const tracer = fiber.getRef(Tracer.Tracer)
    const clock = fiber.getRef(ClockRef)
    const timingEnabled = fiber.getRef(TracerTimingEnabled)
    const annotationsFromEnv = fiber.getRef(TracerSpanAnnotations)
    const linksFromEnv = fiber.getRef(TracerSpanLinks)
    const level = options?.level ?? fiber.getRef(Tracer.CurrentTraceLevel)

    const links = options?.links !== undefined ?
      [...linksFromEnv, ...options.links] :
      linksFromEnv.slice()

    span = tracer.span({
      name,
      parent,
      annotations: options?.annotations ?? Context.empty(),
      links,
      startTime: timingEnabled ? clock.currentTimeNanosUnsafe() : BigInt(0),
      kind: options?.kind ?? "internal",
      root: options?.root ?? Option.isNone(parent),
      sampled: options?.sampled ??
        (Option.isSome(parent) && parent.value.sampled === false
          ? false
          : !isLogLevelGreaterThan(fiber.getRef(Tracer.MinimumTraceLevel), level))
    })

    for (const [key, value] of Object.entries(annotationsFromEnv)) {
      span.attribute(key, value)
    }
    if (options?.attributes !== undefined) {
      for (const [key, value] of Object.entries(options.attributes)) {
        span.attribute(key, value)
      }
    }
  }

  return span
}

/** @internal */
export const makeSpan = (
  name: string,
  options?: Tracer.SpanOptions
): Effect.Effect<Tracer.Span> => withFiber((fiber) => succeed(makeSpanUnsafe(fiber, name, options)))

/** @internal */
export const makeSpanScoped = (
  name: string,
  options?: Tracer.SpanOptionsNoTrace | undefined
): Effect.Effect<Tracer.Span, never, Scope.Scope> =>
  uninterruptible(
    withFiber((fiber) => {
      const scope = Context.getUnsafe(fiber.context, scopeTag)
      const span = makeSpanUnsafe(fiber, name, options ?? {})
      const clock = fiber.getRef(ClockRef)
      const timingEnabled = fiber.getRef(TracerTimingEnabled)
      return as(
        scopeAddFinalizerExit(scope, (exit) => endSpan(span, exit, clock, timingEnabled)),
        span
      )
    })
  )

/** @internal */
export const withSpanScoped: {
  (
    name: string,
    options?: Tracer.SpanOptions
  ): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Tracer.ParentSpan> | Scope.Scope>
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    name: string,
    options?: Tracer.SpanOptions
  ): Effect.Effect<A, E, Exclude<R, Tracer.ParentSpan> | Scope.Scope>
} = function() {
  const dataFirst = typeof arguments[0] !== "string"
  const name = dataFirst ? arguments[1] : arguments[0]
  const options = addSpanStackTrace(dataFirst ? arguments[2] : arguments[1])
  if (dataFirst) {
    const self = arguments[0]
    return flatMap(
      makeSpanScoped(name, options),
      (span) => withParentSpan(self, span, options)
    )
  }
  return (self: Effect.Effect<any, any, any>) =>
    flatMap(
      makeSpanScoped(name, options),
      (span) => withParentSpan(self, span, options)
    )
} as any

const provideSpanStackFrame = (name: string, stack: (() => string | undefined) | undefined) => {
  stack = typeof stack === "function" ? stack : constUndefined
  return updateService(CurrentStackFrame, (parent) => ({
    name,
    stack,
    parent
  }))
}

/** @internal */
export const spanAnnotations: Effect.Effect<Readonly<Record<string, unknown>>> = TracerSpanAnnotations.asEffect()

/** @internal */
export const spanLinks: Effect.Effect<ReadonlyArray<Tracer.SpanLink>> = TracerSpanLinks.asEffect()

/** @internal */
export const linkSpans: {
  (
    span: Tracer.AnySpan | ReadonlyArray<Tracer.AnySpan>,
    attributes?: Record<string, unknown>
  ): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    span: Tracer.AnySpan | ReadonlyArray<Tracer.AnySpan>,
    attributes?: Record<string, unknown>
  ): Effect.Effect<A, E, R>
} = dual((args) => isEffect(args[0]), <A, E, R>(
  self: Effect.Effect<A, E, R>,
  span: Tracer.AnySpan | ReadonlyArray<Tracer.AnySpan>,
  attributes: Record<string, unknown> = {}
): Effect.Effect<A, E, R> => {
  const spans: Array<Tracer.AnySpan> = Array.isArray(span) ? span : [span]
  const links = spans.map((span): Tracer.SpanLink => ({ span, attributes }))
  return updateService(self, TracerSpanLinks, (current) => [...current, ...links])
})

/** @internal */
export const endSpan = <A, E>(
  span: Tracer.Span,
  exit: Exit.Exit<A, E>,
  clock: Clock.Clock,
  timingEnabled: boolean
) =>
  sync(() => {
    if (span.status._tag === "Ended") return
    span.end(timingEnabled ? clock.currentTimeNanosUnsafe() : bigint0, exit)
  })

/** @internal */
export const useSpan: {
  <A, E, R>(name: string, evaluate: (span: Tracer.Span) => Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
  <A, E, R>(
    name: string,
    options: Tracer.SpanOptionsNoTrace,
    evaluate: (span: Tracer.Span) => Effect.Effect<A, E, R>
  ): Effect.Effect<A, E, R>
} = <A, E, R>(
  name: string,
  ...args: [evaluate: (span: Tracer.Span) => Effect.Effect<A, E, R>] | [
    options: any,
    evaluate: (span: Tracer.Span) => Effect.Effect<A, E, R>
  ]
): Effect.Effect<A, E, R> => {
  const options = args.length === 1 ? undefined : args[0]
  const evaluate: (span: Tracer.Span) => Effect.Effect<A, E, R> = args[args.length - 1]
  return withFiber((fiber) => {
    const span = makeSpanUnsafe(fiber, name, options)
    const clock = fiber.getRef(ClockRef)
    return onExit(internalCall(() => evaluate(span)), (exit) =>
      sync(() => {
        if (span.status._tag === "Ended") return
        span.end(clock.currentTimeNanosUnsafe(), exit)
      }))
  })
}

const provideParentSpan = provideService(Tracer.ParentSpan)

/** @internal */
export const withParentSpan: {
  (
    value: Tracer.AnySpan,
    options?: Tracer.TraceOptions
  ): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Tracer.ParentSpan>>
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    value: Tracer.AnySpan,
    options?: Tracer.TraceOptions
  ): Effect.Effect<A, E, Exclude<R, Tracer.ParentSpan>>
} = function() {
  const dataFirst = isEffect(arguments[0])
  const span: Tracer.AnySpan = dataFirst ? arguments[1] : arguments[0]
  let options = dataFirst ? arguments[2] : arguments[1]
  let provideStackFrame: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> = identity
  if (span._tag === "Span") {
    options = addSpanStackTrace(options)
    provideStackFrame = provideSpanStackFrame(span.name, options?.captureStackTrace)
  }
  if (dataFirst) {
    return provideParentSpan(provideStackFrame(arguments[0]), span)
  }
  return (self: Effect.Effect<any, any, any>) => provideParentSpan(provideStackFrame(self), span)
} as any

/** @internal */
export const withSpan: {
  <Args extends ReadonlyArray<any>>(
    name: string,
    options?: Tracer.SpanOptionsNoTrace | ((...args: NoInfer<Args>) => Tracer.SpanOptionsNoTrace) | undefined,
    traceOptions?: Tracer.TraceOptions | undefined
  ): <A, E, R>(self: Effect.Effect<A, E, R>, ...args: Args) => Effect.Effect<A, E, Exclude<R, Tracer.ParentSpan>>
  <A, E, R>(
    self: Effect.Effect<A, E, R>,
    name: string,
    options?: Tracer.SpanOptions | undefined
  ): Effect.Effect<A, E, Exclude<R, Tracer.ParentSpan>>
} = function() {
  const dataFirst = typeof arguments[0] !== "string"
  const name = dataFirst ? arguments[1] : arguments[0]
  const traceOptions = addSpanStackTrace(arguments[2])
  if (dataFirst) {
    const self = arguments[0]
    return useSpan(name, arguments[2], (span) => withParentSpan(self, span, traceOptions))
  }
  const fnArg = typeof arguments[1] === "function" ? arguments[1] : undefined
  const options = fnArg ? undefined : arguments[1]
  return (self: Effect.Effect<any, any, any>, ...args: any) =>
    useSpan(
      name,
      fnArg ? fnArg(...args) : options,
      (span) => withParentSpan(self, span, traceOptions)
    )
} as any

/** @internal */
export const annotateSpans: {
  (key: string, value: unknown): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
  (values: Record<string, unknown>): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
  <A, E, R>(effect: Effect.Effect<A, E, R>, key: string, value: unknown): Effect.Effect<A, E, R>
  <A, E, R>(effect: Effect.Effect<A, E, R>, values: Record<string, unknown>): Effect.Effect<A, E, R>
} = dual(
  (args) => isEffect(args[0]),
  <A, E, R>(
    effect: Effect.Effect<A, E, R>,
    ...args: [Record<string, unknown>] | [key: string, value: unknown]
  ): Effect.Effect<A, E, R> =>
    updateService(effect, TracerSpanAnnotations, (annotations) => {
      const newAnnotations = { ...annotations }
      if (args.length === 1) {
        Object.assign(newAnnotations, args[0])
      } else {
        newAnnotations[args[0]] = args[1]
      }
      return newAnnotations
    })
)

/** @internal */
export const annotateCurrentSpan: {
  (key: string, value: unknown): Effect.Effect<void>
  (values: Record<string, unknown>): Effect.Effect<void>
} = (...args: [Record<string, unknown>] | [key: string, value: unknown]) =>
  withFiber((fiber) => {
    const span = fiber.currentSpanLocal
    if (span) {
      if (args.length === 1) {
        for (const [key, value] of Object.entries(args[0])) {
          span.attribute(key, value)
        }
      } else {
        span.attribute(args[0], args[1])
      }
    }
    return void_
  })

/** @internal */
export const currentSpan: Effect.Effect<Tracer.Span, Cause.NoSuchElementError> = withFiber((fiber) => {
  const span = fiber.currentSpanLocal
  return span ? succeed(span) : fail(new NoSuchElementError())
})

/** @internal */
export const currentParentSpan: Effect.Effect<Tracer.AnySpan, Cause.NoSuchElementError> = serviceOptional(
  Tracer.ParentSpan
)

// ----------------------------------------------------------------------------
// Clock
// ----------------------------------------------------------------------------

/** @internal */
export const ClockRef = Context.Reference<Clock.Clock>("effect/Clock", {
  defaultValue: (): Clock.Clock => new ClockImpl()
})

const MAX_TIMER_MILLIS = 2 ** 31 - 1

class ClockImpl implements Clock.Clock {
  currentTimeMillisUnsafe(): number {
    return Date.now()
  }
  readonly currentTimeMillis: Effect.Effect<number> = sync(() => this.currentTimeMillisUnsafe())
  currentTimeNanosUnsafe(): bigint {
    return processOrPerformanceNow()
  }
  readonly currentTimeNanos: Effect.Effect<bigint> = sync(() => this.currentTimeNanosUnsafe())
  sleep(duration: Duration.Duration): Effect.Effect<void> {
    const millis = Duration.toMillis(duration)
    if (millis <= 0) return yieldNow
    return callback((resume) => {
      if (millis > MAX_TIMER_MILLIS) return
      const handle = setTimeout(() => resume(void_), millis)
      return sync(() => clearTimeout(handle))
    })
  }
}

const performanceNowNanos = (function() {
  const bigint1e6 = BigInt(1_000_000)
  if (typeof performance === "undefined" || typeof performance.now === "undefined") {
    return () => BigInt(Date.now()) * bigint1e6
  } else if (typeof performance.timeOrigin === "number" && performance.timeOrigin === 0) {
    return () => BigInt(Math.round(performance.now() * 1_000_000))
  }
  const origin = (BigInt(Date.now()) * bigint1e6) - BigInt(Math.round(performance.now() * 1_000_000))
  return () => origin + BigInt(Math.round(performance.now() * 1_000_000))
})()
const processOrPerformanceNow = (function() {
  const processHrtime =
    typeof process === "object" && "hrtime" in process && typeof process.hrtime.bigint === "function" ?
      process.hrtime :
      undefined
  if (!processHrtime) {
    return performanceNowNanos
  }
  const origin = performanceNowNanos() - processHrtime.bigint()
  return () => origin + processHrtime.bigint()
})()

/** @internal */
export const clockWith = <A, E, R>(f: (clock: Clock.Clock) => Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
  withFiber((fiber) => f(fiber.getRef(ClockRef)))

/** @internal */
export const sleep = (duration: Duration.Input): Effect.Effect<void> =>
  clockWith((clock) => clock.sleep(Duration.fromInputUnsafe(duration)))

/** @internal */
export const currentTimeMillis: Effect.Effect<number> = clockWith((clock) => clock.currentTimeMillis)

/** @internal */
export const currentTimeNanos: Effect.Effect<bigint> = clockWith((clock) => clock.currentTimeNanos)

// ----------------------------------------------------------------------------
// Errors
// ----------------------------------------------------------------------------

/** @internal */
export const TimeoutErrorTypeId = "~effect/Cause/TimeoutError"

/** @internal */
export const isTimeoutError = (u: unknown): u is Cause.TimeoutError => hasProperty(u, TimeoutErrorTypeId)

/** @internal */
export class TimeoutError extends TaggedError("TimeoutError") {
  readonly [TimeoutErrorTypeId] = TimeoutErrorTypeId
  constructor(message?: string) {
    super({ message } as any)
  }
}

/** @internal */
export const IllegalArgumentErrorTypeId = "~effect/Cause/IllegalArgumentError"

/** @internal */
export const isIllegalArgumentError = (
  u: unknown
): u is Cause.IllegalArgumentError => hasProperty(u, IllegalArgumentErrorTypeId)

/** @internal */
export class IllegalArgumentError extends TaggedError("IllegalArgumentError") {
  readonly [IllegalArgumentErrorTypeId] = IllegalArgumentErrorTypeId
  constructor(message?: string) {
    super({ message } as any)
  }
}

/** @internal */
export const ExceededCapacityErrorTypeId = "~effect/Cause/ExceededCapacityError"

/** @internal */
export const isExceededCapacityError = (
  u: unknown
): u is Cause.ExceededCapacityError => hasProperty(u, ExceededCapacityErrorTypeId)

/** @internal */
export class ExceededCapacityError extends TaggedError("ExceededCapacityError") {
  readonly [ExceededCapacityErrorTypeId] = ExceededCapacityErrorTypeId
  constructor(message?: string) {
    super({ message } as any)
  }
}

/** @internal */
export const AsyncFiberErrorTypeId = "~effect/Cause/AsyncFiberError"

/** @internal */
export const isAsyncFiberError = (
  u: unknown
): u is Cause.AsyncFiberError => hasProperty(u, AsyncFiberErrorTypeId)

/** @internal */
export class AsyncFiberError extends TaggedError("AsyncFiberError")<{
  fiber: Fiber.Fiber<unknown, unknown>
  message: string
}> {
  readonly [AsyncFiberErrorTypeId] = AsyncFiberErrorTypeId
  constructor(fiber: Fiber.Fiber<unknown, unknown>) {
    super({
      message: "An asynchronous Effect was executed with Effect.runSync",
      fiber
    })
  }
}

/** @internal */
export const UnknownErrorTypeId = "~effect/Cause/UnknownError"

/** @internal */
export const isUnknownError = (
  u: unknown
): u is Cause.UnknownError => hasProperty(u, UnknownErrorTypeId)

/** @internal */
export class UnknownError extends TaggedError("UnknownError")<{
  cause: unknown
  message?: string | undefined
}> {
  readonly [UnknownErrorTypeId] = UnknownErrorTypeId
  constructor(cause: unknown, message?: string) {
    super({ message, cause } as any)
  }
}

// ----------------------------------------------------------------------------
// Console
// ----------------------------------------------------------------------------

/** @internal */
export const ConsoleRef = Context.Reference<Console.Console>(
  "effect/Console/CurrentConsole",
  { defaultValue: (): Console.Console => globalThis.console }
)

// ----------------------------------------------------------------------------
// LogLevel
// ----------------------------------------------------------------------------

/** @internal */
export const logLevelToOrder = (level: LogLevel.LogLevel) => {
  switch (level) {
    case "All":
      return Number.MIN_SAFE_INTEGER
    case "Fatal":
      return 50_000
    case "Error":
      return 40_000
    case "Warn":
      return 30_000
    case "Info":
      return 20_000
    case "Debug":
      return 10_000
    case "Trace":
      return 0
    case "None":
      return Number.MAX_SAFE_INTEGER
  }
}

/** @internal */
export const LogLevelOrder = Order.mapInput(Order.Number, logLevelToOrder)

/** @internal */
export const isLogLevelGreaterThan = Order.isGreaterThan(LogLevelOrder)

// ----------------------------------------------------------------------------
// Logger
// ----------------------------------------------------------------------------

/** @internal */
export const CurrentLoggers = Context.Reference<
  ReadonlySet<Logger.Logger<unknown, any>>
>("effect/Loggers/CurrentLoggers", {
  defaultValue: () => new Set([defaultLogger, tracerLogger])
})

/** @internal */
export const LogToStderr = Context.Reference<boolean>("effect/Logger/LogToStderr", {
  defaultValue: constFalse
})

/** @internal */
export const annotateLogsScoped: {
  (key: string, value: unknown): Effect.Effect<void, never, Scope.Scope>
  (values: Record<string, unknown>): Effect.Effect<void, never, Scope.Scope>
} = function() {
  const entries = typeof arguments[0] === "string" ?
    [[arguments[0], arguments[1]]] :
    Object.entries(arguments[0])
  return uninterruptible(withFiber((fiber) => {
    const prev = fiber.getRef(CurrentLogAnnotations)
    const next = { ...prev }
    for (let i = 0; i < entries.length; i++) {
      const [key, value] = entries[i]
      next[key] = value
    }
    fiber.setContext(Context.add(fiber.context, CurrentLogAnnotations, next))
    return scopeAddFinalizerExit(Context.getUnsafe(fiber.context, scopeTag), (_) => {
      const current = fiber.getRef(CurrentLogAnnotations)
      const next = { ...current }
      for (let i = 0; i < entries.length; i++) {
        const [key, value] = entries[i]
        if (current[key] !== value) continue
        if (key in prev) {
          next[key] = prev[key]
        } else {
          delete next[key]
        }
      }
      fiber.setContext(Context.add(fiber.context, CurrentLogAnnotations, next))
      return void_
    })
  }))
}

/** @internal */
export const LoggerTypeId = "~effect/Logger"

const LoggerProto = {
  [LoggerTypeId]: {
    _Message: identity,
    _Output: identity
  },
  pipe() {
    return pipeArguments(this, arguments)
  }
}

/** @internal */
export const loggerMake = <Message, Output>(
  log: (options: Logger.Options<Message>) => Output
): Logger.Logger<Message, Output> => {
  const self = Object.create(LoggerProto)
  self.log = log
  return self
}

/**
 * Sanitize a given string by replacing spaces, equal signs, and double quotes
 * with underscores.
 *
 * @internal
 */
export const formatLabel = (key: string) => key.replace(/[\s="]/g, "_")

/**
 * Formats a log span into a `<label>=<value>ms` string.
 *
 * @internal
 */
export const formatLogSpan = (self: [label: string, timestamp: number], now: number): string => {
  const label = formatLabel(self[0])
  return `${label}=${now - self[1]}ms`
}

/** @internal */
export const structuredMessage = (u: unknown): unknown => {
  switch (typeof u) {
    case "bigint":
    case "function":
    case "symbol": {
      return String(u)
    }
    default: {
      return toJson(u)
    }
  }
}

/** @internal */
export const logWithLevel = (level?: LogLevel.Severity) =>
(
  ...message: ReadonlyArray<any>
): Effect.Effect<void> => {
  let cause: Cause.Cause<unknown> | undefined = undefined
  for (let i = 0, len = message.length; i < len; i++) {
    const msg = message[i]
    if (isCause(msg)) {
      if (cause) {
        ;(message as Array<any>).splice(i, 1)
      } else {
        message = message.slice(0, i).concat(message.slice(i + 1))
      }
      cause = cause ? causeFromReasons(cause.reasons.concat(msg.reasons)) : msg
      i--
    }
  }
  if (cause === undefined) {
    cause = causeEmpty
  }
  return withFiber((fiber) => {
    const logLevel = level ?? fiber.currentLogLevel
    if (isLogLevelGreaterThan(fiber.minimumLogLevel, logLevel)) {
      return void_
    }
    const clock = fiber.getRef(ClockRef)
    const loggers = fiber.getRef(CurrentLoggers)
    if (loggers.size > 0) {
      const date = new Date(clock.currentTimeMillisUnsafe())
      for (const logger of loggers) {
        logger.log({
          cause,
          fiber,
          date,
          logLevel,
          message
        })
      }
    }
    return void_
  })
}

const withColor = (text: string, ...colors: ReadonlyArray<string>) => {
  let out = ""
  for (let i = 0; i < colors.length; i++) {
    out += `\x1b[${colors[i]}m`
  }
  return out + text + "\x1b[0m"
}
const withColorNoop = (text: string, ..._colors: ReadonlyArray<string>) => text
const colors = {
  bold: "1",
  red: "31",
  green: "32",
  yellow: "33",
  blue: "34",
  cyan: "36",
  white: "37",
  gray: "90",
  black: "30",
  bgBrightRed: "101"
} as const

const logLevelColors: Record<LogLevel.LogLevel, ReadonlyArray<string>> = {
  None: [],
  All: [],
  Trace: [colors.gray],
  Debug: [colors.blue],
  Info: [colors.green],
  Warn: [colors.yellow],
  Error: [colors.red],
  Fatal: [colors.bgBrightRed, colors.black]
}
const logLevelStyle: Record<LogLevel.LogLevel, string> = {
  None: "",
  All: "",
  Trace: "color:gray",
  Debug: "color:blue",
  Info: "color:green",
  Warn: "color:orange",
  Error: "color:red",
  Fatal: "background-color:red;color:white"
}

const defaultDateFormat = (date: Date): string =>
  `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${
    date.getSeconds().toString().padStart(2, "0")
  }.${date.getMilliseconds().toString().padStart(3, "0")}`

const hasProcessStdout = typeof process === "object" &&
  process !== null &&
  typeof process.stdout === "object" &&
  process.stdout !== null
const processStdoutIsTTY = hasProcessStdout &&
  process.stdout.isTTY === true
const hasProcessStdoutOrDeno = hasProcessStdout || "Deno" in globalThis

/** @internal */
export const consolePretty = (options?: {
  readonly colors?: "auto" | boolean | undefined
  readonly formatDate?: ((date: Date) => string) | undefined
  readonly mode?: "browser" | "tty" | "auto" | undefined
}) => {
  const mode_ = options?.mode ?? "auto"
  const mode = mode_ === "auto" ? (hasProcessStdoutOrDeno ? "tty" : "browser") : mode_
  const isBrowser = mode === "browser"
  const showColors = typeof options?.colors === "boolean" ? options.colors : processStdoutIsTTY || isBrowser
  const formatDate = options?.formatDate ?? defaultDateFormat
  return isBrowser
    ? prettyLoggerBrowser({ colors: showColors, formatDate })
    : prettyLoggerTty({ colors: showColors, formatDate })
}

const prettyLoggerTty = (options: {
  readonly colors: boolean
  readonly formatDate: (date: Date) => string
}) => {
  const processIsBun = typeof process === "object" && "isBun" in process && process.isBun === true
  const color = options.colors ? withColor : withColorNoop
  return loggerMake<unknown, void>(
    ({ cause, date, fiber, logLevel, message: message_ }) => {
      const console = fiber.getRef(ConsoleRef)
      // oxlint-disable-next-line no-console
      const log = fiber.getRef(LogToStderr) ? console.error : console.log

      const message = Array.isArray(message_) ? message_.slice() : [message_]

      let firstLine = color(`[${options.formatDate(date)}]`, colors.white)
        + ` ${color(logLevel.toUpperCase(), ...logLevelColors[logLevel])}`
        + ` (#${fiber.id})`

      const now = date.getTime()
      const spans = fiber.getRef(CurrentLogSpans)
      for (const span of spans) {
        firstLine += " " + formatLogSpan(span, now)
      }

      firstLine += ":"
      let messageIndex = 0
      if (message.length > 0) {
        const firstMaybeString = structuredMessage(message[0])
        if (typeof firstMaybeString === "string") {
          firstLine += " " + color(firstMaybeString, colors.bold, colors.cyan)
          messageIndex++
        }
      }

      log(firstLine)
      // oxlint-disable-next-line no-console
      if (!processIsBun) console.group()

      if (cause.reasons.length > 0) {
        log(causePretty(cause))
      }

      if (messageIndex < message.length) {
        for (; messageIndex < message.length; messageIndex++) {
          log(redact(message[messageIndex]))
        }
      }

      const annotations = fiber.getRef(CurrentLogAnnotations)
      for (const [key, value] of Object.entries(annotations)) {
        log(color(`${key}:`, colors.bold, colors.white), redact(value))
      }

      // oxlint-disable-next-line no-console
      if (!processIsBun) console.groupEnd()
    }
  )
}

const prettyLoggerBrowser = (options: {
  readonly colors: boolean
  readonly formatDate: (date: Date) => string
}) => {
  const color = options.colors ? "%c" : ""
  return loggerMake<unknown, void>(
    ({ cause, date, fiber, logLevel, message: message_ }) => {
      const console = fiber.getRef(ConsoleRef)

      const message = Array.isArray(message_) ? message_.slice() : [message_]

      let firstLine = `${color}[${options.formatDate(date)}]`
      const firstParams = []
      if (options.colors) {
        firstParams.push("color:gray")
      }
      firstLine += ` ${color}${logLevel.toUpperCase()}${color} (#${fiber.id})`
      if (options.colors) {
        firstParams.push(logLevelStyle[logLevel], "")
      }

      const now = date.getTime()
      const spans = fiber.getRef(CurrentLogSpans)
      for (const span of spans) {
        firstLine += " " + formatLogSpan(span, now)
      }

      firstLine += ":"

      let messageIndex = 0
      if (message.length > 0) {
        const firstMaybeString = structuredMessage(message[0])
        if (typeof firstMaybeString === "string") {
          firstLine += ` ${color}${firstMaybeString}`
          if (options.colors) {
            firstParams.push("color:deepskyblue")
          }
          messageIndex++
        }
      }

      // oxlint-disable-next-line no-console
      console.groupCollapsed(firstLine, ...firstParams)

      if (cause.reasons.length > 0) {
        // oxlint-disable-next-line no-console
        console.error(causePretty(cause))
      }

      if (messageIndex < message.length) {
        for (; messageIndex < message.length; messageIndex++) {
          // oxlint-disable-next-line no-console
          console.log(redact(message[messageIndex]))
        }
      }

      const annotations = fiber.getRef(CurrentLogAnnotations)
      for (const [key, value] of Object.entries(annotations)) {
        const redacted = redact(value)
        if (options.colors) {
          // oxlint-disable-next-line no-console
          console.log(`%c${key}:`, "color:gray", redacted)
        } else {
          // oxlint-disable-next-line no-console
          console.log(`${key}:`, redacted)
        }
      }

      // oxlint-disable-next-line no-console
      console.groupEnd()
    }
  )
}

/** @internal */
export const defaultLogger = loggerMake<unknown, void>(({ cause, date, fiber, logLevel, message }) => {
  const message_ = Array.isArray(message) ? message.slice() : [message]
  if (cause.reasons.length > 0) {
    message_.push(causePretty(cause))
  }
  const now = date.getTime()
  const spans = fiber.getRef(CurrentLogSpans)
  let spanString = ""
  for (const span of spans) {
    spanString += ` ${formatLogSpan(span, now)}`
  }
  const annotations = fiber.getRef(CurrentLogAnnotations)
  if (Object.keys(annotations).length > 0) {
    message_.push(annotations)
  }
  const console = fiber.getRef(ConsoleRef)
  // oxlint-disable-next-line no-console
  const log = fiber.getRef(LogToStderr) ? console.error : console.log
  log(`[${defaultDateFormat(date)}] ${logLevel.toUpperCase()} (#${fiber.id})${spanString}:`, ...message_)
})

/** @internal */
export const tracerLogger = loggerMake<unknown, void>(({ cause, fiber, logLevel, message }) => {
  const clock = fiber.getRef(ClockRef)
  const annotations = fiber.getRef(CurrentLogAnnotations)
  const span = fiber.currentSpan
  if (span === undefined || span._tag === "ExternalSpan") return
  const attributes: Record<string, unknown> = {}
  for (const [key, value] of Object.entries(annotations)) {
    attributes[key] = value
  }
  attributes["effect.fiberId"] = fiber.id
  attributes["effect.logLevel"] = logLevel.toUpperCase()
  if (cause.reasons.length > 0) {
    attributes["effect.cause"] = causePretty(cause)
  }
  span.event(
    toStringUnknown(Array.isArray(message) && message.length === 1 ? message[0] : message),
    clock.currentTimeNanosUnsafe(),
    attributes
  )
})

/** @internal */
export function interruptChildrenPatch() {
  fiberMiddleware.interruptChildren ??= fiberInterruptChildren
}

/** @internal */
const undefined_ = succeed(undefined)

/** @internal */
export { undefined_ as undefined }

// ----------------------------------------------------------------------------
// ErrorReporter
// ----------------------------------------------------------------------------

/** @internal */
export const withErrorReporting: <
  Arg extends Effect.Effect<any, any, any> | {
    readonly defectsOnly?: boolean | undefined
  } | undefined = {
    readonly defectsOnly?: boolean | undefined
  }
>(
  effectOrOptions: Arg,
  options?: {
    readonly defectsOnly?: boolean | undefined
  } | undefined
) => [Arg] extends [Effect.Effect<infer _A, infer _E, infer _R>] ? Arg
  : <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> = dual(
    (args) => isEffect(args[0]),
    <A, E, R>(
      self: Effect.Effect<A, E, R>,
      options?: {
        readonly defectsOnly?: boolean | undefined
      } | undefined
    ): Effect.Effect<A, E, R> =>
      onError(self, (cause) =>
        withFiber((fiber) => {
          reportCauseUnsafe(fiber, cause, options?.defectsOnly)
          return void_
        }))
  )

/** @internal */
export const reportCauseUnsafe = (
  fiber: Fiber.Fiber<unknown, unknown>,
  cause: Cause.Cause<unknown>,
  defectsOnly?: boolean
) => {
  const reporters = fiber.getRef(CurrentErrorReporters)
  if (reporters.size === 0) return
  if (defectsOnly && !hasDies(cause)) return
  const opts = { cause, fiber, timestamp: fiber.getRef(ClockRef).currentTimeNanosUnsafe() }
  reporters.forEach((reporter) => reporter.report(opts))
}
