import { g as getOrThrowWith, a as right, b as left, i as identity, d as isRight, e as getRight, m as match, f as decode, h as map, j as all, k as context, n as fiberId, o as merge, p as sync, q as size, u as suspend, v as get, w as succeed, x as none, y as has, z as isSome, A as some, B as getOrUndefined, C as unsafeMake, D as set, E as struct, F as flatMap, G as remove, H as empty, I as clockWith, J as offer, K as compareAndSet, L as poll, M as EmptyMutableQueue, N as set$1, O as make$2, P as unbounded, Q as symbol, R as pipe, S as cached, T as combine, U as hash, V as symbol$1, W as hasProperty, X as equals, Y as asVoid, _ as _await, Z as when, $ as succeed$1, a0 as toMillis, a1 as onInterrupt, a2 as zipRight, a3 as interrupt, a4 as done, a5 as exit, a6 as provideContext, a7 as YieldableError, a8 as gen, a9 as tryPromise, aa as try_, t as tapError, ab as logWarning, ac as orElseSucceed, ad as infinity } from './Effect-D9S7OTyY.js';
import { a as getAssetPath } from './index-smGPjoDX.js';

/**
 * This module provides types and utility functions to create and work with branded types,
 * which are TypeScript types with an added type tag to prevent accidental usage of a value in the wrong context.
 *
 * The `refined` and `nominal` functions are both used to create branded types in TypeScript.
 * The main difference between them is that `refined` allows for validation of the data, while `nominal` does not.
 *
 * The `nominal` function is used to create a new branded type that has the same underlying type as the input, but with a different name.
 * This is useful when you want to distinguish between two values of the same type that have different meanings.
 * The `nominal` function does not perform any validation of the input data.
 *
 * On the other hand, the `refined` function is used to create a new branded type that has the same underlying type as the input,
 * but with a different name, and it also allows for validation of the input data.
 * The `refined` function takes a predicate that is used to validate the input data.
 * If the input data fails the validation, a `BrandErrors` is returned, which provides information about the specific validation failure.
 *
 * @since 2.0.0
 */
/**
 * @since 2.0.0
 * @category symbols
 */
const RefinedConstructorsTypeId = /*#__PURE__*/Symbol.for("effect/Brand/Refined");
/**
 * Returns a `BrandErrors` that contains a single `RefinementError`.
 *
 * @since 2.0.0
 * @category constructors
 */
const error = (message, meta) => [{
  message,
  meta
}];
function refined(...args) {
  const either = args.length === 2 ? unbranded => args[0](unbranded) ? right(unbranded) : left(args[1](unbranded)) : unbranded => {
    return match(args[0](unbranded), {
      onNone: () => right(unbranded),
      onSome: left
    });
  };
  return Object.assign(unbranded => getOrThrowWith(either(unbranded), identity), {
    [RefinedConstructorsTypeId]: RefinedConstructorsTypeId,
    option: args => getRight(either(args)),
    either,
    is: args => isRight(either(args))
  });
}

