text: /**
   * Copyright (c) Facebook, Inc. and its affiliates.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * Returns an atom, the basic unit of state in Recoil. An atom is a reference to
   * value that can be read, written, and subscribed to. It has a `key` that is
   * stable across time so that it can be persisted.
   *
   * There are two required options for creating an atom:
   *
   *    key. This is a string that uniquely identifies the atom. It should be
   *         stable across time so that persisted states remain valid.
   *
   *    default
   *          If `default` is provided, the atom is initialized to that value.
   *          Or, it may be set to another RecoilValue to use as a fallback.
   *          In that case, the value of the atom will be equal to that of the
   *          fallback, and will remain so until the first time the atom is written
   *          to, at which point it will stop tracking the fallback RecoilValue.
   *
   * The `persistence` option specifies that the atom should be saved to storage.
   * It is an object with two properties: `type` specifies where the atom should
   * be persisted; its only allowed value is "url"; `backButton` specifies whether
   * changes to the atom should result in pushes to the browser history stack; if
   * true, changing the atom and then hitting the Back button will cause the atom's
   * previous value to be restored. Applications are responsible for implementing
   * persistence by using the `useTransactionObservation` hook.
   *
   * Scoped atoms (DEPRECATED):
   * ===================================================================================
   *
   * The scopeRules_APPEND_ONLY_READ_THE_DOCS option causes the atom be be "scoped".
   * A scoped atom's value depends on other parts of the application state.
   * A separate value of the atom is stored for every value of the state that it
   * depends on. The dependencies may be changed without breaking existing URLs --
   * it uses whatever rule was current when the URL was written. Values written
   * under the newer rules are overlaid atop the previously-written values just for
   * those states in which the write occurred, with reads falling back to the older
   * values in other states, and eventually to the default or fallback.
   *
   * The scopedRules_APPEND_ONLY_READ_THE_DOCS parameter is a list of rules;
   * it should start with a single entry. This list must only be appended to:
   * existing entries must never be deleted or modified. Each rule is an atom
   * or selector whose value is some arbitrary key. A different value of the
   * scoped atom will be stored for each key. To change the scope rule, simply add
   * a new function to the list. Each rule is either an array of atoms of primitives,
   * or an atom of an array of primitives.
   *
   * Ordinary atoms may be upgraded to scoped atoms. To un-scope an atom, add a new
   * scope rule consisting of a constant.
   *
   * @emails oncall+recoil
   * @flow strict-local
   * @format
   */
  'use strict';
  
  import type {Loadable, LoadingLoadableType} from '../adt/Recoil_Loadable';
  import type {RecoilValueInfo} from '../core/Recoil_FunctionalCore';
  import type {StoreID} from '../core/Recoil_Keys';
  import type {
    PersistenceInfo,
    ReadWriteNodeOptions,
    Trigger,
  } from '../core/Recoil_Node';
  import type {RecoilState, RecoilValue} from '../core/Recoil_RecoilValue';
  import type {RetainedBy} from '../core/Recoil_RetainedBy';
  import type {AtomWrites, NodeKey, Store, TreeState} from '../core/Recoil_State';
  // @fb-only: import type {ScopeRules} from 'Recoil_ScopedAtom';
  
  // @fb-only: const {scopedAtom} = require('Recoil_ScopedAtom');
  
  const {
    isLoadable,
    loadableWithError,
    loadableWithPromise,
    loadableWithValue,
  } = require('../adt/Recoil_Loadable');
  const {WrappedValue} = require('../adt/Recoil_Wrapper');
  const {peekNodeInfo} = require('../core/Recoil_FunctionalCore');
  const {
    DEFAULT_VALUE,
    DefaultValue,
    getConfigDeletionHandler,
    registerNode,
    setConfigDeletionHandler,
  } = require('../core/Recoil_Node');
  const {isRecoilValue} = require('../core/Recoil_RecoilValue');
  const {
    getRecoilValueAsLoadable,
    markRecoilValueModified,
    setRecoilValue,
    setRecoilValueLoadable,
  } = require('../core/Recoil_RecoilValueInterface');
  const {retainedByOptionWithDefault} = require('../core/Recoil_Retention');
  const selector = require('./Recoil_selector');
  const deepFreezeValue = require('recoil-shared/util/Recoil_deepFreezeValue');
  const err = require('recoil-shared/util/Recoil_err');
  const expectationViolation = require('recoil-shared/util/Recoil_expectationViolation');
  const isPromise = require('recoil-shared/util/Recoil_isPromise');
  const nullthrows = require('recoil-shared/util/Recoil_nullthrows');
  const recoverableViolation = require('recoil-shared/util/Recoil_recoverableViolation');
  
  export type PersistenceSettings<Stored> = $ReadOnly<{
    ...PersistenceInfo,
    validator: (mixed, DefaultValue) => Stored | DefaultValue,
  }>;
  
  // TODO Support Loadable<T> and WrappedValue<T>
  type NewValue<T> = T | DefaultValue | Promise<T | DefaultValue>;
  type NewValueOrUpdater<T> =
    | T
    | DefaultValue
    | Promise<T | DefaultValue>
    | ((T | DefaultValue) => T | DefaultValue);
  
  // Effect is called the first time a node is used with a <RecoilRoot>
  export type AtomEffect<T> = ({
    node: RecoilState<T>,
    storeID: StoreID,
    trigger: Trigger,
  
    // Call synchronously to initialize value or async to change it later
    setSelf: (
      | T
      | DefaultValue
      | Promise<T | DefaultValue>
      | ((T | DefaultValue) => T | DefaultValue),
    ) => void,
    resetSelf: () => void,
  
    // Subscribe callbacks to events.
    // Atom effect observers are called before global transaction observers
    onSet: (
      (newValue: T, oldValue: T | DefaultValue, isReset: boolean) => void,
    ) => void,
  
    // Accessors to read other atoms/selectors
    getPromise: <S>(RecoilValue<S>) => Promise<S>,
    getLoadable: <S>(RecoilValue<S>) => Loadable<S>,
    getInfo_UNSTABLE: <S>(RecoilValue<S>) => RecoilValueInfo<S>,
  }) => void | (() => void);
  
  export type AtomOptionsWithoutDefault<T> = $ReadOnly<{
    key: NodeKey,
    effects?: $ReadOnlyArray<AtomEffect<T>>,
    effects_UNSTABLE?: $ReadOnlyArray<AtomEffect<T>>,
    persistence_UNSTABLE?: PersistenceSettings<T>,
    // @fb-only: scopeRules_APPEND_ONLY_READ_THE_DOCS?: ScopeRules,
    dangerouslyAllowMutability?: boolean,
    retainedBy_UNSTABLE?: RetainedBy,
  }>;
  
  type AtomOptionsWithDefault<T> = $ReadOnly<{
    ...AtomOptionsWithoutDefault<T>,
    default: RecoilValue<T> | Promise<T> | Loadable<T> | WrappedValue<T> | T,
  }>;
  
  export type AtomOptions<T> =
    | AtomOptionsWithDefault<T>
    | AtomOptionsWithoutDefault<T>;
  
  type BaseAtomOptions<T> = $ReadOnly<{
    ...AtomOptions<T>,
    default: Promise<T> | Loadable<T> | WrappedValue<T> | T,
  }>;
  
  function baseAtom<T>(options: BaseAtomOptions<T>): RecoilState<T> {
    const {key, persistence_UNSTABLE: persistence} = options;
  
    const retainedBy = retainedByOptionWithDefault(options.retainedBy_UNSTABLE);
    let liveStoresCount = 0;
  
    function unwrapPromise(promise: Promise<T>): Loadable<T> {
      return loadableWithPromise(
        promise
          .then(value => {
            defaultLoadable = loadableWithValue(value);
            return value;
          })
          .catch(error => {
            defaultLoadable = loadableWithError(error);
            throw error;
          }),
      );
    }
  
    let defaultLoadable: Loadable<T> = isPromise(options.default)
      ? unwrapPromise(options.default)
      : isLoadable(options.default)
      ? options.default.state === 'loading'
        ? unwrapPromise((options.default: LoadingLoadableType<T>).contents)
        : options.default
      : loadableWithValue(
          options.default instanceof WrappedValue
            ? options.default.value
            : options.default,
        );
    maybeFreezeValueOrPromise(defaultLoadable.contents);
  
    let cachedAnswerForUnvalidatedValue: void | Loadable<T> = undefined;
  
    // Cleanup handlers for this atom
    // Rely on stable reference equality of the store to use it as a key per <RecoilRoot>
    const cleanupEffectsByStore: Map<Store, Array<() => void>> = new Map();
  
    function maybeFreezeValueOrPromise(valueOrPromise) {
      if (__DEV__) {
        if (options.dangerouslyAllowMutability !== true) {
          if (isPromise(valueOrPromise)) {
            return valueOrPromise.then(value => {
              deepFreezeValue(value);
              return value;
            });
          } else {
            deepFreezeValue(valueOrPromise);
            return valueOrPromise;
          }
        }
      }
      return valueOrPromise;
    }
  
    function wrapPendingPromise(
      store: Store,
      promise: Promise<T | DefaultValue>,
    ): Promise<T | DefaultValue> {
      const wrappedPromise = promise
        .then(value => {
          const state = store.getState().nextTree ?? store.getState().currentTree;
  
          if (state.atomValues.get(key)?.contents === wrappedPromise) {
            setRecoilValue(store, node, value);
          }
  
          return value;
        })
        .catch(error => {
          const state = store.getState().nextTree ?? store.getState().currentTree;
          if (state.atomValues.get(key)?.contents === wrappedPromise) {
            setRecoilValueLoadable(store, node, loadableWithError(error));
          }
          throw error;
        });
  
      return wrappedPromise;
    }
  
    function initAtom(
      store: Store,
      initState: TreeState,
      trigger: Trigger,
    ): () => void {
      liveStoresCount++;
      const cleanupAtom = () => {
        liveStoresCount--;
        cleanupEffectsByStore.get(store)?.forEach(cleanup => cleanup());
        cleanupEffectsByStore.delete(store);
      };
  
      store.getState().knownAtoms.add(key);
  
      // Setup async defaults to notify subscribers when they resolve
      if (defaultLoadable.state === 'loading') {
        const notifyDefaultSubscribers = () => {
          const state = store.getState().nextTree ?? store.getState().currentTree;
          if (!state.atomValues.has(key)) {
            markRecoilValueModified(store, node);
          }
        };
        defaultLoadable.contents.finally(notifyDefaultSubscribers);
      }
  
      ///////////////////
      // Run Atom Effects
      ///////////////////
  
      const effects = options.effects ?? options.effects_UNSTABLE;
      if (effects != null) {
        // This state is scoped by Store, since this is in the initAtom() closure
        let duringInit = true;
        let initValue: NewValue<T> = DEFAULT_VALUE;
        let isInitError: boolean = false;
        let pendingSetSelf: ?{
          effect: AtomEffect<T>,
          value: T | DefaultValue,
        } = null;
  
        function getLoadable<S>(recoilValue: RecoilValue<S>): Loadable<S> {
          // Normally we can just get the current value of another atom.
          // But for our own value we need to check if there is a pending
          // initialized value or get the fallback default value.
          if (duringInit && recoilValue.key === key) {
            // Cast T to S
            const retValue: NewValue<S> = (initValue: any); // flowlint-line unclear-type:off
            return retValue instanceof DefaultValue
              ? (peekAtom(store, initState): any) // flowlint-line unclear-type:off
              : isPromise(retValue)
              ? loadableWithPromise(
                  retValue.then((v: S | DefaultValue): S | Promise<S> =>
                    v instanceof DefaultValue
                      ? // Cast T to S
                        (defaultLoadable: any).toPromise() // flowlint-line unclear-type:off
                      : v,
                  ),
                )
              : loadableWithValue(retValue);
          }
          return getRecoilValueAsLoadable(store, recoilValue);
        }
  
        function getPromise<S>(recoilValue: RecoilValue<S>): Promise<S> {
          return getLoadable(recoilValue).toPromise();
        }
  
        function getInfo_UNSTABLE<S>(
          recoilValue: RecoilValue<S>,
        ): RecoilValueInfo<S> {
          const info = peekNodeInfo(
            store,
            store.getState().nextTree ?? store.getState().currentTree,
            recoilValue.key,
          );
          return duringInit &&
            recoilValue.key === key &&
            !(initValue instanceof DefaultValue)
            ? {...info, isSet: true, loadable: getLoadable(recoilValue)}
            : info;
        }
  
        const setSelf =
          (effect: AtomEffect<T>) => (valueOrUpdater: NewValueOrUpdater<T>) => {
            if (duringInit) {
              const currentLoadable = getLoadable(node);
              const currentValue: T | DefaultValue =
                currentLoadable.state === 'hasValue'
                  ? currentLoadable.contents
                  : DEFAULT_VALUE;
              initValue =
                typeof valueOrUpdater === 'function'
                  ? // cast to any because we can't restrict T from being a function without losing support for opaque types
                    (valueOrUpdater: any)(currentValue) // flowlint-line unclear-type:off
                  : valueOrUpdater;
              if (isPromise(initValue)) {
                initValue = initValue.then(value => {
                  // Avoid calling onSet() when setSelf() initializes with a Promise
                  pendingSetSelf = {effect, value};
                  return value;
                });
              }
            } else {
              if (isPromise(valueOrUpdater)) {
                throw err('Setting atoms to async values is not implemented.');
              }
  
              if (typeof valueOrUpdater !== 'function') {
                pendingSetSelf = {effect, value: valueOrUpdater};
              }
  
              setRecoilValue(
                store,
                node,
                typeof valueOrUpdater === 'function'
                  ? currentValue => {
                      const newValue =
                        // cast to any because we can't restrict T from being a function without losing support for opaque types
                        (valueOrUpdater: any)(currentValue); // flowlint-line unclear-type:off
                      pendingSetSelf = {effect, value: newValue};
                      return newValue;
                    }
                  : valueOrUpdater,
              );
            }
          };
        const resetSelf = effect => () => setSelf(effect)(DEFAULT_VALUE);
  
        const onSet =
          effect => (handler: (T, T | DefaultValue, boolean) => void) => {
            const {release} = store.subscribeToTransactions(currentStore => {
              // eslint-disable-next-line prefer-const
              let {currentTree, previousTree} = currentStore.getState();
              if (!previousTree) {
                recoverableViolation(
                  'Transaction subscribers notified without a next tree being present -- this is a bug in Recoil',
                  'recoil',
                );
                previousTree = currentTree; // attempt to trundle on
              }
              const newLoadable =
                currentTree.atomValues.get(key) ?? defaultLoadable;
              if (newLoadable.state === 'hasValue') {
                const newValue: T = newLoadable.contents;
                const oldLoadable =
                  previousTree.atomValues.get(key) ?? defaultLoadable;
                const oldValue: T | DefaultValue =
                  oldLoadable.state === 'hasValue'
                    ? oldLoadable.contents
                    : DEFAULT_VALUE; // TODO This isn't actually valid, use as a placeholder for now.
  
                // Ignore atom value changes that were set via setSelf() in the same effect.
                // We will still properly call the handler if there was a subsequent
                // set from something other than an atom effect which was batched
                // with the `setSelf()` call.  However, we may incorrectly ignore
                // the handler if the subsequent batched call happens to set the
                // atom to the exact same value as the `setSelf()`.   But, in that
                // case, it was kind of a noop, so the semantics are debatable..
                if (
                  pendingSetSelf?.effect !== effect ||
                  pendingSetSelf?.value !== newValue
                ) {
                  handler(newValue, oldValue, !currentTree.atomValues.has(key));
                } else if (pendingSetSelf?.effect === effect) {
                  pendingSetSelf = null;
                }
              }
            }, key);
            cleanupEffectsByStore.set(store, [
              ...(cleanupEffectsByStore.get(store) ?? []),
              release,
            ]);
          };
  
        for (const effect of effects) {
          try {
            const cleanup = effect({
              node,
              storeID: store.storeID,
              trigger,
              setSelf: setSelf(effect),
              resetSelf: resetSelf(effect),
              onSet: onSet(effect),
              getPromise,
              getLoadable,
              getInfo_UNSTABLE,
            });
            if (cleanup != null) {
              cleanupEffectsByStore.set(store, [
                ...(cleanupEffectsByStore.get(store) ?? []),
                cleanup,
              ]);
            }
          } catch (error) {
            initValue = error;
            isInitError = true;
          }
        }
  
        duringInit = false;
  
        // Mutate initial state in place since we know there are no other subscribers
        // since we are the ones initializing on first use.
        if (!(initValue instanceof DefaultValue)) {
          const frozenInitValue = maybeFreezeValueOrPromise(initValue);
          const initLoadable = isInitError
            ? loadableWithError(initValue)
            : isPromise(frozenInitValue)
            ? loadableWithPromise(wrapPendingPromise(store, frozenInitValue))
            : loadableWithValue(frozenInitValue);
          initState.atomValues.set(key, initLoadable);
  
          // If there is a pending transaction, then also mutate the next state tree.
          // This could happen if the atom was first initialized in an action that
          // also updated some other atom's state.
          store.getState().nextTree?.atomValues.set(key, initLoadable);
        }
      }
  
      return cleanupAtom;
    }
  
    function peekAtom(_store, state: TreeState): Loadable<T> {
      return (
        state.atomValues.get(key) ??
        cachedAnswerForUnvalidatedValue ??
        defaultLoadable
      );
    }
  
    function getAtom(_store: Store, state: TreeState): Loadable<T> {
      if (state.atomValues.has(key)) {
        // Atom value is stored in state:
        return nullthrows(state.atomValues.get(key));
      } else if (state.nonvalidatedAtoms.has(key)) {
        // Atom value is stored but needs validation before use.
        // We might have already validated it and have a cached validated value:
        if (cachedAnswerForUnvalidatedValue != null) {
          return cachedAnswerForUnvalidatedValue;
        }
  
        if (persistence == null) {
          expectationViolation(
            `Tried to restore a persisted value for atom ${key} but it has no persistence settings.`,
          );
          return defaultLoadable;
        }
        const nonvalidatedValue = state.nonvalidatedAtoms.get(key);
        const validatorResult: T | DefaultValue = persistence.validator(
          nonvalidatedValue,
          DEFAULT_VALUE,
        );
  
        const validatedValueLoadable =
          validatorResult instanceof DefaultValue
            ? defaultLoadable
            : loadableWithValue(validatorResult);
  
        cachedAnswerForUnvalidatedValue = validatedValueLoadable;
  
        return cachedAnswerForUnvalidatedValue;
      } else {
        return defaultLoadable;
      }
    }
  
    function invalidateAtom() {
      cachedAnswerForUnvalidatedValue = undefined;
    }
  
    function setAtom(
      _store: Store,
      state: TreeState,
      newValue: T | DefaultValue,
    ): AtomWrites {
      // Bail out if we're being set to the existing value, or if we're being
      // reset but have no stored value (validated or unvalidated) to reset from:
      if (state.atomValues.has(key)) {
        const existing = nullthrows(state.atomValues.get(key));
        if (existing.state === 'hasValue' && newValue === existing.contents) {
          return new Map();
        }
      } else if (
        !state.nonvalidatedAtoms.has(key) &&
        newValue instanceof DefaultValue
      ) {
        return new Map();
      }
  
      maybeFreezeValueOrPromise(newValue);
  
      cachedAnswerForUnvalidatedValue = undefined; // can be released now if it was previously in use
  
      return new Map().set(key, loadableWithValue(newValue));
    }
  
    function shouldDeleteConfigOnReleaseAtom() {
      return getConfigDeletionHandler(key) !== undefined && liveStoresCount <= 0;
    }
  
    const node = registerNode(
      ({
        key,
        nodeType: 'atom',
        peek: peekAtom,
        get: getAtom,
        set: setAtom,
        init: initAtom,
        invalidate: invalidateAtom,
        shouldDeleteConfigOnRelease: shouldDeleteConfigOnReleaseAtom,
        dangerouslyAllowMutability: options.dangerouslyAllowMutability,
        persistence_UNSTABLE: options.persistence_UNSTABLE
          ? {
              type: options.persistence_UNSTABLE.type,
              backButton: options.persistence_UNSTABLE.backButton,
            }
          : undefined,
        shouldRestoreFromSnapshots: true,
        retainedBy,
      }: ReadWriteNodeOptions<T>),
    );
    return node;
  }
  
  // prettier-ignore
  function atom<T>(options: AtomOptions<T>): RecoilState<T> {
    if (__DEV__) {
      if (typeof options.key !== 'string') {
        throw err(
          'A key option with a unique string value must be provided when creating an atom.',
        );
      }
    }
  
    const {
      // @fb-only: scopeRules_APPEND_ONLY_READ_THE_DOCS,
      ...restOptions
    } = options;
    const optionsDefault: RecoilValue<T> | Promise<T> | Loadable<T> | WrappedValue<T> | T =
      'default' in options
        ? // $FlowIssue[prop-missing] No way to refine in Flow that property is not defined
          // $FlowIssue[incompatible-type] No way to refine in Flow that property is not defined
          options.default
        : new Promise(() => {});
  
    if (isRecoilValue(optionsDefault)
      // Continue to use atomWithFallback for promise defaults for scoped atoms
      // for now, since scoped atoms don't support async defaults
     // @fb-only: || (isPromise(optionsDefault) && scopeRules_APPEND_ONLY_READ_THE_DOCS)
     // @fb-only: || (isLoadable(optionsDefault) && scopeRules_APPEND_ONLY_READ_THE_DOCS)
    ) {
      return atomWithFallback<T>({
        ...restOptions,
        default: optionsDefault,
        // @fb-only: scopeRules_APPEND_ONLY_READ_THE_DOCS,
      });
    // @fb-only: } else if (scopeRules_APPEND_ONLY_READ_THE_DOCS
      // @fb-only: && !isPromise(optionsDefault)
      // @fb-only: && !isLoadable(optionsDefault)
    // @fb-only: ) {
      // @fb-only: return scopedAtom<T>({
        // @fb-only: ...restOptions,
        // @fb-only: default: optionsDefault instanceof WrappedValue
          // @fb-only: ? optionsDefault.value
          // @fb-only: : optionsDefault,
        // @fb-only: scopeRules_APPEND_ONLY_READ_THE_DOCS,
      // @fb-only: });
    } else {
      return baseAtom<T>({...restOptions, default: optionsDefault});
    }
  }
  
  type AtomWithFallbackOptions<T> = $ReadOnly<{
    ...AtomOptions<T>,
    default: RecoilValue<T> | Promise<T> | Loadable<T>,
  }>;
  
  function atomWithFallback<T>(
    options: AtomWithFallbackOptions<T>,
  ): RecoilState<T> {
    const base = atom<T | DefaultValue>({
      ...options,
      default: DEFAULT_VALUE,
      persistence_UNSTABLE:
        options.persistence_UNSTABLE === undefined
          ? undefined
          : {
              ...options.persistence_UNSTABLE,
              validator: storedValue =>
                storedValue instanceof DefaultValue
                  ? storedValue
                  : nullthrows(options.persistence_UNSTABLE).validator(
                      storedValue,
                      DEFAULT_VALUE,
                    ),
            },
      // TODO Hack for now.
      effects: (options.effects: any), // flowlint-line unclear-type: off
      effects_UNSTABLE: (options.effects_UNSTABLE: any), // flowlint-line unclear-type: off
    });
  
    const sel = selector<T>({
      key: `${options.key}__withFallback`,
      get: ({get}) => {
        const baseValue = get(base);
        return baseValue instanceof DefaultValue ? options.default : baseValue;
      },
      set: ({set}, newValue) => set(base, newValue),
      dangerouslyAllowMutability: options.dangerouslyAllowMutability,
    });
    setConfigDeletionHandler(sel.key, getConfigDeletionHandler(options.key));
    return sel;
  }
  
  atom.value = value => new WrappedValue(value);
  
  module.exports = (atom: {
    <T>(AtomOptions<T>): RecoilState<T>,
    value: <S>(S) => WrappedValue<S>,
  });
  