// packages/data/src/redux-store/index.js
import { createStore, applyMiddleware } from "redux";
import EquivalentKeyMap from "equivalent-key-map";
import createReduxRoutineMiddleware from "@wordpress/redux-routine";
import { compose } from "@wordpress/compose";
import { combineReducers } from "./combine-reducers.mjs";
import { builtinControls } from "../controls.mjs";
import { lock } from "../lock-unlock.mjs";
import promise from "../promise-middleware.mjs";
import createResolversCacheMiddleware from "../resolvers-cache-middleware.mjs";
import createThunkMiddleware from "./thunk-middleware.mjs";
import metadataReducer from "./metadata/reducer.mjs";
import * as metadataSelectors from "./metadata/selectors.mjs";
import * as metadataActions from "./metadata/actions.mjs";
var trimUndefinedValues = (array) => {
  const result = [...array];
  for (let i = result.length - 1; i >= 0; i--) {
    if (result[i] === void 0) {
      result.splice(i, 1);
    }
  }
  return result;
};
var mapValues = (obj, callback) => Object.fromEntries(
  Object.entries(obj ?? {}).map(([key, value]) => [
    key,
    callback(value, key)
  ])
);
var devToolsReplacer = (key, state) => {
  if (state instanceof Map) {
    return Object.fromEntries(state);
  }
  if (state instanceof window.HTMLElement) {
    return null;
  }
  return state;
};
function createResolversCache() {
  const cache = {};
  return {
    isRunning(selectorName, args) {
      return cache[selectorName] && cache[selectorName].get(trimUndefinedValues(args));
    },
    clear(selectorName, args) {
      if (cache[selectorName]) {
        cache[selectorName].delete(trimUndefinedValues(args));
      }
    },
    markAsRunning(selectorName, args) {
      if (!cache[selectorName]) {
        cache[selectorName] = new EquivalentKeyMap();
      }
      cache[selectorName].set(trimUndefinedValues(args), true);
    }
  };
}
function createBindingCache(getItem, bindItem) {
  const cache = /* @__PURE__ */ new WeakMap();
  return {
    get(itemName) {
      const item = getItem(itemName);
      if (!item) {
        return null;
      }
      let boundItem = cache.get(item);
      if (!boundItem) {
        boundItem = bindItem(item, itemName);
        cache.set(item, boundItem);
      }
      return boundItem;
    }
  };
}
function createPrivateProxy(publicItems, privateItems) {
  return new Proxy(publicItems, {
    get: (target, itemName) => privateItems.get(itemName) || Reflect.get(target, itemName)
  });
}
function createReduxStore(key, options) {
  const privateActions = {};
  const privateSelectors = {};
  const privateRegistrationFunctions = {
    privateActions,
    registerPrivateActions: (actions) => {
      Object.assign(privateActions, actions);
    },
    privateSelectors,
    registerPrivateSelectors: (selectors) => {
      Object.assign(privateSelectors, selectors);
    }
  };
  const storeDescriptor = {
    name: key,
    instantiate: (registry) => {
      const listeners = /* @__PURE__ */ new Set();
      const reducer = options.reducer;
      const thunkArgs = {
        registry,
        get dispatch() {
          return thunkDispatch;
        },
        get select() {
          return thunkSelect;
        },
        get resolveSelect() {
          return resolveSelectors;
        }
      };
      const store = instantiateReduxStore(
        key,
        options,
        registry,
        thunkArgs
      );
      lock(store, privateRegistrationFunctions);
      const resolversCache = createResolversCache();
      function bindAction(action) {
        return (...args) => Promise.resolve(store.dispatch(action(...args)));
      }
      const actions = {
        ...mapValues(metadataActions, bindAction),
        ...mapValues(options.actions, bindAction)
      };
      const allActions = createPrivateProxy(
        actions,
        createBindingCache(
          (name) => privateActions[name],
          bindAction
        )
      );
      const thunkDispatch = new Proxy(
        (action) => store.dispatch(action),
        { get: (target, name) => allActions[name] }
      );
      lock(actions, allActions);
      const resolvers = options.resolvers ? mapValues(options.resolvers, mapResolver) : {};
      function bindSelector(selector, selectorName) {
        if (selector.isRegistrySelector) {
          selector.registry = registry;
        }
        const boundSelector = (...args) => {
          args = normalize(selector, args);
          const state = store.__unstableOriginalGetState();
          if (selector.isRegistrySelector) {
            selector.registry = registry;
          }
          return selector(state.root, ...args);
        };
        boundSelector.__unstableNormalizeArgs = selector.__unstableNormalizeArgs;
        const resolver = resolvers[selectorName];
        if (!resolver) {
          boundSelector.hasResolver = false;
          return boundSelector;
        }
        return mapSelectorWithResolver(
          boundSelector,
          selectorName,
          resolver,
          store,
          resolversCache,
          boundMetadataSelectors
        );
      }
      function bindMetadataSelector(metaDataSelector) {
        const boundSelector = (selectorName, selectorArgs, ...args) => {
          if (selectorName) {
            const targetSelector = options.selectors?.[selectorName];
            if (targetSelector) {
              selectorArgs = normalize(
                targetSelector,
                selectorArgs
              );
            }
          }
          const state = store.__unstableOriginalGetState();
          return metaDataSelector(
            state.metadata,
            selectorName,
            selectorArgs,
            ...args
          );
        };
        boundSelector.hasResolver = false;
        return boundSelector;
      }
      const boundMetadataSelectors = mapValues(
        metadataSelectors,
        bindMetadataSelector
      );
      const boundSelectors = mapValues(options.selectors, bindSelector);
      const selectors = {
        ...boundMetadataSelectors,
        ...boundSelectors
      };
      const boundPrivateSelectors = createBindingCache(
        (name) => privateSelectors[name],
        bindSelector
      );
      const allSelectors = createPrivateProxy(
        selectors,
        boundPrivateSelectors
      );
      for (const selectorName of Object.keys(privateSelectors)) {
        boundPrivateSelectors.get(selectorName);
      }
      const thunkSelect = new Proxy(
        (selector) => selector(store.__unstableOriginalGetState()),
        { get: (target, name) => allSelectors[name] }
      );
      lock(selectors, allSelectors);
      const bindResolveSelector = mapResolveSelector(
        store,
        boundMetadataSelectors
      );
      const resolveSelectors = mapValues(
        boundSelectors,
        bindResolveSelector
      );
      const allResolveSelectors = createPrivateProxy(
        resolveSelectors,
        createBindingCache(
          (name) => boundPrivateSelectors.get(name),
          bindResolveSelector
        )
      );
      lock(resolveSelectors, allResolveSelectors);
      const bindSuspendSelector = mapSuspendSelector(
        store,
        boundMetadataSelectors
      );
      const suspendSelectors = {
        ...boundMetadataSelectors,
        // no special suspense behavior
        ...mapValues(boundSelectors, bindSuspendSelector)
      };
      const allSuspendSelectors = createPrivateProxy(
        suspendSelectors,
        createBindingCache(
          (name) => boundPrivateSelectors.get(name),
          bindSuspendSelector
        )
      );
      lock(suspendSelectors, allSuspendSelectors);
      const getSelectors = () => selectors;
      const getActions = () => actions;
      const getResolveSelectors = () => resolveSelectors;
      const getSuspendSelectors = () => suspendSelectors;
      store.__unstableOriginalGetState = store.getState;
      store.getState = () => store.__unstableOriginalGetState().root;
      const subscribe = store && ((listener) => {
        listeners.add(listener);
        return () => listeners.delete(listener);
      });
      let lastState = store.__unstableOriginalGetState();
      store.subscribe(() => {
        const state = store.__unstableOriginalGetState();
        const hasChanged = state !== lastState;
        lastState = state;
        if (hasChanged) {
          for (const listener of listeners) {
            listener();
          }
        }
      });
      return {
        reducer,
        store,
        actions,
        selectors,
        resolvers,
        getSelectors,
        getResolveSelectors,
        getSuspendSelectors,
        getActions,
        subscribe
      };
    }
  };
  lock(storeDescriptor, privateRegistrationFunctions);
  return storeDescriptor;
}
function instantiateReduxStore(key, options, registry, thunkArgs) {
  const controls = {
    ...options.controls,
    ...builtinControls
  };
  const normalizedControls = mapValues(
    controls,
    (control) => control.isRegistryControl ? control(registry) : control
  );
  const middlewares = [
    createResolversCacheMiddleware(registry, key),
    promise,
    createReduxRoutineMiddleware(normalizedControls),
    createThunkMiddleware(thunkArgs)
  ];
  const enhancers = [applyMiddleware(...middlewares)];
  if (typeof window !== "undefined" && window.__REDUX_DEVTOOLS_EXTENSION__) {
    enhancers.push(
      window.__REDUX_DEVTOOLS_EXTENSION__({
        name: key,
        instanceId: key,
        serialize: {
          replacer: devToolsReplacer
        }
      })
    );
  }
  const { reducer, initialState } = options;
  const enhancedReducer = combineReducers({
    metadata: metadataReducer,
    root: reducer
  });
  return createStore(
    enhancedReducer,
    { root: initialState },
    compose(enhancers)
  );
}
function mapResolveSelector(store, boundMetadataSelectors) {
  return (selector, selectorName) => {
    if (!selector.hasResolver) {
      return async (...args) => selector.apply(null, args);
    }
    return (...args) => new Promise((resolve, reject) => {
      const hasFinished = () => {
        return boundMetadataSelectors.hasFinishedResolution(
          selectorName,
          args
        );
      };
      const finalize = (result2) => {
        const hasFailed = boundMetadataSelectors.hasResolutionFailed(
          selectorName,
          args
        );
        if (hasFailed) {
          const error = boundMetadataSelectors.getResolutionError(
            selectorName,
            args
          );
          reject(error);
        } else {
          resolve(result2);
        }
      };
      const getResult = () => selector.apply(null, args);
      const result = getResult();
      if (hasFinished()) {
        return finalize(result);
      }
      const unsubscribe = store.subscribe(() => {
        if (hasFinished()) {
          unsubscribe();
          finalize(getResult());
        }
      });
    });
  };
}
function mapSuspendSelector(store, boundMetadataSelectors) {
  return (selector, selectorName) => {
    if (!selector.hasResolver) {
      return selector;
    }
    return (...args) => {
      const result = selector.apply(null, args);
      if (boundMetadataSelectors.hasFinishedResolution(
        selectorName,
        args
      )) {
        if (boundMetadataSelectors.hasResolutionFailed(
          selectorName,
          args
        )) {
          throw boundMetadataSelectors.getResolutionError(
            selectorName,
            args
          );
        }
        return result;
      }
      throw new Promise((resolve) => {
        const unsubscribe = store.subscribe(() => {
          if (boundMetadataSelectors.hasFinishedResolution(
            selectorName,
            args
          )) {
            resolve();
            unsubscribe();
          }
        });
      });
    };
  };
}
function mapResolver(resolver) {
  if (resolver.fulfill) {
    return resolver;
  }
  return {
    ...resolver,
    // Copy the enumerable properties of the resolver function.
    fulfill: resolver
    // Add the fulfill method.
  };
}
function mapSelectorWithResolver(selector, selectorName, resolver, store, resolversCache, boundMetadataSelectors) {
  function fulfillSelector(args) {
    if (resolversCache.isRunning(selectorName, args) || boundMetadataSelectors.hasStartedResolution(selectorName, args)) {
      return;
    }
    resolversCache.markAsRunning(selectorName, args);
    setTimeout(async () => {
      resolversCache.clear(selectorName, args);
      store.dispatch(
        metadataActions.startResolution(selectorName, args)
      );
      try {
        const isFulfilled = typeof resolver.isFulfilled === "function" && resolver.isFulfilled(store.getState(), ...args);
        if (!isFulfilled) {
          const action = resolver.fulfill(...args);
          if (action) {
            await store.dispatch(action);
          }
        }
        store.dispatch(
          metadataActions.finishResolution(selectorName, args)
        );
      } catch (error) {
        store.dispatch(
          metadataActions.failResolution(selectorName, args, error)
        );
      }
    }, 0);
  }
  const selectorResolver = (...args) => {
    args = normalize(selector, args);
    fulfillSelector(args);
    return selector(...args);
  };
  selectorResolver.hasResolver = true;
  return selectorResolver;
}
function normalize(selector, args) {
  if (selector.__unstableNormalizeArgs && typeof selector.__unstableNormalizeArgs === "function" && args?.length) {
    return selector.__unstableNormalizeArgs(args);
  }
  return args;
}
export {
  combineReducers,
  createReduxStore as default
};
//# sourceMappingURL=index.mjs.map