/** @internal */
const complete = (key, exit, entryStats, timeToLiveMillis) => struct({
  _tag: "Complete",
  key,
  exit,
  entryStats,
  timeToLiveMillis
});
/** @internal */
const pending = (key, deferred) => struct({
  _tag: "Pending",
  key,
  deferred
});
/** @internal */
const refreshing = (deferred, complete) => struct({
  _tag: "Refreshing",
  deferred,
  complete
});
/** @internal */
const MapKeyTypeId = /*#__PURE__*/Symbol.for("effect/Cache/MapKey");
class MapKeyImpl {
  current;
  [MapKeyTypeId] = MapKeyTypeId;
  previous = undefined;
  next = undefined;
  constructor(current) {
    this.current = current;
  }
  [symbol]() {
    return pipe(hash(this.current), combine(hash(this.previous)), combine(hash(this.next)), cached(this));
  }
  [symbol$1](that) {
    if (this === that) {
      return true;
    }
    return isMapKey(that) && equals(this.current, that.current) && equals(this.previous, that.previous) && equals(this.next, that.next);
  }
}
/** @internal */
const makeMapKey = current => new MapKeyImpl(current);
/** @internal */
const isMapKey = u => hasProperty(u, MapKeyTypeId);
class KeySetImpl {
  head = undefined;
  tail = undefined;
  add(key) {
    if (key !== this.tail) {
      if (this.tail === undefined) {
        this.head = key;
        this.tail = key;
      } else {
        const previous = key.previous;
        const next = key.next;
        if (next !== undefined) {
          key.next = undefined;
          if (previous !== undefined) {
            previous.next = next;
            next.previous = previous;
          } else {
            this.head = next;
            this.head.previous = undefined;
          }
        }
        this.tail.next = key;
        key.previous = this.tail;
        this.tail = key;
      }
    }
  }
  remove() {
    const key = this.head;
    if (key !== undefined) {
      const next = key.next;
      if (next !== undefined) {
        key.next = undefined;
        this.head = next;
        this.head.previous = undefined;
      } else {
        this.head = undefined;
        this.tail = undefined;
      }
    }
    return key;
  }
}
/** @internal */
const makeKeySet = () => new KeySetImpl();
/**
 * Constructs a new `CacheState` from the specified values.
 *
 * @internal
 */
const makeCacheState = (map, keys, accesses, updating, hits, misses) => ({
  map,
  keys,
  accesses,
  updating,
  hits,
  misses
});
/**
 * Constructs an initial cache state.
 *
 * @internal
 */
