import { trackSelector, when, isPrimitive, isObservable, computeSelector, isFunction, isEmpty, isPromise } from "./index.js";
import React__default, { forwardRef, memo, useContext, useMemo, createContext, createElement, useRef, useEffect, useCallback } from "react";
import { s as shimExports } from "../../../_virtual/index.js";
import { enableReactive } from "./react-reactive-enableReactive.js";
var reactGlobals = {
  inObserver: false
};
var pauseContext = void 0;
var getPauseContext = () => {
  return pauseContext || (pauseContext = createContext(null));
};
function createSelectorFunctions(options, isPaused$) {
  let version = 0;
  let notify;
  let dispose;
  let resubscribe;
  let _selector;
  let prev;
  let pendingUpdate = void 0;
  const run = () => {
    dispose == null ? void 0 : dispose();
    const {
      value,
      dispose: _dispose,
      resubscribe: _resubscribe
    } = trackSelector(
      _selector,
      _update,
      options,
      void 0,
      void 0,
      /*createResubscribe*/
      true
    );
    dispose = _dispose;
    resubscribe = _resubscribe;
    return value;
  };
  const _update = ({ value }) => {
    if (isPaused$ == null ? void 0 : isPaused$.peek()) {
      const next = pendingUpdate;
      pendingUpdate = value;
      if (next === void 0) {
        when(
          () => !isPaused$.get(),
          () => {
            const latest = pendingUpdate;
            pendingUpdate = void 0;
            _update({ value: latest });
          }
        );
      }
    } else {
      let changed = options == null ? void 0 : options.skipCheck;
      if (!changed) {
        const newValue = run();
        if (newValue !== prev || !isPrimitive(newValue) && newValue === value) {
          changed = true;
        }
      }
      if (changed) {
        version++;
        notify == null ? void 0 : notify();
      }
    }
  };
  return {
    subscribe: (onStoreChange) => {
      notify = onStoreChange;
      if ((process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") && !dispose && resubscribe) {
        dispose = resubscribe();
      }
      return () => {
        dispose == null ? void 0 : dispose();
        dispose = void 0;
      };
    },
    getVersion: () => version,
    run: (selector) => {
      _selector = selector;
      return prev = run();
    }
  };
}
function doSuspense(selector) {
  const vProm = when(selector);
  if (React__default.use) {
    React__default.use(vProm);
  } else {
    throw vProm;
  }
}
function useSelector(selector, options) {
  var _a;
  let value;
  if (reactGlobals.inObserver && isObservable(selector) && !(options == null ? void 0 : options.suspense)) {
    value = computeSelector(selector, options);
    if ((options == null ? void 0 : options.suspense) && value === void 0) {
      doSuspense(selector);
    }
    return value;
  }
  try {
    const isPaused$ = useContext(getPauseContext());
    const selectorFn = useMemo(() => createSelectorFunctions(options, isPaused$), []);
    const { subscribe, getVersion, run } = selectorFn;
    value = run(selector);
    shimExports.useSyncExternalStore(subscribe, getVersion, getVersion);
  } catch (err) {
    if ((process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") && ((_a = err == null ? void 0 : err.message) == null ? void 0 : _a.includes("Rendered more"))) {
      console.warn(
        `[legend-state]: You may want to wrap this component in \`observer\` to fix the error of ${err.message}`
      );
    }
    throw err;
  }
  if ((options == null ? void 0 : options.suspense) && value === void 0) {
    doSuspense(selector);
  }
  return value;
}
function Computed({ children }) {
  return useSelector(() => computeSelector(computeSelector(children)), { skipCheck: true });
}
var hasSymbol = typeof Symbol === "function" && Symbol.for;
var didWarnProps = false;
function createReactiveComponent(component, observe3, reactive2, keysReactive, bindKeys) {
  const ReactForwardRefSymbol = hasSymbol ? Symbol.for("react.forward_ref") : (
    // eslint-disable-next-line react/display-name, @typescript-eslint/no-unused-vars
    typeof forwardRef === "function" && forwardRef((props) => null)["$$typeof"]
  );
  const ReactMemoSymbol = hasSymbol ? Symbol.for("react.memo") : (
    // eslint-disable-next-line react/display-name, @typescript-eslint/no-unused-vars
    typeof forwardRef === "function" && memo((props) => null)["$$typeof"]
  );
  if (component["__legend_proxied"])
    return component;
  let useForwardRef = false;
  let useMemo4 = false;
  let render = component;
  if (ReactMemoSymbol && render["$$typeof"] === ReactMemoSymbol && render["type"]) {
    useMemo4 = true;
    render = render["type"];
  }
  if (ReactForwardRefSymbol && render["$$typeof"] === ReactForwardRefSymbol) {
    useForwardRef = true;
    render = render["render"];
    if (process.env.NODE_ENV === "development" && typeof render !== "function") {
      throw new Error(`[legend-state] \`render\` property of ForwardRef was not a function`);
    }
  }
  const keysReactiveSet = keysReactive ? new Set(keysReactive) : void 0;
  const proxyHandler = {
    apply(target, thisArg, argArray) {
      if (reactive2) {
        const props = argArray[0];
        const propsOut = {};
        const keys = Object.keys(props);
        for (let i = 0; i < keys.length; i++) {
          const key = keys[i];
          const p = props[key];
          const isReactiveKey = keysReactiveSet && keysReactiveSet.has(key);
          if (key === "children" && (isFunction(p) || isObservable(p))) {
            props[key] = useSelector(p, { skipCheck: true });
          } else if (isReactiveKey || key.startsWith("$") || key.endsWith("$")) {
            if (process.env.NODE_ENV === "development" && !didWarnProps && key.endsWith("$")) {
              didWarnProps = true;
              console.warn(
                `[legend-state] Reactive props were changed to start with $ instead of end with $ in version 2.0. So please change ${key} to $${key.replace(
                  "$",
                  ""
                )}. See https://legendapp.com/open-source/state/migrating for more details.`
              );
            }
            const k = isReactiveKey ? key : key.endsWith("$") ? key.slice(0, -1) : key.slice(1);
            const bind = bindKeys == null ? void 0 : bindKeys[k];
            const shouldBind = bind && isObservable(p);
            propsOut[k] = shouldBind && (bind == null ? void 0 : bind.selector) ? bind.selector(propsOut, p) : useSelector(p);
            if (shouldBind) {
              if (bind.defaultValue !== void 0 && propsOut[k] === void 0) {
                propsOut[k] = bind.defaultValue;
              }
              if (bind.handler && bind.getValue) {
                const handlerFn = (e) => {
                  var _a;
                  p.set(bind.getValue(e));
                  (_a = props[bind.handler]) == null ? void 0 : _a.call(props, e);
                };
                propsOut[bind.handler] = // If in development mode, don't memoize the handler. fix fast refresh bug
                process.env.NODE_ENV === "development" ? handlerFn : useCallback(handlerFn, [props[bind.handler], bindKeys]);
              }
            }
            if (!isReactiveKey) {
              delete propsOut[key];
            }
          } else if (propsOut[key] === void 0) {
            propsOut[key] = p;
          }
        }
        argArray[0] = propsOut;
      }
      if (observe3) {
        return useSelector(
          () => {
            reactGlobals.inObserver = true;
            try {
              return Reflect.apply(target, thisArg, argArray);
            } finally {
              reactGlobals.inObserver = false;
            }
          },
          { skipCheck: true }
        );
      } else {
        return Reflect.apply(target, thisArg, argArray);
      }
    }
  };
  const proxy = new Proxy(render, proxyHandler);
  let ret;
  if (useForwardRef) {
    ret = forwardRef(proxy);
    ret["__legend_proxied"] = true;
  } else {
    ret = proxy;
  }
  return observe3 || useMemo4 ? memo(ret) : ret;
}
function observer(component) {
  return createReactiveComponent(component, true);
}
function reactive(component, keys, bindKeys) {
  return createReactiveComponent(component, false, true, keys, bindKeys);
}
memo(
  Computed,
  (prev, next) => next.scoped ? prev.children === next.children : true
);
var ReactiveFns = /* @__PURE__ */ new Map();
var ReactiveFnBinders = /* @__PURE__ */ new Map();
function configureReactive({
  components,
  binders
}) {
  if (components) {
    for (const key in components) {
      ReactiveFns.set(key, components[key]);
    }
  }
  if (binders) {
    for (const key in binders) {
      ReactiveFnBinders.set(key, binders[key]);
    }
  }
}
new Proxy(
  {},
  {
    get(target, p) {
      if (!target[p]) {
        const Component = ReactiveFns.get(p) || p;
        const render = forwardRef((props, ref) => {
          const propsOut = { ...props };
          if (ref && (isFunction(ref) || !isEmpty(ref))) {
            propsOut.ref = ref;
          }
          return createElement(Component, propsOut);
        });
        target[p] = reactive(render, [], ReactiveFnBinders.get(p));
      }
      return target[p];
    }
  }
);
if (process.env.NODE_ENV !== "test") {
  enableReactive(configureReactive);
}
var useEffectOnce = (effect, deps) => {
  if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") {
    const refDispose = useRef({ num: 0 });
    useEffect(() => {
      var _a;
      const { current } = refDispose;
      current.num++;
      const dispose = () => {
        if (current.dispose && current.num < 2) {
          current.dispose();
          current.dispose = void 0;
        }
        current.num--;
      };
      if (current.dispose === void 0) {
        const ret = (_a = effect()) != null ? _a : null;
        if (ret && isFunction(ret)) {
          current.dispose = ret;
          return () => queueMicrotask(dispose);
        }
      } else {
        return dispose;
      }
    }, deps);
  } else {
    useEffect(effect, deps);
  }
};
function useMount(fn) {
  return useEffectOnce(() => {
    const ret = fn();
    if (!isPromise(ret)) {
      return ret;
    }
  }, []);
}
export {
  Computed,
  configureReactive,
  hasSymbol,
  observer,
  reactive,
  useSelector as use$,
  useEffectOnce,
  useMount,
  useSelector
};
//# sourceMappingURL=react.js.map
