import { assert } from "../../../shared/src/asserts.js";
import "../../../shared/src/config.js";
import "../../../replicache/src/hash.js";
import "../../../replicache/src/btree/node.js";
import { Latest } from "../../../replicache/src/format-version-enum.js";
import { diffBinarySearch } from "../../../replicache/src/subscriptions.js";
import { diff } from "../../../replicache/src/sync/diff.js";
import { using, withRead } from "../../../replicache/src/with-transactions.js";
import { wrapIterable } from "../../../shared/src/iterables.js";
import { must } from "../../../shared/src/must.js";
import { MemorySource } from "../../../zql/src/ivm/memory-source.js";
import { consume } from "../../../zql/src/ivm/stream.js";
import { ENTITIES_KEY_PREFIX, sourceNameFromKey } from "./keys.js";
class IVMSourceBranch {
  #sources;
  #tables;
  hash;
  constructor(tables, hash, sources = /* @__PURE__ */ new Map()) {
    this.#tables = tables;
    this.#sources = sources;
    this.hash = hash;
  }
  getSource(name) {
    if (this.#sources.has(name)) {
      return this.#sources.get(name);
    }
    const schema = this.#tables[name];
    const source = schema ? new MemorySource(name, schema.columns, schema.primaryKey) : void 0;
    this.#sources.set(name, source);
    return source;
  }
  clear() {
    this.#sources.clear();
  }
  /**
   * Mutates the current branch, advancing it to the new head
   * by applying the given diffs.
   */
  advance(expectedHead, newHead, diffs) {
    assert(
      this.hash === expectedHead,
      () => `Expected head must match the main head. Got: ${this.hash}, expected: ${expectedHead}`
    );
    applyDiffs(diffs, this);
    this.hash = newHead;
  }
  /**
   * Fork the branch and patch it up to match the desired head.
   */
  async forkToHead(store, desiredHead, readOptions) {
    const fork = this.fork();
    if (fork.hash === desiredHead) {
      return fork;
    }
    await patchBranch(desiredHead, store, fork, readOptions);
    fork.hash = desiredHead;
    return fork;
  }
  /**
   * Creates a new IVMSourceBranch that is a copy of the current one.
   * This is a cheap operation since the b-trees are shared until a write is performed
   * and then only the modified nodes are copied.
   *
   * IVM branches are forked when we need to rebase mutations.
   * The mutations modify the fork rather than original branch.
   */
  fork() {
    return new IVMSourceBranch(
      this.#tables,
      this.hash,
      new Map(
        wrapIterable(this.#sources.entries()).map(([name, source]) => [
          name,
          source?.fork()
        ])
      )
    );
  }
}
async function patchBranch(desiredHead, store, fork, readOptions) {
  const diffs = await computeDiffs(
    must(fork.hash),
    desiredHead,
    store,
    readOptions
  );
  if (!diffs) {
    return;
  }
  applyDiffs(diffs, fork);
}
async function computeDiffs(startHash, endHash, store, readOptions) {
  const readFn = (dagRead) => diff(
    startHash,
    endHash,
    dagRead,
    {
      shouldComputeDiffs: () => true,
      shouldComputeDiffsForIndex(_name) {
        return false;
      }
    },
    Latest
  );
  let diffs;
  if (readOptions?.openLazySourceRead) {
    diffs = await using(store.read(readOptions.openLazySourceRead), readFn);
  } else if (readOptions?.openLazyRead) {
    diffs = await readFn(readOptions.openLazyRead);
  } else {
    diffs = await withRead(store, readFn);
  }
  return diffs.get("");
}
function applyDiffs(diffs, branch) {
  for (let i = diffBinarySearch(diffs, ENTITIES_KEY_PREFIX, (diff2) => diff2.key); i < diffs.length; i++) {
    const diff2 = diffs[i];
    const { key } = diff2;
    if (!key.startsWith(ENTITIES_KEY_PREFIX)) {
      break;
    }
    const name = sourceNameFromKey(key);
    const source = must(branch.getSource(name));
    switch (diff2.op) {
      case "del":
        consume(
          source.push({
            type: "remove",
            row: diff2.oldValue
          })
        );
        break;
      case "add":
        consume(
          source.push({
            type: "add",
            row: diff2.newValue
          })
        );
        break;
      case "change":
        consume(
          source.push({
            type: "edit",
            row: diff2.newValue,
            oldRow: diff2.oldValue
          })
        );
        break;
    }
  }
}
export {
  IVMSourceBranch
};
//# sourceMappingURL=ivm-branch.js.map