const initialCacheState = () => makeCacheState(empty(), makeKeySet(), unbounded(), make$2(false), 0, 0);
/** @internal */
const CacheSymbolKey = "effect/Cache";
/** @internal */
const CacheTypeId = /*#__PURE__*/Symbol.for(CacheSymbolKey);
const cacheVariance = {
  /* c8 ignore next */
  _Key: _ => _,
  /* c8 ignore next */
  _Error: _ => _,
  /* c8 ignore next */
  _Value: _ => _
};
/** @internal */
const ConsumerCacheSymbolKey = "effect/ConsumerCache";
/** @internal */
const ConsumerCacheTypeId = /*#__PURE__*/Symbol.for(ConsumerCacheSymbolKey);
const consumerCacheVariance = {
  /* c8 ignore next */
  _Key: _ => _,
  /* c8 ignore next */
  _Error: _ => _,
  /* c8 ignore next */
  _Value: _ => _
};
/** @internal */
const makeCacheStats = options => options;
/** @internal */
const makeEntryStats = loadedMillis => ({
  loadedMillis
});
class CacheImpl {
  capacity;
  context;
  fiberId;
  lookup;
  timeToLive;
  [CacheTypeId] = cacheVariance;
  [ConsumerCacheTypeId] = consumerCacheVariance;
  cacheState;
  constructor(capacity, context, fiberId, lookup, timeToLive) {
    this.capacity = capacity;
    this.context = context;
    this.fiberId = fiberId;
    this.lookup = lookup;
    this.timeToLive = timeToLive;
    this.cacheState = initialCacheState();
  }
  get(key) {
    return map(this.getEither(key), merge);
  }
  get cacheStats() {
    return sync(() => makeCacheStats({
      hits: this.cacheState.hits,
      misses: this.cacheState.misses,
      size: size(this.cacheState.map)
    }));
  }
  getOption(key) {
    return suspend(() => match(get(this.cacheState.map, key), {
      onNone: () => {
        const mapKey = makeMapKey(key);
        this.trackAccess(mapKey);
        this.trackMiss();
        return succeed(none());
      },
      onSome: value => this.resolveMapValue(value)
    }));
  }
  getOptionComplete(key) {
    return suspend(() => match(get(this.cacheState.map, key), {
      onNone: () => {
        const mapKey = makeMapKey(key);
        this.trackAccess(mapKey);
        this.trackMiss();
        return succeed(none());
      },
      onSome: value => this.resolveMapValue(value, true)
    }));
  }
  contains(key) {
    return sync(() => has(this.cacheState.map, key));
  }
  entryStats(key) {
    return sync(() => {
      const option = get(this.cacheState.map, key);
      if (isSome(option)) {
        switch (option.value._tag) {
          case "Complete":
            {
              const loaded = option.value.entryStats.loadedMillis;
              return some(makeEntryStats(loaded));
            }
          case "Pending":
            {
              return none();
            }
          case "Refreshing":
            {
              const loaded = option.value.complete.entryStats.loadedMillis;
              return some(makeEntryStats(loaded));
            }
        }
      }
      return none();
    });
  }
  getEither(key) {
    return suspend(() => {
      const k = key;
      let mapKey = undefined;
      let deferred = undefined;
      let value = getOrUndefined(get(this.cacheState.map, k));
      if (value === undefined) {
        deferred = unsafeMake(this.fiberId);
        mapKey = makeMapKey(k);
        if (has(this.cacheState.map, k)) {
          value = getOrUndefined(get(this.cacheState.map, k));
        } else {
          set(this.cacheState.map, k, pending(mapKey, deferred));
        }
      }
      if (value === undefined) {
        this.trackAccess(mapKey);
        this.trackMiss();
        return map(this.lookupValueOf(key, deferred), right);
      } else {
        return flatMap(this.resolveMapValue(value), match({
          onNone: () => this.getEither(key),
          onSome: value => succeed(left(value))
        }));
      }
    });
  }
  invalidate(key) {
    return sync(() => {
      remove(this.cacheState.map, key);
    });
  }
  invalidateWhen(key, when) {
    return sync(() => {
      const value = get(this.cacheState.map, key);
      if (isSome(value) && value.value._tag === "Complete") {
        if (value.value.exit._tag === "Success") {
          if (when(value.value.exit.value)) {
            remove(this.cacheState.map, key);
          }
        }
      }
    });
  }
  get invalidateAll() {
    return sync(() => {
      this.cacheState.map = empty();
    });
  }
  refresh(key) {
    return clockWith(clock => suspend(() => {
      const k = key;
      const deferred = unsafeMake(this.fiberId);
      let value = getOrUndefined(get(this.cacheState.map, k));
      if (value === undefined) {
        if (has(this.cacheState.map, k)) {
          value = getOrUndefined(get(this.cacheState.map, k));
        } else {
          set(this.cacheState.map, k, pending(makeMapKey(k), deferred));
        }
      }
      if (value === undefined) {
        return asVoid(this.lookupValueOf(key, deferred));
      } else {
        switch (value._tag) {
          case "Complete":
            {
              if (this.hasExpired(clock, value.timeToLiveMillis)) {
                const found = getOrUndefined(get(this.cacheState.map, k));
                if (equals(found, value)) {
                  remove(this.cacheState.map, k);
                }
                return asVoid(this.get(key));
              }
              // Only trigger the lookup if we're still the current value, `completedResult`
              return pipe(this.lookupValueOf(key, deferred), when(() => {
                const current = getOrUndefined(get(this.cacheState.map, k));
                if (equals(current, value)) {
                  const mapValue = refreshing(deferred, value);
                  set(this.cacheState.map, k, mapValue);
                  return true;
                }
                return false;
              }), asVoid);
            }
          case "Pending":
            {
              return _await(value.deferred);
            }
          case "Refreshing":
            {
              return _await(value.deferred);
            }
        }
      }
    }));
  }
  set(key, value) {
    return clockWith(clock => sync(() => {
      const now = clock.unsafeCurrentTimeMillis();
      const k = key;
      const lookupResult = succeed$1(value);
      const mapValue = complete(makeMapKey(k), lookupResult, makeEntryStats(now), now + toMillis(decode(this.timeToLive(lookupResult))));
      set(this.cacheState.map, k, mapValue);
    }));
  }
  get size() {
    return sync(() => {
      return size(this.cacheState.map);
    });
  }
  get values() {
    return sync(() => {
      const values = [];
      for (const entry of this.cacheState.map) {
        if (entry[1]._tag === "Complete" && entry[1].exit._tag === "Success") {
          values.push(entry[1].exit.value);
        }
      }
      return values;
    });
  }
  get entries() {
    return sync(() => {
      const values = [];
      for (const entry of this.cacheState.map) {
        if (entry[1]._tag === "Complete" && entry[1].exit._tag === "Success") {
          values.push([entry[0], entry[1].exit.value]);
        }
      }
      return values;
    });
  }
  get keys() {
    return sync(() => {
      const keys = [];
      for (const entry of this.cacheState.map) {
        if (entry[1]._tag === "Complete" && entry[1].exit._tag === "Success") {
          keys.push(entry[0]);
        }
      }
      return keys;
    });
  }
  resolveMapValue(value, ignorePending = false) {
    return clockWith(clock => {
      switch (value._tag) {
        case "Complete":
          {
            this.trackAccess(value.key);
            if (this.hasExpired(clock, value.timeToLiveMillis)) {
              remove(this.cacheState.map, value.key.current);
              return succeed(none());
            }
            this.trackHit();
            return map(value.exit, some);
          }
        case "Pending":
          {
            this.trackAccess(value.key);
            this.trackHit();
            if (ignorePending) {
              return succeed(none());
            }
            return map(_await(value.deferred), some);
          }
        case "Refreshing":
          {
            this.trackAccess(value.complete.key);
            this.trackHit();
            if (this.hasExpired(clock, value.complete.timeToLiveMillis)) {
              if (ignorePending) {
                return succeed(none());
              }
              return map(_await(value.deferred), some);
            }
            return map(value.complete.exit, some);
          }
      }
    });
  }
  trackHit() {
    this.cacheState.hits = this.cacheState.hits + 1;
  }
  trackMiss() {
    this.cacheState.misses = this.cacheState.misses + 1;
  }
  trackAccess(key) {
    offer(this.cacheState.accesses, key);
    if (compareAndSet(this.cacheState.updating, false, true)) {
      let loop = true;
      while (loop) {
        const key = poll(this.cacheState.accesses, EmptyMutableQueue);
        if (key === EmptyMutableQueue) {
          loop = false;
        } else {
          this.cacheState.keys.add(key);
        }
      }
      let size$1 = size(this.cacheState.map);
      loop = size$1 > this.capacity;
      while (loop) {
        const key = this.cacheState.keys.remove();
        if (key !== undefined) {
          if (has(this.cacheState.map, key.current)) {
            remove(this.cacheState.map, key.current);
            size$1 = size$1 - 1;
            loop = size$1 > this.capacity;
          }
        } else {
          loop = false;
        }
      }
      set$1(this.cacheState.updating, false);
    }
  }
  hasExpired(clock, timeToLiveMillis) {
    return clock.unsafeCurrentTimeMillis() > timeToLiveMillis;
  }
  lookupValueOf(input, deferred) {
    return clockWith(clock => suspend(() => {
      const key = input;
      return pipe(this.lookup(input), provideContext(this.context), exit, flatMap(exit => {
        const now = clock.unsafeCurrentTimeMillis();
        const stats = makeEntryStats(now);
        const value = complete(makeMapKey(key), exit, stats, now + toMillis(decode(this.timeToLive(exit))));
        set(this.cacheState.map, key, value);
        return zipRight(done(deferred, exit), exit);
      }), onInterrupt(() => zipRight(interrupt(deferred), sync(() => {
        remove(this.cacheState.map, key);
      }))));
    }));
  }
}
/** @internal */
const make$1 = options => {
  const timeToLive = decode(options.timeToLive);
  return makeWith({
    capacity: options.capacity,
    lookup: options.lookup,
    timeToLive: () => timeToLive
  });
};
/** @internal */
const makeWith = options => map(all([context(), fiberId]), ([context, fiberId]) => new CacheImpl(options.capacity, context, fiberId, options.lookup, exit => decode(options.timeToLive(exit))));

