/**
 * The `PartitionedSemaphore` module provides a semaphore for limiting
 * concurrency across a shared permit pool while keeping waiters grouped by
 * partition key. A `PartitionedSemaphore<K>` is useful when many independent
 * groups of work compete for the same bounded resource and each group should
 * make progress without one busy group monopolizing released permits.
 *
 * **Mental model**
 *
 * - The semaphore has a fixed shared capacity measured in permits
 * - Work acquires permits with a partition key of type `K`
 * - Waiting acquisitions are tracked per partition
 * - Released permits are assigned to waiting partitions in round-robin order
 * - `withPermit` and `withPermits` acquire permits around an effect and
 *   release them when the effect exits, fails, or is interrupted
 *
 * **Common tasks**
 *
 * - Create a semaphore: {@link make}, {@link makeUnsafe}
 * - Inspect capacity and availability: {@link capacity}, {@link available}
 * - Acquire and release manually: {@link take}, {@link release}
 * - Limit a single operation per partition: {@link withPermit}
 * - Limit weighted work per partition: {@link withPermits}
 * - Run only when permits are immediately available:
 *   {@link withPermitsIfAvailable}
 *
 * **Gotchas**
 *
 * - `withPermitsIfAvailable` does not use a partition key; it only succeeds
 *   when the shared pool has enough permits immediately
 * - Acquiring more permits than the semaphore capacity never completes
 * - Requests for zero or negative permits complete without acquiring anything
 * - Non-finite capacities create an unbounded semaphore whose acquire and
 *   release operations complete immediately
 *
 * @since 4.0.0
 */
import * as Effect from "./Effect.js";
import { dual } from "./Function.js";
import * as MutableHashMap from "./MutableHashMap.js";
import * as Option from "./Option.js";
/**
 * Runtime type identifier used to mark values that implement
 * `PartitionedSemaphore`.
 *
 * **Details**
 *
 * This marker is part of the runtime representation of partitioned semaphore
 * values.
 *
 * @category type IDs
 * @since 4.0.0
 */
export const PartitionedTypeId = "~effect/PartitionedSemaphore";
/**
 * Constructs a `PartitionedSemaphore` synchronously, outside of `Effect`.
 *
 * **When to use**
 *
 * Use when a partitioned semaphore must be constructed synchronously outside an
 * `Effect` workflow.
 *
 * **Details**
 *
 * Negative permit counts are clamped to `0`. Non-finite permit counts create
 * an unbounded semaphore whose acquire and release operations complete
 * immediately.
 *
 * @see {@link make} for creating a partitioned semaphore inside `Effect`
 *
 * @category constructors
 * @since 3.19.4
 */
