/**
 * This file is part of the NocoBase (R) project.
 * Copyright (c) 2020-2024 NocoBase Co., Ltd.
 * Authors: NocoBase Team.
 *
 * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
 * For more information, please refer to: https://www.nocobase.com/agreement.
 */

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 __decorateClass = (decorators, target, key, kind) => {
  var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
    if (decorator = decorators[i])
      result = (kind ? decorator(target, key, result) : decorator(result)) || result;
  if (kind && result) __defProp(target, key, result);
  return result;
};
var repository_exports = {};
__export(repository_exports, {
  FlowModelRepository: () => FlowModelRepository,
  default: () => repository_default,
  transaction: () => transaction
});
module.exports = __toCommonJS(repository_exports);
var import_database = require("@nocobase/database");
var import_utils = require("@nocobase/utils");
var import_lodash = __toESM(require("lodash"));
const nodeKeys = ["properties", "definitions", "patternProperties", "additionalProperties", "items"];
function transaction(transactionAbleArgPosition) {
  return (target, propertyKey, descriptor) => {
    const originalMethod = descriptor.value;
    descriptor.value = async function(...args) {
      if (!import_lodash.default.isNumber(transactionAbleArgPosition)) {
        transactionAbleArgPosition = originalMethod.length - 1;
      }
      let transaction2 = import_lodash.default.get(args, [transactionAbleArgPosition, "transaction"]);
      let handleTransaction = false;
      if (!transaction2) {
        transaction2 = await this.database.sequelize.transaction();
        handleTransaction = true;
        import_lodash.default.set(args, transactionAbleArgPosition, {
          ...import_lodash.default.get(args, transactionAbleArgPosition, {}),
          transaction: transaction2
        });
      }
      if (handleTransaction) {
        try {
          const results = await originalMethod.apply(this, args);
          await transaction2.commit();
          return results;
        } catch (e) {
          await transaction2.rollback();
          throw e;
        }
      } else {
        return await originalMethod.apply(this, args);
      }
    };
    return descriptor;
  };
}
const _FlowModelRepository = class _FlowModelRepository extends import_database.Repository {
  cache;
  get flowModelsTableName() {
    return this.tableNameAdapter(this.model.tableName);
  }
  get flowModelTreePathTableName() {
    const model = this.database.getCollection("flowModelTreePath").model;
    return this.tableNameAdapter(model.tableName);
  }
  static schemaToSingleNodes(schema, carry = [], childOptions = null) {
    const node = import_lodash.default.cloneDeep(
      import_lodash.default.isString(schema) ? {
        uid: schema
      } : schema
    );
    if (!import_lodash.default.get(node, "name")) {
      node.name = (0, import_utils.uid)();
    }
    if (!import_lodash.default.get(node, "uid")) {
      node["uid"] = (0, import_utils.uid)();
    }
    if (childOptions) {
      node.childOptions = childOptions;
    }
    carry.push(node);
    for (const nodeKey of nodeKeys) {
      const nodeProperty = import_lodash.default.get(node, nodeKey);
      const childNodeChildOptions = {
        parentUid: node["uid"],
        parentPath: [node["uid"], ...import_lodash.default.get(childOptions, "parentPath", [])],
        type: nodeKey
      };
      if (nodeKey === "items" && nodeProperty) {
        const handleItems = import_lodash.default.isArray(nodeProperty) ? nodeProperty : [nodeProperty];
        for (const [i, item] of handleItems.entries()) {
          carry = this.schemaToSingleNodes(item, carry, { ...childNodeChildOptions, sort: i + 1 });
        }
      } else if (import_lodash.default.isPlainObject(nodeProperty)) {
        const subNodeNames = import_lodash.default.keys(import_lodash.default.get(node, nodeKey));
        delete node[nodeKey];
        for (const [i, subNodeName] of subNodeNames.entries()) {
          const subSchema = {
            name: subNodeName,
            ...import_lodash.default.get(nodeProperty, subNodeName)
          };
          carry = this.schemaToSingleNodes(subSchema, carry, { ...childNodeChildOptions, sort: i + 1 });
        }
      }
    }
    return carry;
  }
  // if you need to handle cache in repo method, so you must set cache first
  setCache(cache) {
    this.cache = cache;
  }
  /**
   * clear cache with xUid which in flowModelTreePath's Path
   * @param {string} xUid
   * @param {Transaction} transaction
   * @returns {Promise<void>}
   */
  async clearXUidPathCache(xUid, transaction2) {
    if (!this.cache || !xUid) {
      return;
    }
    const uiSchemaNodes = await this.database.getRepository("flowModelTreePath").find({
      filter: {
        descendant: xUid
      },
      transaction: transaction2
    });
    for (const uiSchemaNode of uiSchemaNodes) {
      await this.cache.del(`p_${uiSchemaNode["ancestor"]}`);
      await this.cache.del(`s_${uiSchemaNode["ancestor"]}`);
    }
  }
  tableNameAdapter(tableName) {
    if (this.database.sequelize.getDialect() === "postgres") {
      return `"${this.database.options.schema || "public"}"."${tableName}"`;
    }
    return tableName;
  }
  sqlAdapter(sql) {
    if (this.database.isMySQLCompatibleDialect()) {
      return import_lodash.default.replace(sql, /"/g, "`");
    }
    return sql;
  }
  async getProperties(uid2, options = {}) {
    if ((options == null ? void 0 : options.readFromCache) && this.cache) {
      return this.cache.wrap(`p_${uid2}`, () => {
        return this.doGetProperties(uid2, options);
      });
    }
    return this.doGetProperties(uid2, options);
  }
  async getParentJsonSchema(uid2, options = {}) {
    const parentUid = await this.findParentUid(uid2, options.transaction);
    if (!parentUid) {
      return null;
    }
    return this.getJsonSchema(parentUid, options);
  }
  async getParentProperty(uid2, options = {}) {
    const parentUid = await this.findParentUid(uid2, options.transaction);
    if (!parentUid) {
      return null;
    }
    return this.getJsonSchema(parentUid, options);
  }
  async getJsonSchema(uid2, options) {
    if ((options == null ? void 0 : options.readFromCache) && this.cache) {
      return this.cache.wrap(`s_${uid2}`, () => {
        return this.doGetJsonSchema(uid2, options);
      });
    }
    return this.doGetJsonSchema(uid2, options);
  }
  static optionsToJson(options) {
    return import_lodash.default.isPlainObject(options) ? options : JSON.parse(options);
  }
  static getSortNumber(value) {
    if (typeof value === "number" && Number.isFinite(value)) {
      return value;
    }
    if (typeof value === "string" && value.trim()) {
      const parsed = Number(value);
      if (Number.isFinite(parsed)) {
        return parsed;
      }
    }
    return null;
  }
  static compareBySortAndUid(a, b) {
    const aSort = _FlowModelRepository.getSortNumber(a.sort);
    const bSort = _FlowModelRepository.getSortNumber(b.sort);
    if (aSort !== null && bSort !== null && aSort !== bSort) {
      return aSort - bSort;
    }
    if (aSort !== null && bSort === null) {
      return -1;
    }
    if (aSort === null && bSort !== null) {
      return 1;
    }
    return String(a.uid || "").localeCompare(String(b.uid || ""));
  }
  nodesToSchema(nodes, rootUid) {
    const nodeAttributeSanitize = (node) => {
      const schema = {
        ...this.ignoreSchemaProperties(import_lodash.default.isPlainObject(node.options) ? node.options : JSON.parse(node.options)),
        ...import_lodash.default.pick(node, [...nodeKeys, "name"]),
        ["uid"]: node["uid"],
        ["x-async"]: !!node.async
      };
      const sort = _FlowModelRepository.getSortNumber(node.sort);
      if (sort !== null) {
        schema["x-index"] = sort;
      }
      return schema;
    };
    const buildTree = (rootNode) => {
      if (!rootNode) {
        return null;
      }
      const children = nodes.filter((node) => node.parent == rootNode["uid"]);
      if (children.length > 0) {
        const childrenGroupByType = import_lodash.default.groupBy(children, "type");
        for (const childType of Object.keys(childrenGroupByType)) {
          const properties = childrenGroupByType[childType].map((child) => buildTree(child)).sort(
            (a, b) => _FlowModelRepository.compareBySortAndUid(
              { sort: a["x-index"], uid: a.uid },
              { sort: b["x-index"], uid: b.uid }
            )
          );
          rootNode[childType] = childType == "items" ? properties.length == 1 ? properties[0] : properties : properties.reduce((carry, item) => {
            carry[item.name] = item;
            delete item["name"];
            return carry;
          }, {});
        }
      }
      return nodeAttributeSanitize(rootNode);
    };
    return buildTree(nodes.find((node) => node["uid"] == rootUid));
  }
  async clearAncestor(uid2, options) {
    await this.clearXUidPathCache(uid2, options == null ? void 0 : options.transaction);
    const db = this.database;
    const treeTable = this.flowModelTreePathTableName;
    await db.sequelize.query(
      `DELETE
       FROM ${treeTable}
       WHERE descendant IN
             (SELECT descendant FROM (SELECT descendant FROM ${treeTable} WHERE ancestor = :uid) as descendantTable)
         AND ancestor IN (SELECT ancestor
                          FROM (SELECT ancestor FROM ${treeTable} WHERE descendant = :uid AND ancestor != descendant) as ancestorTable)
      `,
      {
        type: "DELETE",
        replacements: {
          uid: uid2
        },
        transaction: options.transaction
      }
    );
  }
  async emitAfterSaveEvent(s, options) {
    if (!(s == null ? void 0 : s.options)) {
      return;
    }
    const keys = [
      "title",
      "description",
      "x-component-props.title",
      "x-decorator-props.title",
      "x-component-props.content"
    ];
    let r = false;
    for (const key of keys) {
      if (import_lodash.default.get(s == null ? void 0 : s.options, key)) {
        r = true;
        break;
      }
    }
    if (r) {
      await this.database.emitAsync(`${this.collection.name}.afterSave`, s, options);
    }
  }
  async patch(newSchema, options) {
    const { transaction: transaction2 } = options;
    const rootUid = newSchema["uid"];
    await this.clearXUidPathCache(rootUid, transaction2);
    if (!newSchema["properties"]) {
      const s = await this.model.findByPk(rootUid, { transaction: transaction2 });
      s.set("options", {
        ...import_lodash.default.omit(_FlowModelRepository.optionsToJson(s.get("options") || {}), ["uid"]),
        ...import_lodash.default.omit(newSchema, ["uid", "name", "options"])
      });
      await s.save({ transaction: transaction2, hooks: false });
      await this.emitAfterSaveEvent(s, options);
      if (newSchema["x-server-hooks"]) {
        await this.database.emitAsync(`${this.collection.name}.afterSave`, s, options);
      }
      return;
    }
    const oldTree = await this.getJsonSchema(rootUid, { transaction: transaction2 });
    const traverSchemaTree = async (schema, path = []) => {
      const node = schema;
      const oldNode = path.length == 0 ? oldTree : import_lodash.default.get(oldTree, path);
      const oldNodeUid = oldNode["uid"];
      await this.updateNode(oldNodeUid, node, transaction2);
      const properties = node.properties;
      if (import_lodash.default.isPlainObject(properties)) {
        for (const name of Object.keys(properties)) {
          await traverSchemaTree(properties[name], [...path, "properties", name]);
        }
      }
    };
    await traverSchemaTree(newSchema);
  }
  async initializeActionContext(newSchema, options = {}) {
    if (!newSchema["uid"] || !newSchema["x-action-context"]) {
      return;
    }
    const { transaction: transaction2 } = options;
    const nodeModel = await this.findOne({
      filter: {
        uid: newSchema["uid"]
      },
      transaction: transaction2
    });
    if (!import_lodash.default.isEmpty(nodeModel == null ? void 0 : nodeModel.get("options")["x-action-context"])) {
      return;
    }
    return this.patch(import_lodash.default.pick(newSchema, ["uid", "x-action-context"]), options);
  }
  async batchPatch(schemas, options) {
    const { transaction: transaction2 } = options;
    for (const schema of schemas) {
      await this.patch(schema, { ...options, transaction: transaction2 });
    }
  }
  async removeEmptyParents(options) {
    const { transaction: transaction2, uid: uid2, breakRemoveOn } = options;
    await this.clearXUidPathCache(uid2, transaction2);
    const removeParent = async (nodeUid) => {
      const parent = await this.isSingleChild(nodeUid, transaction2);
      if (parent && !this.breakOnMatched(parent, breakRemoveOn)) {
        await removeParent(parent.get("uid"));
      } else {
        await this.remove(nodeUid, { transaction: transaction2 });
      }
    };
    await removeParent(uid2);
  }
  async recursivelyRemoveIfNoChildren(options) {
    const { uid: uid2, transaction: transaction2, breakRemoveOn } = options;
    await this.clearXUidPathCache(uid2, transaction2);
    const removeLeafNode = async (nodeUid) => {
      const isLeafNode = await this.isLeafNode(nodeUid, transaction2);
      if (isLeafNode) {
        const { parentUid, schema } = await this.findNodeSchemaWithParent(nodeUid, transaction2);
        if (this.breakOnMatched(schema, breakRemoveOn)) {
          return;
        } else {
          await this.remove(nodeUid, {
            transaction: transaction2
          });
          await removeLeafNode(parentUid);
        }
      }
    };
    await removeLeafNode(uid2);
  }
  async remove(uid2, options) {
    const { transaction: transaction2 } = options;
    await this.clearXUidPathCache(uid2, transaction2);
    if (options == null ? void 0 : options.removeParentsIfNoChildren) {
      await this.removeEmptyParents({ transaction: transaction2, uid: uid2, breakRemoveOn: options.breakRemoveOn });
      return;
    }
    const descendants = await this.database.sequelize.query(
      this.sqlAdapter(`SELECT descendant FROM ${this.flowModelTreePathTableName} WHERE ancestor = :uid`),
      {
        type: "SELECT",
        replacements: {
          uid: uid2
        },
        transaction: transaction2
      }
    );
    const uids = descendants.map((row) => row.descendant).filter(Boolean);
    if (uids.length) {
      await this.database.emitAsync(
        `${this.collection.name}.beforeRemoveTree`,
        {
          rootUid: uid2,
          uids
        },
        options
      );
    }
    await this.database.sequelize.query(
      this.sqlAdapter(`DELETE FROM ${this.flowModelsTableName} WHERE "uid" IN (
            SELECT descendant FROM ${this.flowModelTreePathTableName} WHERE ancestor = :uid
        )`),
      {
        replacements: {
          uid: uid2
        },
        transaction: transaction2
      }
    );
    await this.database.sequelize.query(
      ` DELETE FROM ${this.flowModelTreePathTableName}
            WHERE descendant IN (
                select descendant FROM
                    (SELECT descendant
                     FROM ${this.flowModelTreePathTableName}
                     WHERE ancestor = :uid)as descendantTable) `,
      {
        replacements: {
          uid: uid2
        },
        transaction: transaction2
      }
    );
  }
  async insertAdjacent(position, target, schema, options) {
    const { transaction: transaction2 } = options;
    await this.clearXUidPathCache(schema["uid"], transaction2);
    if (options.wrap) {
      const wrapSchemaNodes = await this.insertNewSchema(options.wrap, {
        transaction: transaction2,
        returnNode: true
      });
      const lastWrapNode = wrapSchemaNodes[wrapSchemaNodes.length - 1];
      await this.insertAdjacent("afterBegin", lastWrapNode["uid"], schema, import_lodash.default.omit(options, "wrap"));
      schema = wrapSchemaNodes[0]["uid"];
      options.removeParentsIfNoChildren = false;
    } else {
      const schemaExists = await this.schemaExists(schema, { transaction: transaction2 });
      if (schemaExists) {
        schema = import_lodash.default.isString(schema) ? schema : schema["uid"];
      } else {
        const insertedSchema = await this.insertNewSchema(schema, {
          transaction: transaction2,
          returnNode: true
        });
        schema = insertedSchema[0]["uid"];
      }
    }
    const result = await this[`insert${import_lodash.default.upperFirst(position)}`](target, schema, options);
    const s = await this.model.findByPk(schema, { transaction: transaction2 });
    await this.emitAfterSaveEvent(s, options);
    await this.clearXUidPathCache(result["uid"], transaction2);
    return result;
  }
  async duplicate(modelUid, options) {
    let nodes = await this.findNodesById(modelUid, { ...options, includeAsyncNode: true });
    if (!(nodes == null ? void 0 : nodes.length)) {
      return null;
    }
    nodes = this.dedupeNodesForDuplicate(nodes, modelUid);
    const uidMap = {};
    for (const n of nodes) {
      uidMap[n["uid"]] = (0, import_utils.uid)();
    }
    const sorted = [...nodes].sort((a, b) => {
      if (a.depth !== b.depth) return a.depth - b.depth;
      const ap = a.parent || "";
      const bp = b.parent || "";
      if (ap !== bp) return ap < bp ? -1 : 1;
      const at = a.type || "";
      const bt = b.type || "";
      if (at !== bt) return at < bt ? -1 : 1;
      const as = a.sort ?? 0;
      const bs = b.sort ?? 0;
      return as - bs;
    });
    for (const n of sorted) {
      const oldUid = n["uid"];
      const newUid = uidMap[oldUid];
      const oldParentUid = n["parent"];
      const newParentUid = uidMap[oldParentUid] ?? null;
      const optionsObj = this.replaceStepParamsModelUids(
        import_lodash.default.cloneDeep(_FlowModelRepository.optionsToJson(n.options)),
        uidMap
      );
      delete optionsObj.uid;
      delete optionsObj.name;
      delete optionsObj.childOptions;
      delete optionsObj["x-async"];
      if (newParentUid) {
        optionsObj.parent = newParentUid;
        optionsObj.parentId = newParentUid;
      } else {
        delete optionsObj.parent;
        delete optionsObj.parentId;
      }
      const schemaNode = {
        ...optionsObj,
        uid: newUid,
        name: newUid,
        ["x-async"]: !!n.async
      };
      if (newParentUid) {
        schemaNode.childOptions = {
          parentUid: newParentUid,
          type: n.type,
          position: "last"
        };
      }
      await this.insertSingleNode(schemaNode, { transaction: options == null ? void 0 : options.transaction });
    }
    return this.findModelById(uidMap[modelUid], { ...options });
  }
  dedupeNodesForDuplicate(nodes, rootUid) {
    if (!Array.isArray(nodes) || nodes.length <= 1) {
      return nodes;
    }
    const rowsByUid = import_lodash.default.groupBy(nodes, "uid");
    const uniqueUids = Object.keys(rowsByUid);
    if (uniqueUids.length === nodes.length) {
      return nodes;
    }
    const uidsInSubtree = new Set(uniqueUids);
    const rootDepthByUid = /* @__PURE__ */ new Map();
    for (const uid2 of uniqueUids) {
      const rows = rowsByUid[uid2] || [];
      const depths = rows.map((row) => Number((row == null ? void 0 : row.depth) ?? 0));
      rootDepthByUid.set(uid2, depths.length ? Math.min(...depths) : 0);
    }
    const pickRowForUid = (uid2, rows) => {
      if (!(rows == null ? void 0 : rows.length)) return null;
      if (rows.length === 1) return rows[0];
      if (uid2 === rootUid) return rows[0];
      let bestRow = rows[0];
      let bestParentRootDepth = -1;
      for (const row of rows) {
        const parentUid = row == null ? void 0 : row.parent;
        if (!parentUid || !uidsInSubtree.has(parentUid)) {
          continue;
        }
        const parentRootDepth = rootDepthByUid.get(parentUid) ?? -1;
        if (parentRootDepth > bestParentRootDepth) {
          bestParentRootDepth = parentRootDepth;
          bestRow = row;
        }
      }
      return bestRow;
    };
    const uidsInQueryOrder = [];
    const seenUidsInQueryOrder = /* @__PURE__ */ new Set();
    for (const row of nodes) {
      const uid2 = row == null ? void 0 : row.uid;
      if (!uid2 || seenUidsInQueryOrder.has(uid2)) continue;
      seenUidsInQueryOrder.add(uid2);
      uidsInQueryOrder.push(uid2);
    }
    return uidsInQueryOrder.map((uid2) => pickRowForUid(uid2, rowsByUid[uid2])).filter(Boolean);
  }
  replaceStepParamsModelUids(options, uidMap) {
    const opts = options && typeof options === "object" ? options : {};
    const replaceUidString = (v) => typeof v === "string" && uidMap[v] ? uidMap[v] : v;
    const replaceInPlace = (val) => {
      if (Array.isArray(val)) {
        for (let i = 0; i < val.length; i++) {
          val[i] = replaceInPlace(val[i]);
        }
        return val;
      }
      if (val && typeof val === "object") {
        for (const k of Object.keys(val)) {
          val[k] = replaceInPlace(val[k]);
        }
        return val;
      }
      return replaceUidString(val);
    };
    if (opts.stepParams) opts.stepParams = replaceInPlace(opts.stepParams);
    return opts;
  }
  async insert(schema, options) {
    const nodes = _FlowModelRepository.schemaToSingleNodes(schema);
    const insertedNodes = await this.insertNodes(nodes, options);
    const result = await this.getJsonSchema(insertedNodes[0].get("uid"), {
      transaction: options == null ? void 0 : options.transaction
    });
    await this.clearXUidPathCache(result["uid"], options == null ? void 0 : options.transaction);
    return result;
  }
  async insertNewSchema(schema, options) {
    const { transaction: transaction2 } = options;
    const nodes = _FlowModelRepository.schemaToSingleNodes(schema);
    await this.database.sequelize.query(
      this.sqlAdapter(
        `INSERT INTO ${this.flowModelsTableName} ("uid", "name", "options") VALUES ${nodes.map((n) => "(?)").join(",")};`
      ),
      {
        replacements: import_lodash.default.cloneDeep(nodes).map((node) => {
          const { uid: uid2, name } = this.prepareSingleNodeForInsert(node);
          return [uid2, name, JSON.stringify(node)];
        }),
        type: "insert",
        transaction: transaction2
      }
    );
    const treePathData = import_lodash.default.cloneDeep(nodes).reduce((carry, item) => {
      const { uid: uid2, childOptions, async } = this.prepareSingleNodeForInsert(item);
      return [
        ...carry,
        // self reference
        [uid2, uid2, 0, (childOptions == null ? void 0 : childOptions.type) || null, async, null],
        // parent references
        ...import_lodash.default.get(childOptions, "parentPath", []).map((parentUid, index) => {
          return [parentUid, uid2, index + 1, null, null, childOptions.sort];
        })
      ];
    }, []);
    await this.database.sequelize.query(
      this.sqlAdapter(
        `INSERT INTO ${this.flowModelTreePathTableName} (ancestor, descendant, depth, type, async, sort) VALUES ${treePathData.map((item) => "(?)").join(",")}`
      ),
      {
        replacements: treePathData,
        type: "insert",
        transaction: transaction2
      }
    );
    const rootNode = nodes[0];
    if (rootNode["x-server-hooks"]) {
      const rootModel = await this.findOne({ filter: { uid: rootNode["uid"] }, transaction: transaction2 });
      await this.database.emitAsync(`${this.collection.name}.afterCreateWithAssociations`, rootModel, options);
      await this.database.emitAsync(`${this.collection.name}.afterSave`, rootModel, options);
    }
    if (options == null ? void 0 : options.returnNode) {
      return nodes;
    }
    const result = await this.getJsonSchema(nodes[0]["uid"], {
      transaction: transaction2
    });
    await this.clearXUidPathCache(result["uid"], transaction2);
    return result;
  }
  async insertSingleNode(schema, options) {
    const { transaction: transaction2 } = options;
    const db = this.database;
    const { uid: uid2, name, async, childOptions } = this.prepareSingleNodeForInsert(schema);
    let savedNode;
    const existsNode = await this.findOne({
      filter: {
        uid: uid2
      },
      transaction: transaction2
    });
    const treeTable = this.flowModelTreePathTableName;
    if (existsNode) {
      savedNode = existsNode;
    } else {
      savedNode = await this.insertSchemaRecord(name, uid2, schema, transaction2);
    }
    if (childOptions) {
      const oldParentUid = await this.findParentUid(uid2, transaction2);
      const parentUid = childOptions.parentUid;
      const childrenCount = await this.childrenCount(uid2, transaction2);
      const isTree = childrenCount > 0;
      if (isTree) {
        await this.clearAncestor(uid2, { transaction: transaction2 });
        await db.sequelize.query(
          `INSERT INTO ${treeTable} (ancestor, descendant, depth)
           SELECT supertree.ancestor, subtree.descendant, supertree.depth + subtree.depth + 1
           FROM ${treeTable} AS supertree
                    CROSS JOIN ${treeTable} AS subtree
           WHERE supertree.descendant = :parentUid
             AND subtree.ancestor = :uid;`,
          {
            type: "INSERT",
            replacements: {
              uid: uid2,
              parentUid
            },
            transaction: transaction2
          }
        );
      }
      await db.sequelize.query(
        `UPDATE ${treeTable} SET type = :type WHERE depth = 0 AND ancestor = :uid AND descendant = :uid`,
        {
          type: "update",
          transaction: transaction2,
          replacements: {
            type: childOptions.type,
            uid: uid2
          }
        }
      );
      if (!isTree) {
        if (existsNode) {
          await db.sequelize.query(`DELETE FROM ${treeTable} WHERE descendant = :uid AND ancestor != descendant`, {
            type: "DELETE",
            replacements: {
              uid: uid2
            },
            transaction: transaction2
          });
        }
        await db.sequelize.query(
          `INSERT INTO ${treeTable} (ancestor, descendant, depth)
           SELECT t.ancestor, :modelKey, depth + 1 FROM ${treeTable} AS t  WHERE t.descendant = :modelParentKey `,
          {
            type: "INSERT",
            transaction: transaction2,
            replacements: {
              modelKey: savedNode.get("uid"),
              modelParentKey: parentUid
            }
          }
        );
      }
      if (!existsNode) {
        await db.sequelize.query(
          `INSERT INTO ${treeTable}(ancestor, descendant, depth, type, async) VALUES (:modelKey, :modelKey, 0, :type, :async )`,
          {
            type: "INSERT",
            replacements: {
              modelKey: savedNode.get("uid"),
              type: childOptions.type,
              async
            },
            transaction: transaction2
          }
        );
      }
      const nodePosition = childOptions.position || "last";
      let sort;
      if (nodePosition === "first") {
        sort = 1;
        let updateSql2 = `UPDATE ${treeTable} as TreeTable
                SET sort = TreeTable.sort + 1
                FROM ${treeTable} as NodeInfo
                WHERE NodeInfo.descendant = TreeTable.descendant and NodeInfo.depth = 0
                AND TreeTable.depth = 1 AND TreeTable.ancestor = :ancestor and NodeInfo.type = :type`;
        if (this.database.isMySQLCompatibleDialect()) {
          updateSql2 = `UPDATE ${treeTable} as TreeTable
          JOIN ${treeTable} as NodeInfo ON (NodeInfo.descendant = TreeTable.descendant and NodeInfo.depth = 0)
          SET TreeTable.sort = TreeTable.sort + 1
          WHERE TreeTable.depth = 1 AND TreeTable.ancestor = :ancestor and NodeInfo.type = :type`;
        }
        await db.sequelize.query(updateSql2, {
          replacements: {
            ancestor: childOptions.parentUid,
            type: childOptions.type
          },
          transaction: transaction2
        });
      }
      if (nodePosition === "last") {
        const maxSort = await db.sequelize.query(
          `SELECT ${this.database.sequelize.getDialect() === "postgres" ? "coalesce" : "ifnull"}(MAX(TreeTable.sort), 0) as maxsort FROM ${treeTable} as TreeTable
                                                        LEFT JOIN ${treeTable} as NodeInfo
                                                                  ON NodeInfo.descendant = TreeTable.descendant and NodeInfo.depth = 0
           WHERE TreeTable.depth = 1 AND TreeTable.ancestor = :ancestor and NodeInfo.type = :type`,
          {
            type: "SELECT",
            replacements: {
              ancestor: childOptions.parentUid,
              type: childOptions.type
            },
            transaction: transaction2
          }
        );
        sort = parseInt(maxSort[0]["maxsort"]) + 1;
      }
      if (import_lodash.default.isPlainObject(nodePosition)) {
        const targetPosition = nodePosition;
        const target = targetPosition.target;
        const targetSort = await db.sequelize.query(
          `SELECT TreeTable.sort  as sort FROM ${treeTable} as TreeTable
                                 LEFT JOIN ${treeTable} as NodeInfo
                                           ON NodeInfo.descendant = TreeTable.descendant and NodeInfo.depth = 0   WHERE TreeTable.depth = 1 AND TreeTable.ancestor = :ancestor AND TreeTable.descendant = :descendant and NodeInfo.type = :type`,
          {
            type: "SELECT",
            replacements: {
              ancestor: childOptions.parentUid,
              descendant: target,
              type: childOptions.type
            },
            transaction: transaction2
          }
        );
        sort = targetSort[0].sort;
        if (targetPosition.type == "after") {
          sort += 1;
        }
        let updateSql2 = `UPDATE ${treeTable} as TreeTable
                         SET sort = TreeTable.sort + 1
                             FROM ${treeTable} as NodeInfo
                         WHERE NodeInfo.descendant = TreeTable.descendant
                           and NodeInfo.depth = 0
                           AND TreeTable.depth = 1
                           AND TreeTable.ancestor = :ancestor
                           and TreeTable.sort >= :sort
                           and NodeInfo.type = :type`;
        if (this.database.isMySQLCompatibleDialect()) {
          updateSql2 = `UPDATE  ${treeTable} as TreeTable
JOIN ${treeTable} as NodeInfo ON (NodeInfo.descendant = TreeTable.descendant and NodeInfo.depth = 0)
SET TreeTable.sort = TreeTable.sort + 1
WHERE TreeTable.depth = 1 AND  TreeTable.ancestor = :ancestor and TreeTable.sort >= :sort and NodeInfo.type = :type`;
        }
        await db.sequelize.query(updateSql2, {
          replacements: {
            ancestor: childOptions.parentUid,
            sort,
            type: childOptions.type
          },
          transaction: transaction2
        });
      }
      const updateSql = `UPDATE ${treeTable} SET sort = :sort WHERE depth = 1 AND ancestor = :ancestor AND descendant = :descendant`;
      await db.sequelize.query(updateSql, {
        type: "UPDATE",
        replacements: {
          ancestor: childOptions.parentUid,
          sort,
          descendant: uid2
        },
        transaction: transaction2
      });
      if (oldParentUid !== null && oldParentUid !== parentUid) {
        if (options.removeParentsIfNoChildren) {
          await this.recursivelyRemoveIfNoChildren({
            transaction: transaction2,
            uid: oldParentUid,
            breakRemoveOn: options.breakRemoveOn
          });
        }
      }
    } else {
      await db.sequelize.query(
        `INSERT INTO ${treeTable}(ancestor, descendant, depth, async) VALUES (:modelKey, :modelKey, 0, :async )`,
        {
          type: "INSERT",
          replacements: {
            modelKey: savedNode.get("uid"),
            async
          },
          transaction: transaction2
        }
      );
    }
    await this.clearXUidPathCache(uid2, transaction2);
    return savedNode;
  }
  async updateNode(uid2, schema, transaction2) {
    const nodeModel = await this.findOne({
      filter: {
        uid: uid2
      }
    });
    await nodeModel.update(
      {
        options: {
          ...import_lodash.default.omit(_FlowModelRepository.optionsToJson(nodeModel.get("options") || {}), ["uid"]),
          ...import_lodash.default.omit(schema, ["x-async", "name", "uid", "properties"])
        }
      },
      {
        hooks: false,
        transaction: transaction2
      }
    );
    await this.emitAfterSaveEvent(nodeModel, { transaction: transaction2 });
    if (schema["x-server-hooks"]) {
      await this.database.emitAsync(`${this.collection.name}.afterSave`, nodeModel, { transaction: transaction2 });
    }
  }
  async childrenCount(uid2, transaction2) {
    const db = this.database;
    const countResult = await db.sequelize.query(
      `SELECT COUNT(*) as count FROM ${this.flowModelTreePathTableName} where ancestor = :ancestor and depth  = 1`,
      {
        replacements: {
          ancestor: uid2
        },
        type: "SELECT",
        transaction: transaction2
      }
    );
    return parseInt(countResult[0]["count"]);
  }
  async isLeafNode(uid2, transaction2) {
    const childrenCount = await this.childrenCount(uid2, transaction2);
    return childrenCount === 0;
  }
  async findParentUid(uid2, transaction2) {
    const parent = await this.database.getRepository("flowModelTreePath").findOne({
      filter: {
        descendant: uid2,
        depth: 1
      },
      transaction: transaction2
    });
    return parent ? parent.get("ancestor") : null;
  }
  async findSiblingInfo(uid2, transaction2) {
    const rows = await this.database.sequelize.query(
      this.sqlAdapter(`SELECT ParentPath.ancestor as parent, NodeInfo.type as type
        FROM ${this.flowModelTreePathTableName} as ParentPath
        LEFT JOIN ${this.flowModelTreePathTableName} as NodeInfo
          ON NodeInfo.ancestor = ParentPath.descendant
          AND NodeInfo.descendant = ParentPath.descendant
          AND NodeInfo.depth = 0
        WHERE ParentPath.descendant = :uid AND ParentPath.depth = 1`),
      {
        type: "SELECT",
        replacements: { uid: uid2 },
        transaction: transaction2
      }
    );
    const row = rows[0];
    if (!(row == null ? void 0 : row.parent) || !(row == null ? void 0 : row.type)) {
      return null;
    }
    return {
      parentUid: row.parent,
      type: row.type
    };
  }
  async findSiblingSortRows(parentUid, type, transaction2) {
    const treeTable = this.flowModelTreePathTableName;
    const rows = await this.database.sequelize.query(
      this.sqlAdapter(`SELECT ChildPath.descendant as uid, ChildPath.sort as sort
        FROM ${treeTable} as ChildPath
        LEFT JOIN ${treeTable} as NodeInfo
          ON NodeInfo.ancestor = ChildPath.descendant
          AND NodeInfo.descendant = ChildPath.descendant
          AND NodeInfo.depth = 0
        WHERE ChildPath.ancestor = :parentUid
          AND ChildPath.depth = 1
          AND NodeInfo.type = :type`),
      {
        type: "SELECT",
        replacements: { parentUid, type },
        transaction: transaction2
      }
    );
    return rows.sort((a, b) => _FlowModelRepository.compareBySortAndUid(a, b));
  }
  async writeSiblingSorts(parentUid, orderedUids, transaction2) {
    const treeTable = this.flowModelTreePathTableName;
    for (const [index, uid2] of orderedUids.entries()) {
      await this.database.sequelize.query(
        this.sqlAdapter(
          `UPDATE ${treeTable} SET sort = :sort WHERE ancestor = :parentUid AND descendant = :uid AND depth = 1`
        ),
        {
          type: "UPDATE",
          replacements: {
            parentUid,
            uid: uid2,
            sort: index + 1
          },
          transaction: transaction2
        }
      );
    }
    await this.clearXUidPathCache(parentUid, transaction2);
  }
  async normalizeSiblingSorts(info, transaction2) {
    if (!info) {
      return;
    }
    const siblingRows = await this.findSiblingSortRows(info.parentUid, info.type, transaction2);
    await this.writeSiblingSorts(
      info.parentUid,
      siblingRows.map((row) => row.uid),
      transaction2
    );
  }
  async isAncestorOf(ancestorUid, descendantUid, transaction2) {
    const rows = await this.database.sequelize.query(
      this.sqlAdapter(`SELECT 1 as v
        FROM ${this.flowModelTreePathTableName}
        WHERE ancestor = :ancestorUid
          AND descendant = :descendantUid
          AND depth > 0
        LIMIT 1`),
      {
        type: "SELECT",
        replacements: { ancestorUid, descendantUid },
        transaction: transaction2
      }
    );
    return rows.length > 0;
  }
  async updateModelParentOptions(uid2, parentId, subKey, subType, transaction2) {
    const modelInstance = await this.model.findByPk(uid2, { transaction: transaction2 });
    if (!modelInstance) {
      throw new Error(`flowModels:move sourceId '${uid2}' not found`);
    }
    await modelInstance.update(
      {
        options: {
          ...import_lodash.default.omit(_FlowModelRepository.optionsToJson(modelInstance.get("options") || {}), ["uid"]),
          parentId,
          parent: parentId,
          subKey,
          subType
        }
      },
      { transaction: transaction2, hooks: false }
    );
  }
  async findNodeSchemaWithParent(uid2, transaction2) {
    const schema = await this.database.getRepository("flowModels").findOne({
      filter: {
        uid: uid2
      },
      transaction: transaction2
    });
    return {
      parentUid: await this.findParentUid(uid2, transaction2),
      schema
    };
  }
  async isSingleChild(uid2, transaction2) {
    const db = this.database;
    const parent = await this.findParentUid(uid2, transaction2);
    if (!parent) {
      return null;
    }
    const parentChildrenCount = await this.childrenCount(parent, transaction2);
    if (parentChildrenCount == 1) {
      const schema = await db.getRepository("flowModels").findOne({
        filter: {
          uid: parent
        },
        transaction: transaction2
      });
      return schema;
    }
    return null;
  }
  async insertBeside(targetUid, schema, side, options) {
    const { transaction: transaction2 } = options;
    const targetParent = await this.findParentUid(targetUid, transaction2);
    const db = this.database;
    const treeTable = this.flowModelTreePathTableName;
    const typeQuery = await db.sequelize.query(`SELECT type from ${treeTable} WHERE ancestor = :uid AND depth = 0;`, {
      type: "SELECT",
      replacements: {
        uid: targetUid
      },
      transaction: transaction2
    });
    const nodes = _FlowModelRepository.schemaToSingleNodes(schema);
    const rootNode = nodes[0];
    rootNode.childOptions = {
      parentUid: targetParent,
      type: typeQuery[0]["type"],
      position: {
        type: side,
        target: targetUid
      }
    };
    const insertedNodes = await this.insertNodes(nodes, options);
    return await this.getJsonSchema(insertedNodes[0].get("uid"), {
      transaction: transaction2
    });
  }
  async insertInner(targetUid, schema, position, options) {
    const { transaction: transaction2 } = options;
    const nodes = _FlowModelRepository.schemaToSingleNodes(schema);
    const rootNode = nodes[0];
    rootNode.childOptions = {
      parentUid: targetUid,
      type: import_lodash.default.get(schema, "x-node-type", "properties"),
      position
    };
    const insertedNodes = await this.insertNodes(nodes, options);
    return await this.getJsonSchema(insertedNodes[0].get("uid"), {
      transaction: transaction2
    });
  }
  async insertAfterBegin(targetUid, schema, options) {
    return await this.insertInner(targetUid, schema, "first", options);
  }
  async insertBeforeEnd(targetUid, schema, options) {
    return await this.insertInner(targetUid, schema, "last", options);
  }
  async insertBeforeBegin(targetUid, schema, options) {
    return await this.insertBeside(targetUid, schema, "before", options);
  }
  async insertAfterEnd(targetUid, schema, options) {
    return await this.insertBeside(targetUid, schema, "after", options);
  }
  async insertNodes(nodes, options) {
    const { transaction: transaction2 } = options;
    const insertedNodes = [];
    for (const node of nodes) {
      insertedNodes.push(
        await this.insertSingleNode(node, {
          ...options,
          transaction: transaction2
        })
      );
    }
    return insertedNodes;
  }
  async doGetProperties(uid2, options = {}) {
    const { transaction: transaction2 } = options;
    const db = this.database;
    const rawSql = `
        SELECT "SchemaTable"."uid" as "uid", "SchemaTable"."name" as "name", "SchemaTable"."options" as "options",
               TreePath.depth as depth,
               NodeInfo.type as type, NodeInfo.async as async,  ParentPath.ancestor as parent, ParentPath.sort as sort
        FROM ${this.flowModelTreePathTableName} as TreePath
                 LEFT JOIN ${this.flowModelsTableName} as "SchemaTable" ON "SchemaTable"."uid" =  TreePath.descendant
                 LEFT JOIN ${this.flowModelTreePathTableName} as NodeInfo ON NodeInfo.descendant = "SchemaTable"."uid" and NodeInfo.descendant = NodeInfo.ancestor and NodeInfo.depth = 0
                 LEFT JOIN ${this.flowModelTreePathTableName} as ParentPath ON (ParentPath.descendant = "SchemaTable"."uid" AND ParentPath.depth = 1)
        WHERE TreePath.ancestor = :ancestor  AND (NodeInfo.async = false or TreePath.depth <= 1)`;
    const nodes = await db.sequelize.query(this.sqlAdapter(rawSql), {
      replacements: {
        ancestor: uid2
      },
      transaction: transaction2
    });
    if (nodes[0].length == 0) {
      return {};
    }
    const schema = this.nodesToSchema(nodes[0], uid2);
    return import_lodash.default.pick(schema, ["type", "properties"]);
  }
  async findNodesById(uid2, options) {
    const db = this.database;
    const treeTable = this.flowModelTreePathTableName;
    const rawSql = `
        SELECT "SchemaTable"."uid" as "uid", "SchemaTable"."name" as name, "SchemaTable"."options" as "options" ,
               TreePath.depth as depth,
               NodeInfo.type as type, NodeInfo.async as async,  ParentPath.ancestor as parent, ParentPath.sort as sort
        FROM ${treeTable} as TreePath
                 LEFT JOIN ${this.flowModelsTableName} as "SchemaTable" ON "SchemaTable"."uid" =  TreePath.descendant
                 LEFT JOIN ${treeTable} as NodeInfo ON NodeInfo.descendant = "SchemaTable"."uid" and NodeInfo.descendant = NodeInfo.ancestor and NodeInfo.depth = 0
                 LEFT JOIN ${treeTable} as ParentPath ON (ParentPath.descendant = "SchemaTable"."uid" AND ParentPath.depth = 1)
        WHERE TreePath.ancestor = :ancestor  ${(options == null ? void 0 : options.includeAsyncNode) ? "" : "AND (NodeInfo.async != true or TreePath.depth = 0)"}
    `;
    const nodes = await db.sequelize.query(this.sqlAdapter(rawSql), {
      replacements: {
        ancestor: uid2
      },
      transaction: options == null ? void 0 : options.transaction
    });
    if (nodes[0].length == 0) {
      return [];
    }
    return nodes[0];
  }
  async doGetJsonSchema(uid2, options) {
    const nodes = await this.findNodesById(uid2, options);
    return this.nodesToSchema(nodes, uid2);
  }
  ignoreSchemaProperties(schemaProperties) {
    return import_lodash.default.omit(schemaProperties, nodeKeys);
  }
  breakOnMatched(schemaInstance, breakRemoveOn) {
    if (!breakRemoveOn) {
      return false;
    }
    for (const key of Object.keys(breakRemoveOn)) {
      const instanceValue = schemaInstance.get(key);
      const breakRemoveOnValue = breakRemoveOn[key];
      if (instanceValue !== breakRemoveOnValue) {
        return false;
      }
    }
    return true;
  }
  async schemaExists(schema, options) {
    if (import_lodash.default.isObject(schema) && !schema["uid"]) {
      return false;
    }
    const { transaction: transaction2 } = options;
    const result = await this.database.sequelize.query(
      this.sqlAdapter(`select "uid" from ${this.flowModelsTableName} where "uid" = :uid`),
      {
        type: "SELECT",
        replacements: {
          uid: import_lodash.default.isString(schema) ? schema : schema["uid"]
        },
        transaction: transaction2
      }
    );
    return result.length > 0;
  }
  regenerateUid(s) {
    s["uid"] = (0, import_utils.uid)();
    Object.keys(s.properties || {}).forEach((key) => {
      this.regenerateUid(s.properties[key]);
    });
  }
  async insertSchemaRecord(name, uid2, schema, transaction2) {
    const node = await this.create({
      values: {
        name,
        ["uid"]: uid2,
        options: schema
      },
      transaction: transaction2,
      context: {
        disableInsertHook: true
      }
    });
    return node;
  }
  prepareSingleNodeForInsert(schema) {
    const uid2 = schema["uid"];
    const name = schema["name"];
    const async = import_lodash.default.get(schema, "x-async", false);
    const childOptions = schema["childOptions"];
    delete schema["uid"];
    delete schema["x-async"];
    delete schema["name"];
    delete schema["childOptions"];
    return { uid: uid2, name, async, childOptions };
  }
  static modelToSingleNodes(model, parentChildOptions = null) {
    const { uid: oldUid, async, subModels, ...rest } = import_lodash.default.cloneDeep(model);
    const currentUid = oldUid || (0, import_utils.uid)();
    const node = {
      uid: currentUid,
      "x-async": async || false,
      name: currentUid,
      ...rest
    };
    if (parentChildOptions) {
      node.childOptions = parentChildOptions;
    }
    const nodes = [node];
    if (Object.keys(subModels || {}).length > 0) {
      for (const [subKey, subItems] of Object.entries(subModels)) {
        const items = import_lodash.default.castArray(subItems);
        let sort = 0;
        for (const item of items) {
          item.subKey = subKey;
          item.subType = Array.isArray(subItems) ? "array" : "object";
          const childOptions = {
            parentUid: currentUid,
            parentPath: [currentUid, ...(parentChildOptions == null ? void 0 : parentChildOptions.parentPath) || []].filter(Boolean),
            type: subKey,
            // type: 'properties',
            sort: ++sort
          };
          const children = this.modelToSingleNodes(item, childOptions);
          nodes.push(...children);
        }
      }
    }
    return nodes;
  }
  static nodeToModel(node) {
    const { uid: uid2, name, options } = node;
    const model = {
      uid: uid2,
      ...options
    };
    return model;
  }
  static nodesToModel(nodes, rootUid) {
    const nodeMap = /* @__PURE__ */ new Map();
    for (const node of nodes) {
      nodeMap.set(node["uid"], node);
    }
    const rootNode = nodeMap.get(rootUid);
    if (!rootNode) return null;
    const children = nodes.filter((n) => n.parent === rootUid);
    const subModels = {};
    for (const child of children) {
      const { subKey, subType } = this.optionsToJson(child.options);
      if (!subKey) continue;
      const model2 = _FlowModelRepository.nodesToModel(nodes, child["uid"]) || {
        ...this.optionsToJson(child.options),
        uid: child["uid"],
        sortIndex: child.sort
      };
      model2.sortIndex = child.sort;
      if (subType === "array") {
        if (!subModels[subKey]) subModels[subKey] = [];
        subModels[subKey].push(model2);
      } else {
        subModels[subKey] = model2;
      }
    }
    for (const key in subModels) {
      if (Array.isArray(subModels[key])) {
        subModels[key] = subModels[key].sort(
          (a, b) => _FlowModelRepository.compareBySortAndUid(
            { sort: a.sortIndex, uid: a.uid },
            { sort: b.sortIndex, uid: b.uid }
          )
        ).map((item, index) => {
          item.sortIndex = index + 1;
          return item;
        });
      }
    }
    const filteredSubModels = {};
    for (const key in subModels) {
      const value = subModels[key];
      if (Array.isArray(value) && value.length === 0) continue;
      if (!Array.isArray(value) && typeof value === "object" && value !== null && Object.keys(value).length === 0)
        continue;
      filteredSubModels[key] = value;
    }
    const model = {
      ...this.optionsToJson(rootNode.options),
      uid: rootNode["uid"],
      ...Object.keys(filteredSubModels).length > 0 ? { subModels: filteredSubModels } : {}
    };
    if (rootNode.parent) {
      model.parent = rootNode.parent;
      model.parentId = rootNode.parent;
    } else {
      delete model.parent;
      delete model.parentId;
    }
    return model;
  }
  async insertModel(model, options) {
    const nodes = _FlowModelRepository.modelToSingleNodes(model);
    const rootUid = nodes[0]["uid"];
    await this.insertNodes(nodes, options);
    return await this.findModelById(rootUid, options);
  }
  async updateSingleNode(node, options) {
    var _a;
    const instance = await this.model.findByPk(node["uid"], {
      transaction: options == null ? void 0 : options.transaction
    });
    if (instance) {
      await instance.update(
        {
          options: {
            ...import_lodash.default.omit(instance.get("options"), ["uid"]),
            ...import_lodash.default.omit(node, ["x-async", "name", "uid", "childOptions"]),
            ...((_a = node.childOptions) == null ? void 0 : _a.parentUid) ? {
              parent: node.childOptions.parentUid,
              parentId: node.childOptions.parentUid
            } : {}
          }
        },
        {
          hooks: false,
          transaction: options == null ? void 0 : options.transaction
        }
      );
      return true;
    }
    return false;
  }
  async upsertModel(model, options) {
    let childOptions = null;
    if (model.parentId) {
      childOptions = {
        parentUid: model.parentId,
        type: model.subKey,
        // type: 'properties',
        position: "last"
      };
    }
    const nodes = _FlowModelRepository.modelToSingleNodes(model, childOptions);
    const rootUid = nodes[0]["uid"];
    for (const node of nodes) {
      const exists = await this.updateSingleNode(node, options);
      if (!exists) {
        await this.insertSingleNode(node, options);
      }
    }
    return rootUid;
  }
  async findModelById(uid2, options) {
    const nodes = await this.findNodesById(uid2, options);
    return _FlowModelRepository.nodesToModel(nodes, uid2);
  }
  async findModelByParentId(parentUid, options) {
    const r = this.database.getRepository("flowModelTreePath");
    const treePaths = await r.model.findAll({
      where: {
        ancestor: parentUid,
        depth: 1
      },
      transaction: options == null ? void 0 : options.transaction
    });
    const ancestors = treePaths.map((treePath2) => treePath2["descendant"]);
    const where = {
      ancestor: ancestors,
      depth: 0
    };
    if (options == null ? void 0 : options.subKey) {
      where["type"] = options.subKey;
    }
    const treePath = await r.model.findOne({
      where,
      transaction: options == null ? void 0 : options.transaction
    });
    if (treePath == null ? void 0 : treePath["descendant"]) {
      return this.findModelById(treePath["descendant"], options);
    }
    return null;
  }
  async attach(uid2, attachOptions, options) {
    const { transaction: transaction2 } = options || {};
    const modelUid = String(uid2 || "").trim();
    const parentId = String((attachOptions == null ? void 0 : attachOptions.parentId) || "").trim();
    const subKey = String((attachOptions == null ? void 0 : attachOptions.subKey) || "").trim();
    const subType = attachOptions == null ? void 0 : attachOptions.subType;
    if (!modelUid || !parentId || !subKey || subType !== "array" && subType !== "object") {
      throw new Error("flowModels:attach missing required params");
    }
    if (modelUid === parentId) {
      throw new Error("flowModels:attach cannot attach model to itself");
    }
    const treeTable = this.flowModelTreePathTableName;
    const modelInstance = await this.model.findByPk(modelUid, { transaction: transaction2 });
    if (!modelInstance) {
      throw new Error(`flowModels:attach uid '${modelUid}' not found`);
    }
    const parentInstance = await this.model.findByPk(parentId, { transaction: transaction2 });
    if (!parentInstance) {
      throw new Error(`flowModels:attach parentId '${parentId}' not found`);
    }
    const cycle = await this.database.sequelize.query(
      `SELECT 1 as v
       FROM ${treeTable}
       WHERE ancestor = :ancestor AND descendant = :descendant AND depth > 0
       LIMIT 1`,
      {
        type: "SELECT",
        replacements: {
          ancestor: modelUid,
          descendant: parentId
        },
        transaction: transaction2
      }
    );
    if (cycle == null ? void 0 : cycle.length) {
      throw new Error("flowModels:attach cycle detected");
    }
    if (subType === "object") {
      const conflict = await this.database.sequelize.query(
        `SELECT TreeTable.descendant as uid
         FROM ${treeTable} as TreeTable
                  LEFT JOIN ${treeTable} as NodeInfo
                            ON NodeInfo.descendant = TreeTable.descendant
                                AND NodeInfo.depth = 0
         WHERE TreeTable.depth = 1
           AND TreeTable.ancestor = :ancestor
           AND NodeInfo.type = :type
           AND TreeTable.descendant != :uid
         LIMIT 1`,
        {
          type: "SELECT",
          replacements: {
            ancestor: parentId,
            type: subKey,
            uid: modelUid
          },
          transaction: transaction2
        }
      );
      if (conflict == null ? void 0 : conflict.length) {
        throw new Error(`flowModels:attach subKey '${subKey}' already exists on parent '${parentId}'`);
      }
    }
    const normalizePosition = (input) => {
      if (!input) return "last";
      if (input === "first" || input === "last") return input;
      if (typeof input === "object") {
        const p = input;
        const type = p == null ? void 0 : p.type;
        const target = String((p == null ? void 0 : p.target) || "").trim();
        if ((type === "before" || type === "after") && target) {
          return { type, target };
        }
      }
      throw new Error("flowModels:attach invalid position");
    };
    const position = subType === "object" ? "last" : normalizePosition(attachOptions == null ? void 0 : attachOptions.position);
    if (typeof position === "object") {
      const target = String(position.target || "").trim();
      if (target === modelUid) {
        throw new Error("flowModels:attach position target cannot be itself");
      }
      const ok = await this.database.sequelize.query(
        `SELECT 1 as v
         FROM ${treeTable} as TreeTable
                  LEFT JOIN ${treeTable} as NodeInfo
                            ON NodeInfo.descendant = TreeTable.descendant
                                AND NodeInfo.depth = 0
         WHERE TreeTable.depth = 1
           AND TreeTable.ancestor = :ancestor
           AND TreeTable.descendant = :descendant
           AND NodeInfo.type = :type
         LIMIT 1`,
        {
          type: "SELECT",
          replacements: {
            ancestor: parentId,
            descendant: target,
            type: subKey
          },
          transaction: transaction2
        }
      );
      if (!(ok == null ? void 0 : ok.length)) {
        throw new Error("flowModels:attach position target is not a sibling under the same parent/subKey");
      }
    }
    await this.clearXUidPathCache(modelUid, transaction2);
    await modelInstance.update(
      {
        options: {
          ...import_lodash.default.omit(modelInstance.get("options"), ["uid"]),
          parentId,
          parent: parentId,
          subKey,
          subType
        }
      },
      { transaction: transaction2, hooks: false }
    );
    await this.insertSingleNode(
      {
        uid: modelUid,
        name: modelUid,
        childOptions: {
          parentUid: parentId,
          type: subKey,
          position
        }
      },
      { transaction: transaction2, removeParentsIfNoChildren: false }
    );
    return await this.findModelById(modelUid, { transaction: transaction2, includeAsyncNode: true });
  }
  async move(options, transactionOptions) {
    const { sourceId, targetId, position } = options;
    const sourceUid = String(sourceId || "").trim();
    const targetUid = String(targetId || "").trim();
    if (!sourceUid || !targetUid) {
      throw new Error("flowModels:move missing required params");
    }
    if (position !== "before" && position !== "after") {
      throw new Error("flowModels:move invalid position");
    }
    const transaction2 = transactionOptions == null ? void 0 : transactionOptions.transaction;
    const sourceInstance = await this.model.findByPk(sourceUid, { transaction: transaction2 });
    if (!sourceInstance) {
      throw new Error(`flowModels:move sourceId '${sourceUid}' not found`);
    }
    if (sourceUid === targetUid) {
      await this.normalizeSiblingSorts(await this.findSiblingInfo(sourceUid, transaction2), transaction2);
      return null;
    }
    const sourceInfo = await this.findSiblingInfo(sourceUid, transaction2);
    const targetInfo = await this.findSiblingInfo(targetUid, transaction2);
    if (!targetInfo) {
      throw new Error("flowModels:move target is not attached to a parent");
    }
    const targetInstance = await this.model.findByPk(targetUid, { transaction: transaction2 });
    const targetOptions = _FlowModelRepository.optionsToJson((targetInstance == null ? void 0 : targetInstance.get("options")) || {});
    const targetSubType = targetOptions.subType === "object" ? "object" : "array";
    if (targetInfo.parentUid === sourceUid || await this.isAncestorOf(sourceUid, targetInfo.parentUid, transaction2)) {
      throw new Error("flowModels:move cycle detected");
    }
    if ((sourceInfo == null ? void 0 : sourceInfo.parentUid) === targetInfo.parentUid && sourceInfo.type === targetInfo.type) {
      const siblingRows = await this.findSiblingSortRows(sourceInfo.parentUid, sourceInfo.type, transaction2);
      const sourceRow = siblingRows.find((row) => row.uid === sourceUid);
      const targetIndex = siblingRows.findIndex((row) => row.uid === targetUid);
      if (!sourceRow || targetIndex === -1) {
        throw new Error("flowModels:move source and target must be sibling nodes under the same parent/subKey");
      }
      const rowsWithoutSource = siblingRows.filter((row) => row.uid !== sourceUid);
      const insertIndex = rowsWithoutSource.findIndex((row) => row.uid === targetUid);
      rowsWithoutSource.splice(position === "after" ? insertIndex + 1 : insertIndex, 0, sourceRow);
      await this.writeSiblingSorts(
        sourceInfo.parentUid,
        rowsWithoutSource.map((row) => row.uid),
        transaction2
      );
      return await this.findModelById(sourceUid, { transaction: transaction2 });
    }
    await this.normalizeSiblingSorts(sourceInfo, transaction2);
    await this.normalizeSiblingSorts(targetInfo, transaction2);
    await this.updateModelParentOptions(sourceUid, targetInfo.parentUid, targetInfo.type, targetSubType, transaction2);
    await this.insertSingleNode(
      {
        uid: sourceUid,
        name: sourceUid,
        childOptions: {
          parentUid: targetInfo.parentUid,
          type: targetInfo.type,
          position: {
            type: position,
            target: targetUid
          }
        }
      },
      { transaction: transaction2, removeParentsIfNoChildren: false }
    );
    await this.normalizeSiblingSorts(sourceInfo, transaction2);
    await this.normalizeSiblingSorts(await this.findSiblingInfo(sourceUid, transaction2), transaction2);
    return await this.findModelById(sourceUid, { transaction: transaction2 });
  }
};
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "clearAncestor", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "patch", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "initializeActionContext", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "batchPatch", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "remove", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "insertAdjacent", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "duplicate", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "insert", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "insertNewSchema", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "insertBeside", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "insertInner", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "insertAfterBegin", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "insertBeforeEnd", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "insertBeforeBegin", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "insertAfterEnd", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "insertNodes", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "insertModel", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "updateSingleNode", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "upsertModel", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "attach", 1);
__decorateClass([
  transaction()
], _FlowModelRepository.prototype, "move", 1);
let FlowModelRepository = _FlowModelRepository;
var repository_default = FlowModelRepository;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  FlowModelRepository,
  transaction
});