/**
 * Constructs a new cache with the specified capacity, time to live, and
 * lookup function.
 *
 * @since 2.0.0
 * @category constructors
 */
const make = make$1;

/**
 * Provides a constructor for a Case Class.
 *
 * @since 2.0.0
 * @category constructors
 */
const Error$1 = /*#__PURE__*/function () {
  const plainArgsSymbol = /*#__PURE__*/Symbol.for("effect/Data/Error/plainArgs");
  const O = {
    BaseEffectError: class extends YieldableError {
      constructor(args) {
        super(args?.message, args?.cause ? {
          cause: args.cause
        } : undefined);
        if (args) {
          Object.assign(this, args);
          // @effect-diagnostics-next-line floatingEffect:off
          Object.defineProperty(this, plainArgsSymbol, {
            value: args,
            enumerable: false
          });
        }
      }
      toJSON() {
        return {
          ...this[plainArgsSymbol],
          ...this
        };
      }
    }
  };
  return O.BaseEffectError;
}();
/**
 * @since 2.0.0
 * @category constructors
 */
const TaggedError = tag => {
  const O = {
    BaseEffectError: class extends Error$1 {
      _tag = tag;
    }
  };
  O.BaseEffectError.prototype.name = tag;
  return O.BaseEffectError;
};

class FetchError extends TaggedError('FetchError') {
}
class NotFoundError extends TaggedError('NotFoundError') {
}
const httpClient = (input, init) => gen(function* () {
    const response = yield* tryPromise({
        try: () => fetch(input, init),
        catch: error => new FetchError({ cause: error }),
    });
    if (response.status === 404) {
        return yield* new NotFoundError({ url: response.url });
    }
    return response;
});
class ResponseTextError extends TaggedError('ResponseTextError') {
    constructor(error) {
        super(new Error(`Failed to read response text`, { cause: error }));
    }
}
const responseAsText = (res) => tryPromise({
    try: () => res.text(),
    catch: error => new ResponseTextError(error),
});

