/**
 * The `SynchronizedRef` module provides mutable references whose updates are
 * serialized, including updates that run effects before deciding the next
 * value. A `SynchronizedRef<A>` behaves like a `Ref<A>` for reading and basic
 * updates, but uses an internal semaphore so concurrent modifications observe a
 * consistent current value and apply one at a time.
 *
 * **When to use**
 *
 * Use to coordinate shared state that may be updated by many fibers
 * - Running effectful state transitions that must not overlap
 * - Computing both a return value and a new stored value atomically
 * - Applying partial updates with `Option`, where `None` leaves the value
 *   unchanged
 *
 * **Gotchas**
 *
 * - Effectful update functions run while the semaphore is held, so long-running
 *   effects delay other updates to the same ref
 * - Failed effectful updates do not replace the stored value
 * - `getUnsafe` and `makeUnsafe` bypass the `Effect` API and should be reserved
 *   for low-level or carefully controlled code
 *
 * @since 2.0.0
 */
import * as Effect from "./Effect.ts"
import { dual } from "./Function.ts"
import { PipeInspectableProto } from "./internal/core.ts"
import * as Option from "./Option.ts"
import * as Ref from "./Ref.ts"
import * as Semaphore from "./Semaphore.ts"

const TypeId = "~effect/SynchronizedRef"

/**
 * A mutable reference whose update and modify operations are serialized with an
 * internal semaphore, including effectful transformations.
 *
 * **When to use**
 *
 * Use when shared state may be updated by multiple fibers and each update,
 * including effectful state transitions, must observe one current value and run
 * one at a time.
 *
 * @see {@link Ref.Ref} for a plain `Ref` when updates do not need effectful synchronization
 *
 * @category models
 * @since 2.0.0
 */
export interface SynchronizedRef<in out A> extends Ref.Ref<A> {
  readonly [TypeId]: typeof TypeId
  readonly backing: Ref.Ref<A>
  readonly semaphore: Semaphore.Semaphore
}

const Proto = {
  ...PipeInspectableProto,
  [TypeId]: TypeId,
  toJSON(this: SynchronizedRef<any>) {
    return {
      _id: "SynchronizedRef",
      value: this.backing.ref.current
    }
  }
}

/**
 * Creates a `SynchronizedRef` synchronously from an initial value.
 *
 * **When to use**
 *
 * Use when synchronous construction is required outside an Effect workflow.
 *
 * @category constructors
 * @since 4.0.0
 */
export const makeUnsafe = <A>(value: A): SynchronizedRef<A> => {
  const self = Object.create(Proto)
  self.semaphore = Semaphore.makeUnsafe(1)
  self.backing = Ref.makeUnsafe(value)
  return self
}

/**
 * Creates a `SynchronizedRef` from an initial value, wrapped in an `Effect`.
 *
 * **When to use**
 *
 * Use to create a synchronized reference inside an Effect program when later
 * updates may run effects and must be serialized.
 *
 * **Details**
 *
 * The returned effect constructs a fresh `SynchronizedRef` by delegating to
 * `makeUnsafe` when the effect is evaluated.
 *
 * @see {@link makeUnsafe} for synchronous construction when the caller controls safe initialization
 * @see {@link Ref.make} for a plain `Ref` when updates do not need effectful synchronization
 *
 * @category constructors
 * @since 2.0.0
 */
export const make = <A>(value: A): Effect.Effect<SynchronizedRef<A>> => Effect.sync(() => makeUnsafe(value))

/**
 * Reads the current value synchronously, bypassing the `Effect` API and the
 * ref's semaphore.
 *
 * **When to use**
 *
 * Use when you need immediate synchronous access to a `SynchronizedRef` value
 * in low-level code that can safely read outside an `Effect`.
 *
 * @see {@link get} for the Effect-wrapped read when composing inside Effect programs
 *
 * @category getters
 * @since 4.0.0
 */
export const getUnsafe = <A>(self: SynchronizedRef<A>): A => self.backing.ref.current

/**
 * Returns an `Effect` that reads the current value of the `SynchronizedRef`.
 *
 * **When to use**
 *
 * Use to read the current value of a `SynchronizedRef` inside an `Effect`
 * program without changing it.
 *
 * @see {@link getUnsafe} for synchronous reads when the caller controls safe access outside `Effect`
 *
 * @category getters
 * @since 2.0.0
 */
export const get = <A>(self: SynchronizedRef<A>): Effect.Effect<A> => Effect.sync(() => getUnsafe(self))

/**
 * Sets a new value atomically and returns the previous value, serialized by the
 * ref's semaphore.
 *
 * **When to use**
 *
 * Use to replace a `SynchronizedRef` with a known value when the previous value
 * is also needed.
 *
 * @see {@link set} for setting a value without returning the previous value
 * @see {@link setAndGet} for setting a value and returning the new value
 * @see {@link getAndUpdate} for deriving the new value from the current value
 *
 * @category mutations
 * @since 2.0.0
 */