export const makeUnsafe = options => {
  const maxPermits = Math.max(0, options.permits);
  if (!Number.isFinite(maxPermits)) {
    return {
      [PartitionedTypeId]: PartitionedTypeId,
      capacity: maxPermits,
      available: Effect.succeed(maxPermits),
      take: () => Effect.void,
      release: () => Effect.succeed(maxPermits),
      withPermits: () => effect => effect,
      withPermit: () => effect => effect,
      withPermitsIfAvailable: () => effect => Effect.asSome(effect)
    };
  }
  let totalPermits = maxPermits;
  let waitingPermits = 0;
  const partitions = MutableHashMap.empty();
  let iterator = partitions[Symbol.iterator]();
  const releaseUnsafe = permits => {
    while (permits > 0) {
      if (waitingPermits === 0) {
        totalPermits = Math.min(maxPermits, totalPermits + permits);
        return totalPermits;
      }
      let state = iterator.next();
      if (state.done) {
        iterator = partitions[Symbol.iterator]();
        state = iterator.next();
        if (state.done) {
          return totalPermits;
        }
      }
      const waiter = state.value[1].values().next().value;
      if (waiter === undefined) {
        continue;
      }
      waiter.permits -= 1;
      waitingPermits -= 1;
      if (waiter.permits === 0) {
        waiter.resume();
      }
      permits -= 1;
    }
    return totalPermits;
  };
  const take = (key, permits) => {
    if (permits <= 0) {
      return Effect.void;
    }
    return Effect.callback(resume => {
      if (maxPermits < permits) {
        resume(Effect.never);
        return;
      }
      if (totalPermits >= permits) {
        totalPermits -= permits;
        resume(Effect.void);
        return;
      }
      const needed = permits - totalPermits;
      const taken = permits - needed;
      if (totalPermits > 0) {
        totalPermits = 0;
      }
      waitingPermits += needed;
      const waiters = Option.getOrElse(MutableHashMap.get(partitions, key), () => {
        const set = new Set();
        MutableHashMap.set(partitions, key, set);
        return set;
      });
      const entry = {
        permits: needed,
        resume: () => {
          cleanup();
          resume(Effect.void);
        }
      };
      const cleanup = () => {
        waiters.delete(entry);
        if (waiters.size === 0) {
          MutableHashMap.remove(partitions, key);
        }
      };
      waiters.add(entry);
      return Effect.sync(() => {
        cleanup();
        waitingPermits -= entry.permits;
        if (taken > 0) {
          releaseUnsafe(taken);
        }
      });
    });
  };
  const withPermits = (key, permits) => effect => {
    if (permits <= 0) {
      return effect;
    }
    const takePermits = take(key, permits);
    return Effect.uninterruptibleMask(restore => Effect.flatMap(restore(takePermits), () => Effect.ensuring(restore(effect), Effect.sync(() => {
      releaseUnsafe(permits);
    }))));
  };
  const tryTake = permits => {
    if (permits <= 0) {
      return true;
    }
    if (maxPermits < permits || totalPermits < permits) {
      return false;
    }
    totalPermits -= permits;
    return true;
  };
  return {
    [PartitionedTypeId]: PartitionedTypeId,
    capacity: maxPermits,
    available: Effect.sync(() => totalPermits),
    take,
    release: permits => Effect.sync(() => releaseUnsafe(permits)),
    withPermits,
    withPermit: key => withPermits(key, 1),
    withPermitsIfAvailable: permits => effect => {
      if (permits <= 0) {
        return Effect.asSome(effect);
      }
      return Effect.suspend(() => {
        if (!tryTake(permits)) {
          return Effect.succeed(Option.none());
        }
        return Effect.ensuring(Effect.asSome(effect), Effect.sync(() => {
          releaseUnsafe(permits);
        }));
      });
    }
  };
};
/**
 * Creates a `PartitionedSemaphore` inside an `Effect`.
 *
 * **When to use**
 *
 * Use when semaphore construction should stay inside an `Effect` workflow.
 *
 * **Details**
 *
 * The `permits` option sets the shared permit capacity. The resulting
 * semaphore tracks waiters by partition key and distributes released permits
 * across waiting partitions in round-robin order.
 *
 * **Gotchas**
 *
 * Negative permit counts are clamped to `0`. Non-finite permit counts create
 * an unbounded semaphore.
 *
 * @see {@link makeUnsafe} for synchronous construction
 *
 * @category constructors
 * @since 3.19.4
 */
export const make = options => Effect.sync(() => makeUnsafe(options));
/**
 * Gets the current number of available permits.
 *
 * **When to use**
 *
 * Use to inspect a snapshot of how many permits are currently free.
 *
 * **Details**
 *
 * Running the returned effect reads the semaphore's current availability.
 * Taking permits decreases availability, and releasing permits can increase it
 * up to the semaphore capacity.
 *
 * **Gotchas**
 *
 * Reading availability does not reserve permits.
 *
 * @see {@link capacity} for the fixed total permit capacity
 * @see {@link release} for returning permits to the shared pool
 * @see {@link withPermitsIfAvailable} for running only when permits are immediately available
 *
 * @category combinators
 * @since 4.0.0
 */
export const available = self => self.available;
/**
 * Gets the total capacity.
 *
 * **When to use**
 *
 * Use to inspect the fixed number of permits configured for the semaphore.
 *
 * **Details**
 *
 * Capacity is stored when the semaphore is created and does not change as
 * permits are acquired or released.
 *
 * @see {@link available} for the current number of free permits
 *
 * @category getters
 * @since 4.0.0
 */
