import {
  configureStore as configureReduxStore,
  combineReducers
} from "@reduxjs/toolkit";
import deepEqual from "../utils/deepEqual";
import * as Documents from "./modules/read-write-documents";
import * as ElementTrees from "./modules/element-trees";
import * as ReactComponents from "./modules/react-components";
import * as BoxModels from "./modules/box-models";
import * as ComponentsMeta from "./modules/components-meta";
import * as PropControllers from "./modules/prop-controllers";
import * as PropControllerHandles from "./modules/prop-controller-handles";
import * as IsInBuilder from "./modules/is-in-builder";
import * as IsPreview from "./modules/is-preview";
import * as BuilderEditMode from "./modules/builder-edit-mode";
import * as Pointer from "./modules/pointer";
import * as ElementImperativeHandles from "./modules/element-imperative-handles";
import * as Breakpoints from "./modules/breakpoints";
import { withSetupTeardown } from "./mixins/setup-teardown";
import * as ReactPage from "./react-page";
import * as Shared from "./shared-api";
import { BuilderActionTypes } from "./builder-api/actions";
import * as Builder from "./builder-api/actions";
import { InternalActionTypes } from "./actions/internal";
import * as Internal from "./actions/internal";
import { actionMiddleware, middlewareOptions, devToolsConfig } from "./toolkit";
import { createPropController } from "../prop-controllers/instances";
import { serializeControls } from "../builder";
import { HostActionTypes } from "./host-api";
import { createBox, getBox, parse } from "./modules/box-models";
const reducer = combineReducers({
  documents: Documents.reducer,
  elementTrees: ElementTrees.reducer,
  reactComponents: ReactComponents.reducer,
  boxModels: BoxModels.reducer,
  componentsMeta: ComponentsMeta.reducer,
  propControllers: PropControllers.reducer,
  propControllerHandles: PropControllerHandles.reducer,
  isInBuilder: IsInBuilder.reducer,
  isPreview: IsPreview.reducer,
  builderEditMode: BuilderEditMode.reducer,
  pointer: Pointer.reducer,
  elementImperativeHandles: ElementImperativeHandles.reducer,
  breakpoints: Breakpoints.reducer
});
function getDocumentsStateSlice(state) {
  return state.documents;
}
function getBoxModelsStateSlice(state) {
  return state.boxModels;
}
function getMeasurables(state) {
  return BoxModels.getMeasurables(getBoxModelsStateSlice(state));
}
function getBoxModels(state) {
  return BoxModels.getBoxModels(getBoxModelsStateSlice(state));
}
function getBoxModel(state, documentKey, elementKey) {
  return BoxModels.getBoxModel(getBoxModelsStateSlice(state), documentKey, elementKey);
}
function getComponentsMetaStateSlice(state) {
  return state.componentsMeta;
}
function getComponentsMeta(state) {
  return ComponentsMeta.getComponentsMeta(getComponentsMetaStateSlice(state));
}
function getPropControllersStateSlice(state) {
  return state.propControllers;
}
function getComponentPropControllerDescriptors(state, componentType) {
  return PropControllers.getComponentPropControllerDescriptors(
    getPropControllersStateSlice(state),
    componentType
  );
}
function getPropControllerHandlesStateSlice(state) {
  return state.propControllerHandles;
}
function getPointer(state) {
  return Pointer.getPointer(state.pointer);
}
function getElementImperativeHandles(state) {
  return ElementImperativeHandles.getElementImperativeHandles(state.elementImperativeHandles);
}
function getElementImperativeHandlesContainingElement(state, element) {
  const elementImperativeHandles = getElementImperativeHandles(state);
  const filteredElementImperativeHandles = /* @__PURE__ */ new Map();
  for (const [documentKey, byElementKey] of elementImperativeHandles) {
    const filteredByElementKey = /* @__PURE__ */ new Map();
    for (const [elementKey, elementImperativeHandle] of byElementKey) {
      const handleElement = elementImperativeHandle.getDomNode();
      if (handleElement?.contains(element)) {
        filteredByElementKey.set(elementKey, elementImperativeHandle);
      }
    }
    if (filteredByElementKey.size > 0) {
      filteredElementImperativeHandles.set(documentKey, filteredByElementKey);
    }
  }
  return filteredElementImperativeHandles;
}
function measureElements() {
  return (dispatch, getState) => {
    const measurables = getMeasurables(getState());
    const currentBoxModels = getBoxModels(getState());
    const measuredBoxModels = /* @__PURE__ */ new Map();
    measurables.forEach((documentMeasurables, documentKey) => {
      const measuredDocumentBoxModels = /* @__PURE__ */ new Map();
      documentMeasurables.forEach((measurable, elementKey) => {
        const boxModel = BoxModels.measure(measurable);
        if (boxModel != null)
          measuredDocumentBoxModels.set(elementKey, boxModel);
      });
      if (measuredDocumentBoxModels.size > 0) {
        measuredBoxModels.set(documentKey, measuredDocumentBoxModels);
      }
    });
    const changedBoxModels = /* @__PURE__ */ new Map();
    currentBoxModels.forEach((currentDocumentBoxModels, documentKey) => {
      const changedDocumentBoxModels = /* @__PURE__ */ new Map();
      currentDocumentBoxModels.forEach((_boxModel, elementKey) => {
        if (!measuredBoxModels.get(documentKey)?.has(elementKey)) {
          changedDocumentBoxModels.set(elementKey, null);
        }
        if (changedDocumentBoxModels.size > 0) {
          changedBoxModels.set(documentKey, changedDocumentBoxModels);
        }
      });
    });
    measuredBoxModels.forEach((measuredDocumentBoxModels, documentKey) => {
      const changedDocumentBoxModels = /* @__PURE__ */ new Map();
      measuredDocumentBoxModels.forEach((measuredBoxModel, elementKey) => {
        const currentBoxModel = getBoxModel(getState(), documentKey, elementKey);
        if (currentBoxModel == null || !deepEqual(currentBoxModel, measuredBoxModel)) {
          changedDocumentBoxModels.set(elementKey, measuredBoxModel);
        }
      });
      if (changedDocumentBoxModels.size > 0) {
        changedBoxModels.set(documentKey, changedDocumentBoxModels);
      }
    });
    if (changedBoxModels.size > 0)
      dispatch(Builder.changeElementBoxModels(changedBoxModels));
  };
}
function startMeasuringElements() {
  return (dispatch) => {
    let animationFrameHandle = requestAnimationFrame(handleAnimationFrameRequest);
    return () => {
      cancelAnimationFrame(animationFrameHandle);
    };
    function handleAnimationFrameRequest() {
      dispatch(measureElements());
      animationFrameHandle = requestAnimationFrame(handleAnimationFrameRequest);
    }
  };
}
function getElementSize(element) {
  return {
    offsetWidth: element.offsetWidth,
    offsetHeight: element.offsetHeight,
    clientWidth: element.clientWidth,
    clientHeight: element.clientHeight,
    scrollWidth: element.scrollWidth,
    scrollHeight: element.scrollHeight,
    scrollTop: element.scrollTop,
    scrollLeft: element.scrollLeft
  };
}
function lockDocumentScroll() {
  return (dispatch) => {
    const lastDocumentOverflow = window.document.documentElement.style.overflow;
    window.document.documentElement.style.overflow = "hidden";
    window.document.documentElement.addEventListener("wheel", handleWheelEvent);
    return () => {
      window.document.documentElement.style.overflow = lastDocumentOverflow;
      window.document.documentElement.removeEventListener("wheel", handleWheelEvent);
    };
    function handleWheelEvent({ deltaX, deltaY }) {
      dispatch(Builder.handleWheel({ deltaX, deltaY }));
    }
  };
}
function startHandlingPointerMoveEvent() {
  return (dispatch) => {
    window.document.documentElement.addEventListener("pointermove", handlePointerMoveEvent);
    return () => {
      window.document.documentElement.removeEventListener("pointermove", handlePointerMoveEvent);
    };
    function handlePointerMoveEvent({ clientX, clientY }) {
      dispatch(Builder.handlePointerMove({ clientX, clientY }));
    }
  };
}
function startHandlingFocusEvents() {
  return (_dispatch, getState) => {
    window.addEventListener("focusin", handleFocusIn);
    window.addEventListener("focusout", handleFocusOut);
    return () => {
      window.removeEventListener("focusin", handleFocusIn);
      window.removeEventListener("focusout", handleFocusOut);
    };
    function handleFocusIn(event) {
      if (ReactPage.getBuilderEditMode(getState()) === BuilderEditMode.BuilderEditMode.INTERACT) {
        return;
      }
      if (!(event.target instanceof window.HTMLElement) || !event.target.isContentEditable) {
        window.parent.focus();
      }
    }
    function handleFocusOut(event) {
      if (ReactPage.getBuilderEditMode(getState()) === BuilderEditMode.BuilderEditMode.INTERACT) {
        return;
      }
      if (!(event.relatedTarget instanceof window.HTMLElement) || !event.relatedTarget.isContentEditable) {
        window.parent.focus();
      }
    }
  };
}
function startMeasuringDocumentElement() {
  return (dispatch) => {
    let animationFrameHandle = requestAnimationFrame(handleAnimationFrameRequest);
    let lastSize;
    return () => {
      cancelAnimationFrame(animationFrameHandle);
    };
    function handleAnimationFrameRequest() {
      const nextSize = getElementSize(window.document.documentElement);
      if (!deepEqual(lastSize, nextSize)) {
        lastSize = nextSize;
        dispatch(Builder.changeDocumentElementSize(nextSize));
      }
      animationFrameHandle = requestAnimationFrame(handleAnimationFrameRequest);
    }
  };
}
function elementKeysFromElementFromPoint(elementFromPoint) {
  return (_dispatch, getState) => {
    if (elementFromPoint == null)
      return null;
    const elementImperativeHandles = getElementImperativeHandlesContainingElement(
      getState(),
      elementFromPoint
    );
    const ascendingDepthDocumentKeys = ReactPage.getDocumentKeysSortedByDepth(getState());
    const descendingDepthDocumentKeys = ascendingDepthDocumentKeys.slice().reverse();
    let currentElement = elementFromPoint;
    let keys = null;
    while (currentElement != null) {
      for (const documentKey of descendingDepthDocumentKeys) {
        const byElementKey = elementImperativeHandles.get(documentKey);
        if (byElementKey == null)
          continue;
        for (const [elementKey, elementImperativeHandle] of byElementKey) {
          if (elementImperativeHandle.getDomNode() === currentElement) {
            return { documentKey, elementKey };
          }
        }
      }
      currentElement = currentElement.parentElement;
    }
    return keys;
  };
}
function startPollingElementFromPoint() {
  return (dispatch, getState) => {
    let lastElementFromPoint = null;
    let animationFrameRequestId = requestAnimationFrame(handleAnimationFrameRequest);
    return () => {
      cancelAnimationFrame(animationFrameRequestId);
    };
    function handleAnimationFrameRequest() {
      const pointer = getPointer(getState());
      const elementFromPoint = pointer == null ? null : document.elementFromPoint(pointer.x, pointer.y);
      if (elementFromPoint !== lastElementFromPoint) {
        lastElementFromPoint = elementFromPoint;
        const keys = dispatch(elementKeysFromElementFromPoint(elementFromPoint));
        dispatch(Builder.elementFromPointChange(keys));
      }
      animationFrameRequestId = requestAnimationFrame(handleAnimationFrameRequest);
    }
  };
}
function registerBuilderComponents() {
  return (dispatch, getState) => {
    const state = getState();
    const componentsMeta = getComponentsMeta(state);
    componentsMeta.forEach((meta, type) => {
      const descriptors = getComponentPropControllerDescriptors(state, type);
      if (descriptors != null) {
        const [serializedControls, transferables] = serializeControls(descriptors);
        dispatch(
          Builder.registerBuilderComponent({ type, meta, serializedControls }, transferables)
        );
      }
    });
    return () => {
      componentsMeta.forEach((_, type) => {
        dispatch(Builder.unregisterBuilderComponent({ type }));
      });
    };
  };
}
function registerBuilderDocuments() {
  return (dispatch, getState) => {
    const documents = Documents.getDocuments(getDocumentsStateSlice(getState()));
    documents.forEach((document2) => {
      dispatch(Builder.registerBuilderDocument(document2));
    });
    return () => {
      documents.forEach((_document, documentKey) => {
        dispatch(Builder.unregisterBuilderDocument(documentKey));
      });
    };
  };
}
function initialize(builderProxy) {
  return (dispatch, getState) => {
    const unregisterBuilderDocuments = dispatch(registerBuilderDocuments());
    const stopMeasuringElements = dispatch(startMeasuringElements());
    const stopMeasuringDocumentElement = dispatch(startMeasuringDocumentElement());
    const stopHandlingFocusEvent = dispatch(startHandlingFocusEvents());
    const unlockDocumentScroll = dispatch(lockDocumentScroll());
    const stopHandlingPointerMoveEvent = dispatch(startHandlingPointerMoveEvent());
    const stopPollingElementFromPoint = dispatch(startPollingElementFromPoint());
    const unregisterBuilderComponents = dispatch(registerBuilderComponents());
    const breakpoints = ReactPage.getBreakpoints(getState());
    dispatch(Shared.setBreakpoints(breakpoints));
    dispatch(Internal.setIsInBuilder(true));
    builderProxy.dispatchBuffered();
    return () => {
      unregisterBuilderDocuments();
      stopMeasuringElements();
      stopMeasuringDocumentElement();
      stopHandlingFocusEvent();
      unlockDocumentScroll();
      stopHandlingPointerMoveEvent();
      stopPollingElementFromPoint();
      unregisterBuilderComponents();
      dispatch(Internal.setIsInBuilder(false));
    };
  };
}
function measureBoxModelsMiddleware() {
  return actionMiddleware(({ dispatch }) => (next) => {
    return (action) => {
      switch (action.type) {
        case InternalActionTypes.REGISTER_COMPONENT_HANDLE: {
          if (BoxModels.isMeasurable(action.payload.componentHandle)) {
            dispatch(
              Internal.registerMeasurable(
                action.payload.documentKey,
                action.payload.elementKey,
                action.payload.componentHandle
              )
            );
          }
          break;
        }
        case InternalActionTypes.UNREGISTER_COMPONENT_HANDLE:
          dispatch(
            Internal.unregisterMeasurable(action.payload.documentKey, action.payload.elementKey)
          );
          break;
      }
      return next(action);
    };
  });
}
function builderAPIMiddleware(builderProxy) {
  return actionMiddleware(({ dispatch }) => (next) => {
    if (typeof window === "undefined")
      return (action) => next(action);
    let cleanUp = () => {
    };
    return (action) => {
      switch (action.type) {
        case BuilderActionTypes.CHANGE_ELEMENT_BOX_MODELS:
        case BuilderActionTypes.MOUNT_COMPONENT:
        case BuilderActionTypes.UNMOUNT_COMPONENT:
        case BuilderActionTypes.CHANGE_DOCUMENT_ELEMENT_SIZE:
        case BuilderActionTypes.MESSAGE_BUILDER_PROP_CONTROLLER:
        case BuilderActionTypes.HANDLE_WHEEL:
        case BuilderActionTypes.HANDLE_POINTER_MOVE:
        case BuilderActionTypes.ELEMENT_FROM_POINT_CHANGE:
        case BuilderActionTypes.SET_LOCALE:
        case BuilderActionTypes.SET_BREAKPOINTS:
        case BuilderActionTypes.REGISTER_BUILDER_DOCUMENT:
        case BuilderActionTypes.UNREGISTER_BUILDER_DOCUMENT:
        case BuilderActionTypes.REGISTER_BUILDER_COMPONENT:
        case BuilderActionTypes.UNREGISTER_BUILDER_COMPONENT:
          builderProxy.execute(action);
          break;
        case HostActionTypes.CHANGE_DOCUMENT_ELEMENT_SCROLL_TOP:
          window.document.documentElement.scrollTop = action.payload.scrollTop;
          break;
        case HostActionTypes.SCROLL_DOCUMENT_ELEMENT:
          window.document.documentElement.scrollTop += action.payload.scrollTopDelta;
          break;
        case HostActionTypes.SET_BUILDER_EDIT_MODE:
          window.getSelection()?.removeAllRanges();
          break;
        case HostActionTypes.INIT:
          cleanUp = dispatch(initialize(builderProxy));
          break;
        case HostActionTypes.CLEAN_UP:
          cleanUp();
          break;
      }
      return next(action);
    };
  });
}
function createAndRegisterPropControllers(documentKey, elementKey) {
  return (dispatch, getState) => {
    const descriptors = ReactPage.getElementPropControllerDescriptors(
      getState(),
      documentKey,
      elementKey
    );
    if (descriptors == null)
      return null;
    const propControllers = Object.entries(descriptors).reduce(
      (acc, [propName, descriptor]) => {
        const propController = createPropController(
          descriptor,
          (message) => dispatch(
            Builder.messageBuilderPropController(documentKey, elementKey, propName, message)
          )
        );
        return { ...acc, [propName]: propController };
      },
      {}
    );
    dispatch(Internal.registerPropControllers(documentKey, elementKey, propControllers));
    return propControllers;
  };
}
function propControllerHandlesMiddleware() {
  return actionMiddleware(({ dispatch, getState }) => (next) => {
    return (action) => {
      switch (action.type) {
        case InternalActionTypes.REGISTER_COMPONENT_HANDLE: {
          const { documentKey, elementKey, componentHandle } = action.payload;
          const element = ReactPage.getElement(getState(), documentKey, elementKey);
          const propControllers = dispatch(
            createAndRegisterPropControllers(documentKey, elementKey)
          );
          if (element != null && !ReactPage.isElementReference(element) && PropControllerHandles.isPropControllersHandle(componentHandle)) {
            dispatch(
              Internal.registerPropControllersHandle(documentKey, elementKey, componentHandle)
            );
            componentHandle.setPropControllers(propControllers);
          }
          break;
        }
        case InternalActionTypes.UNREGISTER_COMPONENT_HANDLE: {
          const { documentKey, elementKey } = action.payload;
          const handle = PropControllerHandles.getPropControllersHandle(
            getPropControllerHandlesStateSlice(getState()),
            documentKey,
            elementKey
          );
          handle?.setPropControllers(null);
          dispatch(Internal.unregisterPropControllers(documentKey, elementKey));
          break;
        }
        case HostActionTypes.MESSAGE_HOST_PROP_CONTROLLER: {
          const propController = PropControllerHandles.getPropController(
            getPropControllerHandlesStateSlice(getState()),
            action.payload.documentKey,
            action.payload.elementKey,
            action.payload.propName
          );
          if (propController)
            propController.recv(action.payload.message);
        }
      }
      return next(action);
    };
  });
}
function makeswiftApiClientSyncMiddleware(client) {
  return actionMiddleware(() => (next) => {
    return (action) => {
      client.makeswiftApiClient.dispatch(action);
      return next(action);
    };
  });
}
function setupBuilderProxy(builderProxy) {
  return (dispatch) => {
    builderProxy.setup({ onHostAction: (action) => dispatch(action) });
  };
}
function configureStore({
  preloadedState,
  client,
  builderProxy
}) {
  const initialState = {
    ...preloadedState,
    isPreview: IsPreview.getInitialState(true)
  };
  const store = configureReduxStore({
    reducer,
    preloadedState: initialState,
    middleware: (getDefaultMiddleware) => getDefaultMiddleware(middlewareOptions).concat(
      ReactPage.elementTreeMiddleware(),
      measureBoxModelsMiddleware(),
      builderAPIMiddleware(builderProxy),
      propControllerHandlesMiddleware(),
      makeswiftApiClientSyncMiddleware(client)
    ),
    enhancers: (getDefaultEnhancers) => getDefaultEnhancers().concat(
      withSetupTeardown(
        () => {
          const dispatch = store.dispatch;
          dispatch(setupBuilderProxy(builderProxy));
        },
        () => builderProxy.teardown()
      )
    ),
    devTools: devToolsConfig({
      name: `Host store (${(/* @__PURE__ */ new Date()).toISOString()})`,
      actionsDenylist: [
        HostActionTypes.BUILDER_POINTER_MOVE,
        BuilderActionTypes.HANDLE_POINTER_MOVE,
        BuilderActionTypes.ELEMENT_FROM_POINT_CHANGE
      ]
    })
  });
  return store;
}
export {
  builderAPIMiddleware,
  configureStore,
  createBox,
  getBox,
  initialize,
  parse,
  propControllerHandlesMiddleware,
  reducer,
  startMeasuringElements
};
//# sourceMappingURL=react-builder-preview.js.map