export const getAndSet: {
  /**
   * Sets a new value atomically and returns the previous value, serialized by the
   * ref's semaphore.
   *
   * **When to use**
   *
   * Use to replace a `SynchronizedRef` with a known value when the previous value
   * is also needed.
   *
   * @see {@link set} for setting a value without returning the previous value
   * @see {@link setAndGet} for setting a value and returning the new value
   * @see {@link getAndUpdate} for deriving the new value from the current value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(value: A): (self: SynchronizedRef<A>) => Effect.Effect<A>
  /**
   * Sets a new value atomically and returns the previous value, serialized by the
   * ref's semaphore.
   *
   * **When to use**
   *
   * Use to replace a `SynchronizedRef` with a known value when the previous value
   * is also needed.
   *
   * @see {@link set} for setting a value without returning the previous value
   * @see {@link setAndGet} for setting a value and returning the new value
   * @see {@link getAndUpdate} for deriving the new value from the current value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(self: SynchronizedRef<A>, value: A): Effect.Effect<A>
} = dual(
  2,
  <A>(self: SynchronizedRef<A>, value: A): Effect.Effect<A> =>
    self.semaphore.withPermit(Ref.getAndSet(self.backing, value))
)

/**
 * Updates the current value atomically with a function and returns the previous
 * value, serialized by the ref's semaphore.
 *
 * **When to use**
 *
 * Use to run a pure state update when the previous stored value is also needed.
 *
 * @see {@link update} for updating without returning a value
 * @see {@link updateAndGet} for updating and returning the new value
 * @see {@link getAndUpdateEffect} for effectful updates that return the previous value
 *
 * @category mutations
 * @since 2.0.0
 */
export const getAndUpdate: {
  /**
   * Updates the current value atomically with a function and returns the previous
   * value, serialized by the ref's semaphore.
   *
   * **When to use**
   *
   * Use to run a pure state update when the previous stored value is also needed.
   *
   * @see {@link update} for updating without returning a value
   * @see {@link updateAndGet} for updating and returning the new value
   * @see {@link getAndUpdateEffect} for effectful updates that return the previous value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(f: (a: A) => A): (self: SynchronizedRef<A>) => Effect.Effect<A>
  /**
   * Updates the current value atomically with a function and returns the previous
   * value, serialized by the ref's semaphore.
   *
   * **When to use**
   *
   * Use to run a pure state update when the previous stored value is also needed.
   *
   * @see {@link update} for updating without returning a value
   * @see {@link updateAndGet} for updating and returning the new value
   * @see {@link getAndUpdateEffect} for effectful updates that return the previous value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(self: SynchronizedRef<A>, f: (a: A) => A): Effect.Effect<A>
} = dual(
  2,
  <A>(self: SynchronizedRef<A>, f: (a: A) => A): Effect.Effect<A> =>
    self.semaphore.withPermit(Ref.getAndUpdate(self.backing, f))
)

/**
 * Runs an effectful update atomically while holding the ref's semaphore, sets
 * the new value if the effect succeeds, and returns the previous value.
 *
 * **When to use**
 *
 * Use when an effectful state transition must return the previous stored value.
 *
 * @see {@link getAndUpdate} for pure updates that return the previous value
 * @see {@link updateEffect} for effectful updates without returning a value
 * @see {@link updateAndGetEffect} for effectful updates that return the new value
 * @see {@link modifyEffect} for effectful updates with a custom return value
 * @see {@link getAndUpdateSomeEffect} for conditional effectful updates that return the previous value
 *
 * @category mutations
 * @since 2.0.0
 */
export const getAndUpdateEffect: {
  /**
   * Runs an effectful update atomically while holding the ref's semaphore, sets
   * the new value if the effect succeeds, and returns the previous value.
   *
   * **When to use**
   *
   * Use when an effectful state transition must return the previous stored value.
   *
   * @see {@link getAndUpdate} for pure updates that return the previous value
   * @see {@link updateEffect} for effectful updates without returning a value
   * @see {@link updateAndGetEffect} for effectful updates that return the new value
   * @see {@link modifyEffect} for effectful updates with a custom return value
   * @see {@link getAndUpdateSomeEffect} for conditional effectful updates that return the previous value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, R, E>(f: (a: A) => Effect.Effect<A, E, R>): (self: SynchronizedRef<A>) => Effect.Effect<A, E, R>
  /**
   * Runs an effectful update atomically while holding the ref's semaphore, sets
   * the new value if the effect succeeds, and returns the previous value.
   *
   * **When to use**
   *
   * Use when an effectful state transition must return the previous stored value.
   *
   * @see {@link getAndUpdate} for pure updates that return the previous value
   * @see {@link updateEffect} for effectful updates without returning a value
   * @see {@link updateAndGetEffect} for effectful updates that return the new value
   * @see {@link modifyEffect} for effectful updates with a custom return value
   * @see {@link getAndUpdateSomeEffect} for conditional effectful updates that return the previous value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, R, E>(self: SynchronizedRef<A>, f: (a: A) => Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
} = dual(
  2,
  <A, R, E>(self: SynchronizedRef<A>, f: (a: A) => Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
    self.semaphore.withPermit(Effect.suspend(() => {
      const value = getUnsafe(self)
      return Effect.map(f(value), (newValue) => {
        self.backing.ref.current = newValue
        return value
      })
    }))
)

/**
 * Applies a partial update atomically and returns the previous value. If the
 * function returns `Option.some`, the ref is updated; if it returns
 * `Option.none`, the ref is left unchanged.
 *
 * **When to use**
 *
 * Use to return the previous value while applying a pure conditional update.
 *
 * @see {@link getAndUpdate} for always applying a pure update
 * @see {@link updateSome} for applying a pure conditional update without returning the previous value
 *
 * @category mutations
 * @since 2.0.0
 */
export const getAndUpdateSome: {
  /**
   * Applies a partial update atomically and returns the previous value. If the
   * function returns `Option.some`, the ref is updated; if it returns
   * `Option.none`, the ref is left unchanged.
   *
   * **When to use**
   *
   * Use to return the previous value while applying a pure conditional update.
   *
   * @see {@link getAndUpdate} for always applying a pure update
   * @see {@link updateSome} for applying a pure conditional update without returning the previous value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(pf: (a: A) => Option.Option<A>): (self: SynchronizedRef<A>) => Effect.Effect<A>
  /**
   * Applies a partial update atomically and returns the previous value. If the
   * function returns `Option.some`, the ref is updated; if it returns
   * `Option.none`, the ref is left unchanged.
   *
   * **When to use**
   *
   * Use to return the previous value while applying a pure conditional update.
   *
   * @see {@link getAndUpdate} for always applying a pure update
   * @see {@link updateSome} for applying a pure conditional update without returning the previous value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(self: SynchronizedRef<A>, pf: (a: A) => Option.Option<A>): Effect.Effect<A>
} = dual(
  2,
  <A>(self: SynchronizedRef<A>, pf: (a: A) => Option.Option<A>): Effect.Effect<A> =>
    self.semaphore.withPermit(Ref.getAndUpdateSome(self, pf))
)

/**
 * Runs an effectful partial update atomically while holding the ref's semaphore
 * and returns the previous value. `Option.some` updates the ref; `Option.none`
 * leaves it unchanged.
 *
 * **When to use**
 *
 * Use to return the previous value while running an effectful conditional
 * update.
 *
 * @see {@link getAndUpdateSome} for the pure conditional variant
 * @see {@link updateSomeEffect} for effectful conditional updates without returning the previous value
 *
 * @category mutations
 * @since 2.0.0
 */
export const getAndUpdateSomeEffect: {
  /**
   * Runs an effectful partial update atomically while holding the ref's semaphore
   * and returns the previous value. `Option.some` updates the ref; `Option.none`
   * leaves it unchanged.
   *
   * **When to use**
   *
   * Use to return the previous value while running an effectful conditional
   * update.
   *
   * @see {@link getAndUpdateSome} for the pure conditional variant
   * @see {@link updateSomeEffect} for effectful conditional updates without returning the previous value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, R, E>(pf: (a: A) => Effect.Effect<Option.Option<A>, E, R>): (self: SynchronizedRef<A>) => Effect.Effect<A, E, R>
  /**
   * Runs an effectful partial update atomically while holding the ref's semaphore
   * and returns the previous value. `Option.some` updates the ref; `Option.none`
   * leaves it unchanged.
   *
   * **When to use**
   *
   * Use to return the previous value while running an effectful conditional
   * update.
   *
   * @see {@link getAndUpdateSome} for the pure conditional variant
   * @see {@link updateSomeEffect} for effectful conditional updates without returning the previous value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, R, E>(
    self: SynchronizedRef<A>,
    pf: (a: A) => Effect.Effect<Option.Option<A>, E, R>
  ): Effect.Effect<A, E, R>
} = dual(
  2,
  <A, R, E>(self: SynchronizedRef<A>, pf: (a: A) => Effect.Effect<Option.Option<A>, E, R>): Effect.Effect<A, E, R> =>
    self.semaphore.withPermit(Effect.suspend(() => {
      const value = getUnsafe(self)
      return Effect.flatMap(pf(value), (option) => {
        if (Option.isNone(option)) {
          return Effect.succeed(value)
        }
        self.backing.ref.current = option.value
        return Effect.succeed(value)
      })
    }))
)

/**
 * Computes a return value and a new ref value atomically, stores the new value,
 * and returns the computed result.
 *
 * **When to use**
 *
 * Use to derive a separate result and the next stored value from the same
 * current value in one serialized pure update.
 *
 * @see {@link modifyEffect} for effectfully deriving both the result and next stored value
 * @see {@link modifySome} for deriving a result and optionally updating the stored value
 * @see {@link updateAndGet} for returning the new stored value instead of a separate result
 *
 * @category mutations
 * @since 2.0.0
 */
export const modify: {
  /**
   * Computes a return value and a new ref value atomically, stores the new value,
   * and returns the computed result.
   *
   * **When to use**
   *
   * Use to derive a separate result and the next stored value from the same
   * current value in one serialized pure update.
   *
   * @see {@link modifyEffect} for effectfully deriving both the result and next stored value
   * @see {@link modifySome} for deriving a result and optionally updating the stored value
   * @see {@link updateAndGet} for returning the new stored value instead of a separate result
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, B>(f: (a: A) => readonly [B, A]): (self: SynchronizedRef<A>) => Effect.Effect<B>
  /**
   * Computes a return value and a new ref value atomically, stores the new value,
   * and returns the computed result.
   *
   * **When to use**
   *
   * Use to derive a separate result and the next stored value from the same
   * current value in one serialized pure update.
   *
   * @see {@link modifyEffect} for effectfully deriving both the result and next stored value
   * @see {@link modifySome} for deriving a result and optionally updating the stored value
   * @see {@link updateAndGet} for returning the new stored value instead of a separate result
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, B>(self: SynchronizedRef<A>, f: (a: A) => readonly [B, A]): Effect.Effect<B>
} = dual(
  2,
  <A, B>(self: SynchronizedRef<A>, f: (a: A) => readonly [B, A]): Effect.Effect<B> =>
    self.semaphore.withPermit(Ref.modify(self.backing, f))
)

/**
 * Runs an effectful modification atomically while holding the ref's semaphore,
 * stores the new value if the effect succeeds, and returns the computed result.
 *
 * **When to use**
 *
 * Use to effectfully compute both a separate return value and the next stored
 * value in one serialized update.
 *
 * @see {@link modify} for the pure variant
 * @see {@link updateEffect} for effectfully storing a new value without a separate result
 *
 * @category mutations
 * @since 2.0.0
 */
export const modifyEffect: {
  /**
   * Runs an effectful modification atomically while holding the ref's semaphore,
   * stores the new value if the effect succeeds, and returns the computed result.
   *
   * **When to use**
   *
   * Use to effectfully compute both a separate return value and the next stored
   * value in one serialized update.
   *
   * @see {@link modify} for the pure variant
   * @see {@link updateEffect} for effectfully storing a new value without a separate result
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, B, E, R>(f: (a: A) => Effect.Effect<readonly [B, A], E, R>): (self: SynchronizedRef<A>) => Effect.Effect<B, E, R>
  /**
   * Runs an effectful modification atomically while holding the ref's semaphore,
   * stores the new value if the effect succeeds, and returns the computed result.
   *
   * **When to use**
   *
   * Use to effectfully compute both a separate return value and the next stored
   * value in one serialized update.
   *
   * @see {@link modify} for the pure variant
   * @see {@link updateEffect} for effectfully storing a new value without a separate result
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, B, E, R>(
    self: SynchronizedRef<A>,
    f: (a: A) => Effect.Effect<readonly [B, A], E, R>
  ): Effect.Effect<B, E, R>
} = dual(
  2,
  <A, B, E, R>(self: SynchronizedRef<A>, f: (a: A) => Effect.Effect<readonly [B, A], E, R>): Effect.Effect<B, E, R> =>
    self.semaphore.withPermit(Effect.suspend(() => {
      const value = getUnsafe(self)
      return Effect.map(f(value), ([b, a]) => {
        self.backing.ref.current = a
        return b
      })
    }))
)

/**
 * Computes a return value and an optional new ref value atomically.
 * `Option.some` updates the ref; `Option.none` leaves it unchanged.
 *
 * **When to use**
 *
 * Use to compute a return value while optionally updating the stored value.
 *
 * @see {@link modify} for always storing a new value
 * @see {@link updateSome} for optional updates without a separate return value
 *
 * @category mutations
 * @since 2.0.0
 */
export const modifySome: {
  /**
   * Computes a return value and an optional new ref value atomically.
   * `Option.some` updates the ref; `Option.none` leaves it unchanged.
   *
   * **When to use**
   *
   * Use to compute a return value while optionally updating the stored value.
   *
   * @see {@link modify} for always storing a new value
   * @see {@link updateSome} for optional updates without a separate return value
   *
   * @category mutations
   * @since 2.0.0
   */
  <B, A>(pf: (a: A) => readonly [B, Option.Option<A>]): (self: SynchronizedRef<A>) => Effect.Effect<B>
  /**
   * Computes a return value and an optional new ref value atomically.
   * `Option.some` updates the ref; `Option.none` leaves it unchanged.
   *
   * **When to use**
   *
   * Use to compute a return value while optionally updating the stored value.
   *
   * @see {@link modify} for always storing a new value
   * @see {@link updateSome} for optional updates without a separate return value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, B>(self: SynchronizedRef<A>, pf: (a: A) => readonly [B, Option.Option<A>]): Effect.Effect<B>
} = dual(
  2,
  <A, B>(
    self: SynchronizedRef<A>,
    pf: (a: A) => readonly [B, Option.Option<A>]
  ): Effect.Effect<B> => self.semaphore.withPermit(Ref.modifySome(self.backing, pf))
)

/**
 * Runs an effectful modification atomically while holding the ref's semaphore.
 * The effect computes a return value and an optional new ref value;
 * `Option.some` updates the ref and `Option.none` leaves it unchanged.
 *
 * **When to use**
 *
 * Use to effectfully compute a return value while optionally updating the
 * stored value.
 *
 * @see {@link modifySome} for the pure variant
 * @see {@link updateSomeEffect} for effectful optional updates without a separate return value
 *
 * @category mutations
 * @since 2.0.0
 */
export const modifySomeEffect: {
  /**
   * Runs an effectful modification atomically while holding the ref's semaphore.
   * The effect computes a return value and an optional new ref value;
   * `Option.some` updates the ref and `Option.none` leaves it unchanged.
   *
   * **When to use**
   *
   * Use to effectfully compute a return value while optionally updating the
   * stored value.
   *
   * @see {@link modifySome} for the pure variant
   * @see {@link updateSomeEffect} for effectful optional updates without a separate return value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, B, R, E>(
    fallback: B,
    pf: (a: A) => Effect.Effect<readonly [B, Option.Option<A>], E, R>
  ): (self: SynchronizedRef<A>) => Effect.Effect<B, E, R>
  /**
   * Runs an effectful modification atomically while holding the ref's semaphore.
   * The effect computes a return value and an optional new ref value;
   * `Option.some` updates the ref and `Option.none` leaves it unchanged.
   *
   * **When to use**
   *
   * Use to effectfully compute a return value while optionally updating the
   * stored value.
   *
   * @see {@link modifySome} for the pure variant
   * @see {@link updateSomeEffect} for effectful optional updates without a separate return value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, B, R, E>(
    self: SynchronizedRef<A>,
    pf: (a: A) => Effect.Effect<readonly [B, Option.Option<A>], E, R>
  ): Effect.Effect<B, E, R>
} = dual(
  2,
  <A, B, R, E>(
    self: SynchronizedRef<A>,
    pf: (a: A) => Effect.Effect<readonly [B, Option.Option<A>], E, R>
  ): Effect.Effect<B, E, R> =>
    self.semaphore.withPermit(Effect.suspend(() => {
      const value = getUnsafe(self)
      return Effect.flatMap(pf(value), ([b, maybeA]) => {
        if (Option.isNone(maybeA)) {
          return Effect.succeed(b)
        }
        self.backing.ref.current = maybeA.value
        return Effect.succeed(b)
      })
    }))
)

/**
 * Sets the value of the `SynchronizedRef`, serialized by the ref's semaphore.
 *
 * **When to use**
 *
 * Use to replace the current value of a `SynchronizedRef` with a known value
 * while keeping the write serialized with other synchronized updates.
 *
 * @see {@link getAndSet} for replacing the value when the previous value is needed
 * @see {@link setAndGet} for replacing the value when the new value should be returned
 * @see {@link update} for deriving the next value from the current value
 *
 * @category mutations
 * @since 2.0.0
 */
export const set: {
  /**
   * Sets the value of the `SynchronizedRef`, serialized by the ref's semaphore.
   *
   * **When to use**
   *
   * Use to replace the current value of a `SynchronizedRef` with a known value
   * while keeping the write serialized with other synchronized updates.
   *
   * @see {@link getAndSet} for replacing the value when the previous value is needed
   * @see {@link setAndGet} for replacing the value when the new value should be returned
   * @see {@link update} for deriving the next value from the current value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(value: A): (self: SynchronizedRef<A>) => Effect.Effect<void>
  /**
   * Sets the value of the `SynchronizedRef`, serialized by the ref's semaphore.
   *
   * **When to use**
   *
   * Use to replace the current value of a `SynchronizedRef` with a known value
   * while keeping the write serialized with other synchronized updates.
   *
   * @see {@link getAndSet} for replacing the value when the previous value is needed
   * @see {@link setAndGet} for replacing the value when the new value should be returned
   * @see {@link update} for deriving the next value from the current value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(self: SynchronizedRef<A>, value: A): Effect.Effect<void>
} = dual(
  2,
  <A>(self: SynchronizedRef<A>, value: A): Effect.Effect<void> =>
    self.semaphore.withPermit(Ref.set(self.backing, value))
)

/**
 * Sets the value of the `SynchronizedRef` and returns the new value.
 *
 * **When to use**
 *
 * Use to replace the current value with a known value and return that new
 * value.
 *
 * @see {@link set} for setting without returning a value
 * @see {@link getAndSet} for setting while returning the previous value
 *
 * @category mutations
 * @since 2.0.0
 */
export const setAndGet: {
  /**
   * Sets the value of the `SynchronizedRef` and returns the new value.
   *
   * **When to use**
   *
   * Use to replace the current value with a known value and return that new
   * value.
   *
   * @see {@link set} for setting without returning a value
   * @see {@link getAndSet} for setting while returning the previous value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(value: A): (self: SynchronizedRef<A>) => Effect.Effect<A>
  /**
   * Sets the value of the `SynchronizedRef` and returns the new value.
   *
   * **When to use**
   *
   * Use to replace the current value with a known value and return that new
   * value.
   *
   * @see {@link set} for setting without returning a value
   * @see {@link getAndSet} for setting while returning the previous value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(self: SynchronizedRef<A>, value: A): Effect.Effect<A>
} = dual(
  2,
  <A>(self: SynchronizedRef<A>, value: A): Effect.Effect<A> =>
    self.semaphore.withPermit(Ref.setAndGet(self.backing, value))
)

/**
 * Updates the value of the `SynchronizedRef` with a function, serialized by the
 * ref's semaphore.
 *
 * **When to use**
 *
 * Use to apply a pure state transition to a `SynchronizedRef` as a serialized
 * `Effect`.
 *
 * @see {@link updateEffect} for effectfully deriving the next value
 * @see {@link updateAndGet} for returning the new stored value
 * @see {@link getAndUpdate} for returning the previous stored value
 *
 * @category mutations
 * @since 2.0.0
 */
export const update: {
  /**
   * Updates the value of the `SynchronizedRef` with a function, serialized by the
   * ref's semaphore.
   *
   * **When to use**
   *
   * Use to apply a pure state transition to a `SynchronizedRef` as a serialized
   * `Effect`.
   *
   * @see {@link updateEffect} for effectfully deriving the next value
   * @see {@link updateAndGet} for returning the new stored value
   * @see {@link getAndUpdate} for returning the previous stored value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(f: (a: A) => A): (self: SynchronizedRef<A>) => Effect.Effect<void>
  /**
   * Updates the value of the `SynchronizedRef` with a function, serialized by the
   * ref's semaphore.
   *
   * **When to use**
   *
   * Use to apply a pure state transition to a `SynchronizedRef` as a serialized
   * `Effect`.
   *
   * @see {@link updateEffect} for effectfully deriving the next value
   * @see {@link updateAndGet} for returning the new stored value
   * @see {@link getAndUpdate} for returning the previous stored value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(self: SynchronizedRef<A>, f: (a: A) => A): Effect.Effect<void>
} = dual(
  2,
  <A>(self: SynchronizedRef<A>, f: (a: A) => A): Effect.Effect<void> =>
    self.semaphore.withPermit(Ref.update(self.backing, f))
)

/**
 * Runs an effectful update while holding the ref's semaphore and stores the new
 * value if the effect succeeds.
 *
 * **When to use**
 *
 * Use to run an effectful state transition on a `SynchronizedRef` when storing
 * the new value is the only result you need.
 *
 * @see {@link update} for a pure state transition
 * @see {@link getAndUpdateEffect} for returning the previous stored value
 * @see {@link updateAndGetEffect} for returning the new stored value
 * @see {@link modifyEffect} for returning a separate result while storing a new value
 * @see {@link updateSomeEffect} for effectfully applying only some state transitions
 *
 * @category mutations
 * @since 2.0.0
 */
export const updateEffect: {
  /**
   * Runs an effectful update while holding the ref's semaphore and stores the new
   * value if the effect succeeds.
   *
   * **When to use**
   *
   * Use to run an effectful state transition on a `SynchronizedRef` when storing
   * the new value is the only result you need.
   *
   * @see {@link update} for a pure state transition
   * @see {@link getAndUpdateEffect} for returning the previous stored value
   * @see {@link updateAndGetEffect} for returning the new stored value
   * @see {@link modifyEffect} for returning a separate result while storing a new value
   * @see {@link updateSomeEffect} for effectfully applying only some state transitions
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, R, E>(f: (a: A) => Effect.Effect<A, E, R>): (self: SynchronizedRef<A>) => Effect.Effect<void, E, R>
  /**
   * Runs an effectful update while holding the ref's semaphore and stores the new
   * value if the effect succeeds.
   *
   * **When to use**
   *
   * Use to run an effectful state transition on a `SynchronizedRef` when storing
   * the new value is the only result you need.
   *
   * @see {@link update} for a pure state transition
   * @see {@link getAndUpdateEffect} for returning the previous stored value
   * @see {@link updateAndGetEffect} for returning the new stored value
   * @see {@link modifyEffect} for returning a separate result while storing a new value
   * @see {@link updateSomeEffect} for effectfully applying only some state transitions
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, R, E>(self: SynchronizedRef<A>, f: (a: A) => Effect.Effect<A, E, R>): Effect.Effect<void, E, R>
} = dual(
  2,
  <A, R, E>(self: SynchronizedRef<A>, f: (a: A) => Effect.Effect<A, E, R>): Effect.Effect<void, E, R> =>
    self.semaphore.withPermit(Effect.suspend(() => {
      const value = getUnsafe(self)
      return Effect.map(f(value), (newValue) => {
        self.backing.ref.current = newValue
      })
    }))
)

/**
 * Updates the value of the `SynchronizedRef` with a function and returns the
 * new value.
 *
 * **When to use**
 *
 * Use to apply a pure state transition and return the new stored value.
 *
 * @see {@link update} for updating without returning the new value
 * @see {@link getAndUpdate} for updating while returning the previous value
 *
 * @category mutations
 * @since 2.0.0
 */
export const updateAndGet: {
  /**
   * Updates the value of the `SynchronizedRef` with a function and returns the
   * new value.
   *
   * **When to use**
   *
   * Use to apply a pure state transition and return the new stored value.
   *
   * @see {@link update} for updating without returning the new value
   * @see {@link getAndUpdate} for updating while returning the previous value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(f: (a: A) => A): (self: SynchronizedRef<A>) => Effect.Effect<A>
  /**
   * Updates the value of the `SynchronizedRef` with a function and returns the
   * new value.
   *
   * **When to use**
   *
   * Use to apply a pure state transition and return the new stored value.
   *
   * @see {@link update} for updating without returning the new value
   * @see {@link getAndUpdate} for updating while returning the previous value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(self: SynchronizedRef<A>, f: (a: A) => A): Effect.Effect<A>
} = dual(
  2,
  <A>(self: SynchronizedRef<A>, f: (a: A) => A): Effect.Effect<A> =>
    self.semaphore.withPermit(Ref.updateAndGet(self.backing, f))
)

/**
 * Runs an effectful update while holding the ref's semaphore, stores the new
 * value if the effect succeeds, and returns that new value.
 *
 * **When to use**
 *
 * Use to run an effectful state transition and return the new stored value.
 *
 * @see {@link updateEffect} for effectful updates without returning the new value
 * @see {@link updateAndGet} for the pure variant
 *
 * @category mutations
 * @since 2.0.0
 */
export const updateAndGetEffect: {
  /**
   * Runs an effectful update while holding the ref's semaphore, stores the new
   * value if the effect succeeds, and returns that new value.
   *
   * **When to use**
   *
   * Use to run an effectful state transition and return the new stored value.
   *
   * @see {@link updateEffect} for effectful updates without returning the new value
   * @see {@link updateAndGet} for the pure variant
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, R, E>(f: (a: A) => Effect.Effect<A, E, R>): (self: SynchronizedRef<A>) => Effect.Effect<A, E, R>
  /**
   * Runs an effectful update while holding the ref's semaphore, stores the new
   * value if the effect succeeds, and returns that new value.
   *
   * **When to use**
   *
   * Use to run an effectful state transition and return the new stored value.
   *
   * @see {@link updateEffect} for effectful updates without returning the new value
   * @see {@link updateAndGet} for the pure variant
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, R, E>(self: SynchronizedRef<A>, f: (a: A) => Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
} = dual(
  2,
  <A, R, E>(self: SynchronizedRef<A>, f: (a: A) => Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
    self.semaphore.withPermit(Effect.suspend(() => {
      const value = getUnsafe(self)
      return Effect.map(f(value), (newValue) => {
        self.backing.ref.current = newValue
        return newValue
      })
    }))
)

/**
 * Applies a partial update to the current value. `Option.some` stores the new
 * value; `Option.none` leaves the ref unchanged.
 *
 * **When to use**
 *
 * Use to apply a pure conditional update without returning a value.
 *
 * @see {@link update} for always applying a pure update
 * @see {@link updateSomeAndGet} for returning the resulting current value
 *
 * @category mutations
 * @since 2.0.0
 */
export const updateSome: {
  /**
   * Applies a partial update to the current value. `Option.some` stores the new
   * value; `Option.none` leaves the ref unchanged.
   *
   * **When to use**
   *
   * Use to apply a pure conditional update without returning a value.
   *
   * @see {@link update} for always applying a pure update
   * @see {@link updateSomeAndGet} for returning the resulting current value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(f: (a: A) => Option.Option<A>): (self: SynchronizedRef<A>) => Effect.Effect<void>
  /**
   * Applies a partial update to the current value. `Option.some` stores the new
   * value; `Option.none` leaves the ref unchanged.
   *
   * **When to use**
   *
   * Use to apply a pure conditional update without returning a value.
   *
   * @see {@link update} for always applying a pure update
   * @see {@link updateSomeAndGet} for returning the resulting current value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(self: SynchronizedRef<A>, f: (a: A) => Option.Option<A>): Effect.Effect<void>
} = dual(
  2,
  <A>(self: SynchronizedRef<A>, f: (a: A) => Option.Option<A>): Effect.Effect<void> =>
    self.semaphore.withPermit(Ref.updateSome(self.backing, f))
)

/**
 * Runs an effectful partial update while holding the ref's semaphore.
 * `Option.some` stores the new value; `Option.none` leaves the ref unchanged.
 *
 * **When to use**
 *
 * Use to run an effectful conditional update without returning a value.
 *
 * @see {@link updateSome} for the pure conditional variant
 * @see {@link updateEffect} for effectful updates that always store a new value
 *
 * @category mutations
 * @since 2.0.0
 */
export const updateSomeEffect: {
  /**
   * Runs an effectful partial update while holding the ref's semaphore.
   * `Option.some` stores the new value; `Option.none` leaves the ref unchanged.
   *
   * **When to use**
   *
   * Use to run an effectful conditional update without returning a value.
   *
   * @see {@link updateSome} for the pure conditional variant
   * @see {@link updateEffect} for effectful updates that always store a new value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, R, E>(pf: (a: A) => Effect.Effect<Option.Option<A>, E, R>): (self: SynchronizedRef<A>) => Effect.Effect<void, E, R>
  /**
   * Runs an effectful partial update while holding the ref's semaphore.
   * `Option.some` stores the new value; `Option.none` leaves the ref unchanged.
   *
   * **When to use**
   *
   * Use to run an effectful conditional update without returning a value.
   *
   * @see {@link updateSome} for the pure conditional variant
   * @see {@link updateEffect} for effectful updates that always store a new value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, R, E>(
    self: SynchronizedRef<A>,
    pf: (a: A) => Effect.Effect<Option.Option<A>, E, R>
  ): Effect.Effect<void, E, R>
} = dual(
  2,
  <A, R, E>(self: SynchronizedRef<A>, pf: (a: A) => Effect.Effect<Option.Option<A>, E, R>): Effect.Effect<void, E, R> =>
    self.semaphore.withPermit(Effect.suspend(() => {
      const value = getUnsafe(self)
      return Effect.map(pf(value), (option) => {
        if (Option.isNone(option)) {
          return
        }
        self.backing.ref.current = option.value
      })
    }))
)

/**
 * Applies a partial update and returns the resulting current value.
 * `Option.some` stores and returns the new value; `Option.none` returns the
 * unchanged value.
 *
 * **When to use**
 *
 * Use to apply a pure conditional update and return the resulting current
 * value.
 *
 * @see {@link updateSome} for conditional updates without returning a value
 * @see {@link updateAndGet} for always applying a pure update and returning the new value
 *
 * @category mutations
 * @since 2.0.0
 */
export const updateSomeAndGet: {
  /**
   * Applies a partial update and returns the resulting current value.
   * `Option.some` stores and returns the new value; `Option.none` returns the
   * unchanged value.
   *
   * **When to use**
   *
   * Use to apply a pure conditional update and return the resulting current
   * value.
   *
   * @see {@link updateSome} for conditional updates without returning a value
   * @see {@link updateAndGet} for always applying a pure update and returning the new value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(pf: (a: A) => Option.Option<A>): (self: SynchronizedRef<A>) => Effect.Effect<A>
  /**
   * Applies a partial update and returns the resulting current value.
   * `Option.some` stores and returns the new value; `Option.none` returns the
   * unchanged value.
   *
   * **When to use**
   *
   * Use to apply a pure conditional update and return the resulting current
   * value.
   *
   * @see {@link updateSome} for conditional updates without returning a value
   * @see {@link updateAndGet} for always applying a pure update and returning the new value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A>(self: SynchronizedRef<A>, pf: (a: A) => Option.Option<A>): Effect.Effect<A>
} = dual(
  2,
  <A>(self: SynchronizedRef<A>, pf: (a: A) => Option.Option<A>): Effect.Effect<A> =>
    self.semaphore.withPermit(Ref.updateSomeAndGet(self.backing, pf))
)

/**
 * Runs an effectful partial update while holding the ref's semaphore and
 * returns the resulting current value. `Option.some` stores and returns the new
 * value; `Option.none` returns the unchanged value.
 *
 * **When to use**
 *
 * Use to run an effectful conditional update and return the resulting current
 * value.
 *
 * @see {@link updateSomeEffect} for effectful conditional updates without returning a value
 * @see {@link updateAndGetEffect} for effectful updates that always store and return a new value
 *
 * @category mutations
 * @since 2.0.0
 */
export const updateSomeAndGetEffect: {
  /**
   * Runs an effectful partial update while holding the ref's semaphore and
   * returns the resulting current value. `Option.some` stores and returns the new
   * value; `Option.none` returns the unchanged value.
   *
   * **When to use**
   *
   * Use to run an effectful conditional update and return the resulting current
   * value.
   *
   * @see {@link updateSomeEffect} for effectful conditional updates without returning a value
   * @see {@link updateAndGetEffect} for effectful updates that always store and return a new value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, R, E>(pf: (a: A) => Effect.Effect<Option.Option<A>, E, R>): (self: SynchronizedRef<A>) => Effect.Effect<A, E, R>
  /**
   * Runs an effectful partial update while holding the ref's semaphore and
   * returns the resulting current value. `Option.some` stores and returns the new
   * value; `Option.none` returns the unchanged value.
   *
   * **When to use**
   *
   * Use to run an effectful conditional update and return the resulting current
   * value.
   *
   * @see {@link updateSomeEffect} for effectful conditional updates without returning a value
   * @see {@link updateAndGetEffect} for effectful updates that always store and return a new value
   *
   * @category mutations
   * @since 2.0.0
   */
  <A, R, E>(
    self: SynchronizedRef<A>,
    pf: (a: A) => Effect.Effect<Option.Option<A>, E, R>
  ): Effect.Effect<A, E, R>
} = dual(
  2,
  <A, R, E>(self: SynchronizedRef<A>, pf: (a: A) => Effect.Effect<Option.Option<A>, E, R>): Effect.Effect<A, E, R> =>
    self.semaphore.withPermit(Effect.suspend(() => {
      const value = getUnsafe(self)
      return Effect.flatMap(pf(value), (option) => {
        if (Option.isNone(option)) {
          return Effect.succeed(value)
        }
        self.backing.ref.current = option.value
        return Effect.succeed(option.value)
      })
    }))
)
