"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.useSharedPluginState = useSharedPluginState;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _react = require("react");
var _debounce = _interopRequireDefault(require("lodash/debounce"));
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any

/**
 *
 * Directly map object values
 *
 * @param object The object to transform
 * @param mapFunction The function to map an old value to new one
 * @returns Object with the same key but transformed values
 *
 */
function mapValues(object, mapFunction) {
  return Object.entries(object).reduce(function (acc, _ref) {
    var _ref2 = (0, _slicedToArray2.default)(_ref, 2),
      key = _ref2[0],
      value = _ref2[1];
    return _objectSpread(_objectSpread({}, acc), {}, (0, _defineProperty2.default)({}, key, mapFunction(value)));
  }, {});
}

// When we use the `useSharedPluginState` example: `useSharedPluginState(api, ['width'])`
// it will re-render every time the component re-renders as the array "['width']" is seen as an update.
// This hook is used to prevent re-renders due to this.
function useStaticPlugins(plugins) {
  // eslint-disable-next-line react-hooks/exhaustive-deps
  return (0, _react.useMemo)(function () {
    return plugins;
  }, []);
}
/**
 *
 * NOTE: Generally you want to use `useSharedPluginStateWithSelector` over this which behaves similarly
 * but selects a slice of the state which is more performant.
 *
 * ⚠️⚠️⚠️ This is a debounced hook ⚠️⚠️⚠️
 * If the plugins you are listening to generate multiple shared states while the user is typing,
 * your React Component will get only the last one.
 *
 * Usually, for UI updates, you may need only the last state. But, if you have a specific scenario requiring you to access all states,
 * do not use this hook. Instead, you can subscribe directly to the plugin sharedState API:
 *
 * ```typescript
 *
 * function ExampleSpecialCase({ api }: Props) {
 *   const [dogState, setDogState] = React.useState(null);
 *   useEffect(() => {
 *     const unsub = api.dog.sharedState.onChange(({ nextSharedState, prevSharedState }) => {
 *        setDogState(nextSharedState);
 *     });
 *
 *     return unsub;
 *   }, [api]);
 *
 *   useEffect(() => {
 *    someCriticalAndWeirdUseCase(dogState);
 *
 *   }, [dogState]);
 *
 *   return null;
 * }
 *
 * ```
 *
 * Used to return the current plugin state of
 * input dependencies
 *
 * Example in plugin:
 *
 * ```typescript
 * function ExampleContent({ api }: Props) {
 *   const { dogState, exampleState } = useSharedPluginState(
 *     api,
 *     ['dog', 'example']
 *   )
 *   return <p>{ dogState.title } { exampleState.description }</p>
 * }
 *
 * const examplePlugin: NextEditorPlugin<'example', { dependencies: [typeof pluginDog] }> = ({ api }) => {
 *   return {
 *     name: 'example',
 *     contentComponent: () =>
 *       <ExampleContent
 *         api={api}
 *         />
 *   }
 * }
 * ```
 *
 * @param injectionApi Plugin injection API from `NextEditorPlugin`
 * @param plugins Plugin names to get the shared plugin state for
 * @param options The useSharedPluginState options
 * @returns A corresponding object, the keys are names of the plugin with `State` appended,
 * the values are the shared state exposed by that plugin.
 */
function useSharedPluginState(injectionApi, plugins, options) {
  var pluginNames = useStaticPlugins(plugins);

  // Create a memoized object containing the named plugins
  var namedExternalPlugins = (0, _react.useMemo)(function () {
    return pluginNames.reduce(function (acc, pluginName) {
      return _objectSpread(_objectSpread({}, acc), {}, (0, _defineProperty2.default)({}, "".concat(String(pluginName), "State"), injectionApi === null || injectionApi === void 0 ? void 0 : injectionApi[pluginName]));
    }, {});
  }, [injectionApi, pluginNames]);
  return useSharedPluginStateInternal(namedExternalPlugins, options);
}
function useSharedPluginStateInternal(externalPlugins) {
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  var _useState = (0, _react.useState)(mapValues(externalPlugins, function (value) {
      return options.disabled ? undefined : value === null || value === void 0 ? void 0 : value.sharedState.currentState();
    })),
    _useState2 = (0, _slicedToArray2.default)(_useState, 2),
    pluginStates = _useState2[0],
    setPluginState = _useState2[1];
  var refStates = (0, _react.useRef)({});
  var mounted = (0, _react.useRef)(false);
  (0, _react.useLayoutEffect)(function () {
    if (options.disabled) {
      return;
    }
    var debouncedPluginStateUpdate = (0, _debounce.default)(function () {
      setPluginState(function (currentPluginStates) {
        return _objectSpread(_objectSpread({}, currentPluginStates), refStates.current);
      });
    });

    // If we re-render this hook due to a change in the external
    // plugins we need to push a state update to ensure we have
    // the most current state.
    if (mounted.current) {
      refStates.current = mapValues(externalPlugins, function (value) {
        return value === null || value === void 0 ? void 0 : value.sharedState.currentState();
      });
      debouncedPluginStateUpdate();
    }
    var unsubs = Object.entries(externalPlugins).map(function (_ref3) {
      var _ref4 = (0, _slicedToArray2.default)(_ref3, 2),
        pluginKey = _ref4[0],
        externalPlugin = _ref4[1];
      return externalPlugin === null || externalPlugin === void 0 ? void 0 : externalPlugin.sharedState.onChange(function (_ref5) {
        var nextSharedState = _ref5.nextSharedState,
          prevSharedState = _ref5.prevSharedState;
        if (prevSharedState === nextSharedState) {
          return;
        }
        refStates.current[pluginKey] = nextSharedState;
        debouncedPluginStateUpdate();
      });
    });
    mounted.current = true;
    return function () {
      refStates.current = {};
      unsubs.forEach(function (cb) {
        return cb === null || cb === void 0 ? void 0 : cb();
      });
    };
    // Do not re-render due to state changes, we only need to check this when
    // setting up the initial subscription.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [externalPlugins, options.disabled]);
  return pluginStates;
}