"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var element_trees_exports = {};
__export(element_trees_exports, {
  buildElementTree: () => buildElementTree,
  getChangedElementsPaths: () => getChangedElementsPaths,
  getElement: () => getElement,
  getElementId: () => getElementId,
  getElementIds: () => getElementIds,
  getElements: () => getElements,
  getInitialState: () => getInitialState,
  reducer: () => reducer,
  traverseElementTree: () => traverseElementTree
});
module.exports = __toCommonJS(element_trees_exports);
var import_immutable = require("immutable");
var import_controls = require("@makeswift/controls");
var Introspection = __toESM(require("../../prop-controllers/introspection"));
var import_actions = require("../actions");
var import_read_only_documents = require("./read-only-documents");
function getInitialState(documents, descriptors) {
  const state = /* @__PURE__ */ new Map();
  if (documents == null || descriptors == null)
    return state;
  for (const [documentKey, document] of documents) {
    state.set(documentKey, buildElementTree((0, import_read_only_documents.getRootElement)(document), descriptors));
  }
  return state;
}
function getElementTree(state, documentKey) {
  return state.get(documentKey) ?? null;
}
function getElements(state, documentKey) {
  return getElementTree(state, documentKey)?.elements ?? /* @__PURE__ */ new Map();
}
function getElementIds(state, documentKey) {
  return getElementTree(state, documentKey)?.elementIds ?? /* @__PURE__ */ new Map();
}
function getElement(state, documentKey, elementKey) {
  return getElements(state, documentKey).get(elementKey) ?? null;
}
function getElementId(state, documentKey, elementKey) {
  return getElementIds(state, documentKey).get(elementKey) ?? null;
}
function reducer(state = getInitialState(), action) {
  if (!(0, import_actions.isKnownAction)(action))
    return state;
  switch (action.type) {
    case import_actions.ActionTypes.CREATE_ELEMENT_TREE: {
      const { document, descriptors } = action.payload;
      return new Map(state).set(
        document.key,
        buildElementTree((0, import_read_only_documents.getRootElement)(document), descriptors)
      );
    }
    case import_actions.ActionTypes.DELETE_ELEMENT_TREE: {
      const nextState = new Map(state);
      const deleted = nextState.delete(action.payload.documentKey);
      return deleted ? nextState : state;
    }
    case import_actions.ActionTypes.CHANGE_ELEMENT_TREE: {
      const { oldDocument, newDocument, descriptors, operation } = action.payload;
      const documentKey = oldDocument.key;
      console.assert(
        documentKey === newDocument.key,
        `Mismatching document keys ${documentKey} !== ${newDocument.key}`
      );
      const elementTree = state.get(documentKey);
      if (elementTree == null)
        return state;
      const updatedElementTree = applyChanges(
        elementTree,
        descriptors,
        {
          old: (0, import_read_only_documents.getRootElement)(oldDocument),
          new: (0, import_read_only_documents.getRootElement)(newDocument)
        },
        operation
      );
      return new Map(state).set(documentKey, updatedElementTree);
    }
    default:
      return state;
  }
}
function* traverseElementTree(element, descriptors) {
  yield element;
  if ((0, import_controls.isElementReference)(element))
    return;
  const elementDescriptors = descriptors.get(element.type);
  if (elementDescriptors == null)
    return;
  for (const [propKey, descriptor] of Object.entries(elementDescriptors)) {
    const children = Introspection.getElementChildren(descriptor, element.props[propKey]);
    for (const child of children) {
      yield* traverseElementTree(child, descriptors);
    }
  }
}
function getElementIdProp(element, descriptors) {
  const elementDescriptors = descriptors.get(element.type);
  if (elementDescriptors == null)
    return null;
  for (const [propName, descriptor] of Object.entries(elementDescriptors)) {
    const elementId = Introspection.getElementId(descriptor, element.props[propName]);
    if (elementId != null)
      return elementId;
  }
  return null;
}
function buildElementTree(rootElement, descriptors) {
  const elements = /* @__PURE__ */ new Map();
  const elementIds = /* @__PURE__ */ new Map();
  for (const element of traverseElementTree(rootElement, descriptors)) {
    elements.set(element.key, element);
    if (!(0, import_controls.isElementReference)(element)) {
      const elementId = getElementIdProp(element, descriptors);
      if (elementId != null)
        elementIds.set(element.key, elementId);
    }
  }
  return {
    elements,
    elementIds
  };
}
function isElement(item) {
  return typeof item === "object" && item != null && "key" in item && "type" in item && typeof item.key === "string" && typeof item.type === "string";
}
function getElementOperationPath(path) {
  if (path.length === 0) {
    return { elementPath: [], propName: null };
  }
  const i = path.findLastIndex(
    (fragment, i2) => typeof fragment === "number" && (i2 === path.length - 1 || path[i2 + 1] === "props")
  );
  if (i === -1 && path[0] !== "props" || path.length - i < 3) {
    console.error("Operation path does not point to an element property", { path });
    return { elementPath: [], propName: null };
  }
  return { elementPath: path.slice(0, i + 1), propName: `${path.slice(i + 1).at(1)}` };
}
function getChangedElementsPaths(path) {
  let elementOp = getElementOperationPath(path);
  const result = [elementOp];
  while (elementOp.elementPath.length > 0) {
    elementOp = getElementOperationPath(elementOp.elementPath.slice(0, -1));
    result.push(elementOp);
  }
  return result;
}
function getElementByPath(rootElement, elementPath) {
  const item = (0, import_immutable.getIn)(rootElement, elementPath);
  if (!isElement(item)) {
    console.error("Expected an element, got", item, {
      rootElement,
      elementPath
    });
    return null;
  }
  return item;
}
function getElementAndPropName(rootElement, { elementPath, propName }) {
  const element = getElementByPath(rootElement, elementPath);
  return element != null ? [element, propName] : [rootElement, null];
}
function updateParentElements(elements, elementPaths, rootElement) {
  elementPaths.forEach(({ elementPath }) => {
    const element = getElementByPath(rootElement, elementPath);
    if (element != null)
      elements.set(element.key, element);
  });
}
function hasChildren(element, propName, descriptors) {
  if ((0, import_controls.isElementReference)(element))
    return false;
  const propDescriptor = descriptors.get(element.type)?.[propName];
  if (propDescriptor == null)
    return false;
  const children = Introspection.getElementChildren(propDescriptor, element.props[propName]);
  return children.length > 0;
}
function deleteElement({ elements, elementIds }, deletedElement, propName, descriptors) {
  if (propName == null || hasChildren(deletedElement, propName, descriptors)) {
    for (const element of traverseElementTree(deletedElement, descriptors)) {
      elements.delete(element.key);
      elementIds.delete(element.key);
    }
  } else {
    elements.delete(deletedElement.key);
    elementIds.delete(deletedElement.key);
  }
}
function applyDelete(elementTree, descriptors, rootElements, path) {
  const [deleteElementPath, ...parentElementPaths] = getChangedElementsPaths(path);
  const [deletedElement, propName] = getElementAndPropName(rootElements.old, deleteElementPath);
  const elements = new Map(elementTree.elements);
  const elementIds = new Map(elementTree.elementIds);
  deleteElement({ elements, elementIds }, deletedElement, propName, descriptors);
  updateParentElements(elements, parentElementPaths, rootElements.new);
  return {
    elements,
    elementIds
  };
}
function insertElement({ elements, elementIds }, insertedElement, propName, descriptors) {
  if (propName == null || hasChildren(insertedElement, propName, descriptors)) {
    for (const element of traverseElementTree(insertedElement, descriptors)) {
      elements.set(element.key, element);
      if (!(0, import_controls.isElementReference)(element)) {
        const elementId = getElementIdProp(element, descriptors);
        if (elementId != null)
          elementIds.set(element.key, elementId);
      }
    }
  } else {
    elements.set(insertedElement.key, insertedElement);
    if (!(0, import_controls.isElementReference)(insertedElement)) {
      const elementId = getElementIdProp(insertedElement, descriptors);
      if (elementId != null)
        elementIds.set(insertedElement.key, elementId);
    }
  }
}
function applyInsert(elementTree, descriptors, rootElements, path) {
  const [insertedElementPath, ...parentElementPaths] = getChangedElementsPaths(path);
  const [insertedElement, propName] = getElementAndPropName(rootElements.new, insertedElementPath);
  const elements = new Map(elementTree.elements);
  const elementIds = new Map(elementTree.elementIds);
  insertElement({ elements, elementIds }, insertedElement, propName, descriptors);
  updateParentElements(elements, parentElementPaths, rootElements.new);
  return {
    elements,
    elementIds
  };
}
function applyUpdate(elementTree, descriptors, rootElements, path) {
  const [updateElementPath, ...parentElementPaths] = getChangedElementsPaths(path);
  const [deletedElement, propName] = getElementAndPropName(rootElements.old, updateElementPath);
  const [insertedElement, _] = getElementAndPropName(rootElements.new, updateElementPath);
  const elements = new Map(elementTree.elements);
  const elementIds = new Map(elementTree.elementIds);
  deleteElement({ elements, elementIds }, deletedElement, propName, descriptors);
  insertElement({ elements, elementIds }, insertedElement, propName, descriptors);
  updateParentElements(elements, parentElementPaths, rootElements.new);
  return {
    elements,
    elementIds
  };
}
function applyChanges(elementTree, descriptors, rootElements, operation) {
  return operation.reduce((tree, op) => {
    const hasDelete = "ld" in op || "od" in op;
    const hasInsert = "li" in op || "oi" in op;
    if (hasDelete && hasInsert) {
      return applyUpdate(tree, descriptors, rootElements, op.p);
    }
    if (hasDelete)
      return applyDelete(tree, descriptors, rootElements, op.p);
    if (hasInsert)
      return applyInsert(tree, descriptors, rootElements, op.p);
    return tree;
  }, elementTree);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  buildElementTree,
  getChangedElementsPaths,
  getElement,
  getElementId,
  getElementIds,
  getElements,
  getInitialState,
  reducer,
  traverseElementTree
});
//# sourceMappingURL=element-trees.js.map