"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.memoizeableAction = memoizeableAction;
loader.lazyRequireGetter(this, "_asyncValue", "devtools/client/debugger/src/utils/async-value");
loader.lazyRequireGetter(this, "_context", "devtools/client/debugger/src/utils/context");

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */

/*
 * memoizableActon is a utility for actions that should only be performed
 * once per key. It is useful for loading sources, parsing symbols ...
 *
 * @getValue - gets the result from the redux store
 * @createKey - creates a key for the requests map
 * @action - kicks off the async work for the action
 *
 *
 * For Example
 *
 * export const setItem = memoizeableAction(
 *   "setItem",
 *   {
 *     hasValue: ({ a }, { getState }) => hasItem(getState(), a),
 *     getValue: ({ a }, { getState }) => getItem(getState(), a),
 *     createKey: ({ a }) => a,
 *     action: ({ a }, thunkArgs) => doSetItem(a, thunkArgs)
 *   }
 * );
 *
 */
function memoizeableAction(name, {
  getValue,
  createKey,
  action
}) {
  const requests = new Map();
  return args => async thunkArgs => {
    let result = (0, _asyncValue.asSettled)(getValue(args, thunkArgs));

    if (!result) {
      const key = createKey(args, thunkArgs);

      if (!requests.has(key)) {
        requests.set(key, (async () => {
          try {
            await action(args, thunkArgs);
          } catch (e) {
            console.warn(`Action ${name} had an exception:`, e);
          } finally {
            requests.delete(key);
          }
        })());
      }

      await requests.get(key);

      if (args.cx) {
        (0, _context.validateContext)(thunkArgs.getState(), args.cx);
      }

      result = (0, _asyncValue.asSettled)(getValue(args, thunkArgs));

      if (!result) {
        // Returning null here is not ideal. This means that the action
        // resolved but 'getValue' didn't return a loaded value, for instance
        // if the data the action was meant to store was deleted. In a perfect
        // world we'd throw a ContextError here or handle cancellation somehow.
        // Throwing will also allow us to change the return type on the action
        // to always return a promise for the getValue AsyncValue type, but
        // for now we have to add an additional '| null' for this case.
        return null;
      }
    }

    if (result.state === "rejected") {
      throw result.value;
    }

    return result.value;
  };
}