class URLCreationError extends TaggedError('URLCreationError') {
}
const createAssetUrlWithBasePath = (file, basePath) => try_(() => getAssetPath(['/assets', file].filter(Boolean).join('/').replace('//', '/'))).pipe(tapError(() => logWarning(`Failed to create URL for icon "${file}".
           Please provide an absolute URL in your app's 'setAssetPath(...)' configuration!
           Falling back to /assets/`)), orElseSucceed(() => window.location.origin + '/assets/' + file));

const SVGString = refined(isValidSVG, () => error('SVG data is malformed'));
class BrandValidationError extends TaggedError('BrandValidationError') {
    constructor(error, type) {
        super(new Error(`Brand validation failed for type ${type}.`, { cause: error }));
    }
}
const createValidSVGString = (svg) => try_({
    try: () => SVGString(svg),
    catch: error => new BrandValidationError(error, 'SVGString'),
});
function isValidSVG(svg) {
    if (typeof svg !== 'string') {
        return false;
    }
    try {
        const parser = new DOMParser();
        const doc = parser.parseFromString(svg, 'image/svg+xml');
        return doc.documentElement.nodeName === 'svg';
    }
    catch {
        return false;
    }
}

const emptyDefaultSvg = () => SVGString(`<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"></svg>`);
const createIconCache = () => make({
    capacity: 200,
    timeToLive: infinity,
    lookup: (key) => gen(function* () {
        const name = key;
        const assetPath = yield* createAssetUrlWithBasePath(`./icons/${name}.svg`);
        const res = yield* httpClient(assetPath);
        const responseString = yield* responseAsText(res);
        return yield* createValidSVGString(responseString);
    }),
});
let cacheInstance = null;
function fetchIconSvg(name) {
    const key = name;
    return gen(function* () {
        if (!cacheInstance) {
            cacheInstance = yield* createIconCache();
        }
        return yield* cacheInstance.get(key);
    });
}
function clearIconCache() {
    return gen(function* () {
        if (cacheInstance) {
            yield* cacheInstance.invalidateAll;
        }
        else {
            logWarning('Attempted to clear icon cache before it was initialized');
        }
    });
}

export { clearIconCache as c, emptyDefaultSvg as e, fetchIconSvg as f };