export const capacity = self => self.capacity;
/**
 * Returns an effect that acquires the requested number of permits for the
 * given partition key.
 *
 * **When to use**
 *
 * Use to manually acquire permits for a partition when acquisition and release
 * must be controlled as separate effects.
 *
 * **Details**
 *
 * If enough permits are available, the effect completes immediately. Otherwise
 * it waits until released permits are assigned to this partition.
 *
 * **Gotchas**
 *
 * Requests for more permits than the semaphore capacity never complete.
 * Requests for zero or a negative number of permits complete without acquiring
 * anything.
 *
 * @see {@link release} for manually returning permits to the shared pool
 * @see {@link withPermits} for automatic acquire and release around an effect
 * @see {@link withPermit} for acquiring exactly one permit around an effect
 *
 * @category combinators
 * @since 4.0.0
 */
export const take = /*#__PURE__*/dual(3, (self, key, permits) => self.take(key, permits));
/**
 * Returns an effect that releases permits back to the shared pool and returns
 * the current available permit count.
 *
 * **When to use**
 *
 * Use to manually return permits acquired with `take` when a lower-level
 * partitioned permit protocol needs explicit release control.
 *
 * **Details**
 *
 * Released permits are first assigned to waiting partitions in round-robin
 * order. Only permits not needed by waiters increase the available count,
 * which is capped at the semaphore capacity.
 *
 * @see {@link take} for manual acquisition
 * @see {@link withPermits} for automatic acquire and release around an effect
 * @see {@link available} for reading the permit count without releasing
 *
 * @category combinators
 * @since 4.0.0
 */
export const release = /*#__PURE__*/dual(2, (self, permits) => self.release(permits));
/**
 * Runs an effect after acquiring permits for a partition, then releases those
 * permits when the effect exits.
 *
 * **When to use**
 *
 * Use to guard weighted partitioned work with automatic permit acquisition and
 * release around an effect.
 *
 * **Details**
 *
 * Permit acquisition may wait according to `take` semantics. Once acquired,
 * the permits are released even if the wrapped effect fails or is interrupted.
 *
 * **Gotchas**
 *
 * Requests for more permits than the semaphore capacity never complete.
 * Requests for zero or a negative number of permits run the effect without
 * acquiring anything.
 *
 * @see {@link withPermit} for the single-permit variant
 * @see {@link withPermitsIfAvailable} for running only when permits are
 * immediately available
 * @see {@link take} for manual acquisition
 * @see {@link release} for manual release
 *
 * @category combinators
 * @since 4.0.0
 */
export const withPermits = (...args) => {
  if (args.length === 3) {
    const [self, key, permits] = args;
    return effect => self.withPermits(key, permits)(effect);
  }
  const [self, key, permits, effect] = args;
  return self.withPermits(key, permits)(effect);
};
/**
 * Runs an effect after acquiring one permit for a partition, then releases the
 * permit when the effect exits.
 *
 * **When to use**
 *
 * Use to guard partitioned work with exactly one permit and automatic release
 * when the effect exits.
 *
 * **Details**
 *
 * This is the single-permit variant of `withPermits`. The permit is released
 * even if the wrapped effect fails or is interrupted.
 *
 * @see {@link withPermits} for acquiring a weighted number of permits
 * @see {@link withPermitsIfAvailable} for running only when permits are
 * immediately available
 * @see {@link take} for manual acquisition
 * @see {@link release} for manual release
 *
 * @category combinators
 * @since 4.0.0
 */
export const withPermit = (...args) => {
  if (args.length === 2) {
    const [self, key] = args;
    return effect => self.withPermit(key)(effect);
  }
  const [self, key, effect] = args;
  return self.withPermit(key)(effect);
};
/**
 * Runs an effect only when the requested permits can be acquired immediately,
 * returning the result in `Some`.
 *
 * **When to use**
 *
 * Use when guarded work should run only if the shared permit pool can provide
 * the requested permits immediately.
 *
 * **Details**
 *
 * If the permits are not available, the effect is not run and the result is
 * `None`. When permits are acquired, they are released after the wrapped
 * effect completes, fails, or is interrupted. Requests for zero or a negative
 * number of permits run the effect and return `Some`.
 *
 * @see {@link withPermits} for the keyed variant that waits until permits are
 * available for a partition
 *
 * @category combinators
 * @since 4.0.0
 */
export const withPermitsIfAvailable = (...args) => {
  if (args.length === 2) {
    const [self, permits] = args;
    return effect => self.withPermitsIfAvailable(permits)(effect);
  }
  const [self, permits, effect] = args;
  return self.withPermitsIfAvailable(permits)(effect);
};
//# sourceMappingURL=PartitionedSemaphore.js.map