import { assert, unreachable } from "../../../shared/src/asserts.js";
import { must } from "../../../shared/src/must.js";
import { emptyArray } from "../../../shared/src/sentinels.js";
function* pushAccumulatedChanges(accumulatedPushes, output, pusher, fanOutChangeType, mergeRelationships2, addEmptyRelationships) {
  if (accumulatedPushes.length === 0) {
    return;
  }
  const candidatesToPush = /* @__PURE__ */ new Map();
  for (const change of accumulatedPushes) {
    if (fanOutChangeType === "child" && change.type !== "child") {
      assert(
        candidatesToPush.has(change.type) === false,
        () => `Fan-in:child expected at most one ${change.type} when fan-out is of type child`
      );
    }
    const existing = candidatesToPush.get(change.type);
    let mergedChange = change;
    if (existing) {
      mergedChange = mergeRelationships2(existing, change);
    }
    candidatesToPush.set(change.type, mergedChange);
  }
  accumulatedPushes.length = 0;
  const types = [...candidatesToPush.keys()];
  switch (fanOutChangeType) {
    case "remove":
      assert(
        types.length === 1 && types[0] === "remove",
        "Fan-in:remove expected all removes"
      );
      yield* output.push(
        addEmptyRelationships(must(candidatesToPush.get("remove"))),
        pusher
      );
      return;
    case "add":
      assert(
        types.length === 1 && types[0] === "add",
        "Fan-in:add expected all adds"
      );
      yield* output.push(
        addEmptyRelationships(must(candidatesToPush.get("add"))),
        pusher
      );
      return;
    case "edit": {
      assert(
        types.every(
          (type) => type === "add" || type === "remove" || type === "edit"
        ),
        "Fan-in:edit expected all adds, removes, or edits"
      );
      const addChange = candidatesToPush.get("add");
      const removeChange = candidatesToPush.get("remove");
      let editChange = candidatesToPush.get("edit");
      if (editChange) {
        if (addChange) {
          editChange = mergeRelationships2(editChange, addChange);
        }
        if (removeChange) {
          editChange = mergeRelationships2(editChange, removeChange);
        }
        yield* output.push(addEmptyRelationships(editChange), pusher);
        return;
      }
      if (addChange && removeChange) {
        yield* output.push(
          addEmptyRelationships({
            type: "edit",
            node: addChange.node,
            oldNode: removeChange.node
          }),
          pusher
        );
        return;
      }
      yield* output.push(
        addEmptyRelationships(must(addChange ?? removeChange)),
        pusher
      );
      return;
    }
    case "child": {
      assert(
        types.every(
          (type) => type === "add" || // exists can change child to add or remove
          type === "remove" || // exists can change child to add or remove
          type === "child"
          // other operators may preserve the child change
        ),
        "Fan-in:child expected all adds, removes, or children"
      );
      assert(
        types.length <= 2,
        "Fan-in:child expected at most 2 types on a child change from fan-out"
      );
      const childChange = candidatesToPush.get("child");
      if (childChange) {
        yield* output.push(childChange, pusher);
        return;
      }
      const addChange = candidatesToPush.get("add");
      const removeChange = candidatesToPush.get("remove");
      assert(
        addChange === void 0 || removeChange === void 0,
        "Fan-in:child expected either add or remove, not both"
      );
      yield* output.push(
        addEmptyRelationships(must(addChange ?? removeChange)),
        pusher
      );
      return;
    }
  }
}
function mergeRelationships(left, right) {
  if (left.type === right.type) {
    switch (left.type) {
      case "add": {
        return {
          type: "add",
          node: {
            row: left.node.row,
            relationships: {
              ...right.node.relationships,
              ...left.node.relationships
            }
          }
        };
      }
      case "remove": {
        return {
          type: "remove",
          node: {
            row: left.node.row,
            relationships: {
              ...right.node.relationships,
              ...left.node.relationships
            }
          }
        };
      }
      case "edit": {
        assert(
          right.type === "edit",
          () => `mergeRelationships: when left.type is edit and types match, right.type must be edit, got ${right.type}`
        );
        return {
          type: "edit",
          node: {
            row: left.node.row,
            relationships: {
              ...right.node.relationships,
              ...left.node.relationships
            }
          },
          oldNode: {
            row: left.oldNode.row,
            relationships: {
              ...right.oldNode.relationships,
              ...left.oldNode.relationships
            }
          }
        };
      }
      case "child": {
        assert(
          right.type === "child",
          () => `mergeRelationships: when left.type is child and types match, right.type must be child, got ${right.type}`
        );
        return {
          type: "child",
          node: {
            row: left.node.row,
            relationships: {
              ...right.node.relationships,
              ...left.node.relationships
            }
          },
          child: left.child
        };
      }
    }
  }
  assert(
    left.type === "edit",
    () => `mergeRelationships: when types differ, left.type must be edit, got left.type=${left.type}, right.type=${right.type}`
  );
  switch (right.type) {
    case "add": {
      return {
        type: "edit",
        node: {
          ...left.node,
          relationships: {
            ...right.node.relationships,
            ...left.node.relationships
          }
        },
        oldNode: left.oldNode
      };
    }
    case "remove": {
      return {
        type: "edit",
        node: left.node,
        oldNode: {
          ...left.oldNode,
          relationships: {
            ...right.node.relationships,
            ...left.oldNode.relationships
          }
        }
      };
    }
  }
  unreachable();
}
function makeAddEmptyRelationships(schema) {
  return (change) => {
    if (Object.keys(schema.relationships).length === 0) {
      return change;
    }
    switch (change.type) {
      case "add":
      case "remove": {
        const ret = {
          ...change,
          node: {
            ...change.node,
            relationships: {
              ...change.node.relationships
            }
          }
        };
        mergeEmpty(ret.node.relationships, Object.keys(schema.relationships));
        return ret;
      }
      case "edit": {
        const ret = {
          ...change,
          node: {
            ...change.node,
            relationships: {
              ...change.node.relationships
            }
          },
          oldNode: {
            ...change.oldNode,
            relationships: {
              ...change.oldNode.relationships
            }
          }
        };
        mergeEmpty(ret.node.relationships, Object.keys(schema.relationships));
        mergeEmpty(
          ret.oldNode.relationships,
          Object.keys(schema.relationships)
        );
        return ret;
      }
      case "child":
        return change;
    }
  };
}
function mergeEmpty(relationships, relationshipNames) {
  for (const relName of relationshipNames) {
    if (relationships[relName] === void 0) {
      relationships[relName] = () => emptyArray;
    }
  }
}
export {
  makeAddEmptyRelationships,
  mergeEmpty,
  mergeRelationships,
  pushAccumulatedChanges
};
//# sourceMappingURL=push-accumulated.js.map
