import { assert } from "../../../shared/src/asserts.js";
import { diff } from "../btree/diff.js";
import { BTreeRead, allEntriesAsDiff } from "../btree/read.js";
import { BTreeWrite } from "../btree/write.js";
import { DD31 } from "../format-version-enum.js";
import { emptyHash } from "../hash.js";
import { lazy } from "../lazy.js";
import { DiffsMap } from "../sync/diff.js";
import { commitFromHash, getMutationID, newSnapshotDD31, newLocalDD31, baseSnapshotHashFromHash } from "./commit.js";
import { Add, Remove } from "./index-operation-enum.js";
import { indexValue, IndexWrite } from "./index.js";
import { SnapshotDD31, LocalDD31 } from "./meta-type-enum.js";
import { Read, readIndexesForRead } from "./read.js";
class Write extends Read {
  #dagWrite;
  #basis;
  #meta;
  #clientID;
  #formatVersion;
  constructor(dagWrite, map, basis, meta, indexes, clientID, formatVersion) {
    super(dagWrite, map, indexes);
    this.#dagWrite = dagWrite;
    this.#basis = basis;
    this.#meta = meta;
    this.#clientID = clientID;
    this.#formatVersion = formatVersion;
    if (basis === void 0) {
      assert(meta.basisHash === emptyHash);
    } else {
      assert(meta.basisHash === basis.chunk.hash);
    }
  }
  /**
   * The value needs to be frozen since it is kept in memory and used later for
   * comparison as well as returned in `get`.
   */
  async put(lc, key, value) {
    const oldVal = lazy(() => this.map.get(key));
    await updateIndexes(lc, this.indexes, key, oldVal, value);
    await this.map.put(key, value);
  }
  getMutationID() {
    return getMutationID(this.#clientID, this.#dagWrite, this.#meta);
  }
  async del(lc, key) {
    const oldVal = lazy(() => this.map.get(key));
    if (oldVal !== void 0) {
      await updateIndexes(lc, this.indexes, key, oldVal, void 0);
    }
    return this.map.del(key);
  }
  async clear() {
    await this.map.clear();
    const ps = [];
    for (const idx of this.indexes.values()) {
      ps.push(idx.clear());
    }
    await Promise.all(ps);
  }
  async putCommit() {
    const valueHash = await this.map.flush();
    const indexRecords = [];
    for (const index of this.indexes.values()) {
      const valueHash2 = await index.flush();
      const indexRecord = {
        definition: index.meta.definition,
        valueHash: valueHash2
      };
      indexRecords.push(indexRecord);
    }
    let commit;
    const meta = this.#meta;
    switch (meta.type) {
      case LocalDD31: {
        assert(this.#formatVersion >= DD31);
        const {
          basisHash,
          mutationID,
          mutatorName,
          mutatorArgsJSON,
          originalHash,
          timestamp
        } = meta;
        commit = newLocalDD31(
          this.#dagWrite.createChunk,
          basisHash,
          await baseSnapshotHashFromHash(basisHash, this.#dagWrite),
          mutationID,
          mutatorName,
          mutatorArgsJSON,
          originalHash,
          valueHash,
          indexRecords,
          timestamp,
          this.#clientID
        );
        break;
      }
      case SnapshotDD31: {
        assert(this.#formatVersion > DD31);
        const { basisHash, lastMutationIDs, cookieJSON } = meta;
        commit = newSnapshotDD31(
          this.#dagWrite.createChunk,
          basisHash,
          lastMutationIDs,
          cookieJSON,
          valueHash,
          indexRecords
        );
        break;
      }
    }
    await this.#dagWrite.putChunk(commit.chunk);
    return commit;
  }
  // Return value is the hash of the new commit.
  async commit(headName) {
    const commit = await this.putCommit();
    const commitHash = commit.chunk.hash;
    await this.#dagWrite.setHead(headName, commitHash);
    await this.#dagWrite.commit();
    return commitHash;
  }
  async commitWithDiffs(headName, diffConfig) {
    const commit = this.putCommit();
    const diffMap = await this.#generateDiffs(diffConfig);
    const commitHash = (await commit).chunk.hash;
    await this.#dagWrite.setHead(headName, commitHash);
    await this.#dagWrite.commit();
    return [commitHash, diffMap];
  }
  async #generateDiffs(diffConfig) {
    const diffsMap = new DiffsMap();
    if (!diffConfig.shouldComputeDiffs()) {
      return diffsMap;
    }
    let valueDiff = [];
    if (this.#basis) {
      const basisMap = new BTreeRead(
        this.#dagWrite,
        this.#formatVersion,
        this.#basis.valueHash
      );
      valueDiff = await diff(basisMap, this.map);
    }
    diffsMap.set("", valueDiff);
    let basisIndexes;
    if (this.#basis) {
      basisIndexes = readIndexesForRead(
        this.#basis,
        this.#dagWrite,
        this.#formatVersion
      );
    } else {
      basisIndexes = /* @__PURE__ */ new Map();
    }
    for (const [name, index] of this.indexes) {
      if (!diffConfig.shouldComputeDiffsForIndex(name)) {
        continue;
      }
      const basisIndex = basisIndexes.get(name);
      assert(index !== basisIndex);
      const indexDiffResult = await (basisIndex ? diff(basisIndex.map, index.map) : (
        // No basis. All keys are new.
        allEntriesAsDiff(index.map, "add")
      ));
      diffsMap.set(name, indexDiffResult);
    }
    for (const [name, basisIndex] of basisIndexes) {
      if (!this.indexes.has(name) && diffConfig.shouldComputeDiffsForIndex(name)) {
        const indexDiffResult = await allEntriesAsDiff(basisIndex.map, "del");
        diffsMap.set(name, indexDiffResult);
      }
    }
    return diffsMap;
  }
  close() {
    this.#dagWrite.release();
  }
}
async function newWriteLocal(basisHash, mutatorName, mutatorArgsJSON, originalHash, dagWrite, timestamp, clientID, formatVersion) {
  const basis = await commitFromHash(basisHash, dagWrite);
  const bTreeWrite = new BTreeWrite(dagWrite, formatVersion, basis.valueHash);
  const mutationID = await basis.getNextMutationID(clientID, dagWrite);
  const indexes = readIndexesForWrite(basis, dagWrite, formatVersion);
  assert(formatVersion >= DD31);
  return new Write(
    dagWrite,
    bTreeWrite,
    basis,
    {
      type: LocalDD31,
      basisHash,
      baseSnapshotHash: await baseSnapshotHashFromHash(basisHash, dagWrite),
      mutatorName,
      mutatorArgsJSON,
      mutationID,
      originalHash,
      timestamp,
      clientID
    },
    indexes,
    clientID,
    formatVersion
  );
}
async function newWriteSnapshotDD31(basisHash, lastMutationIDs, cookieJSON, dagWrite, clientID, formatVersion) {
  const basis = await commitFromHash(basisHash, dagWrite);
  const bTreeWrite = new BTreeWrite(dagWrite, formatVersion, basis.valueHash);
  return new Write(
    dagWrite,
    bTreeWrite,
    basis,
    { basisHash, type: SnapshotDD31, lastMutationIDs, cookieJSON },
    readIndexesForWrite(basis, dagWrite, formatVersion),
    clientID,
    formatVersion
  );
}
async function updateIndexes(lc, indexes, key, oldValGetter, newVal) {
  const ps = [];
  for (const idx of indexes.values()) {
    const { keyPrefix } = idx.meta.definition;
    if (!keyPrefix || key.startsWith(keyPrefix)) {
      const oldVal = await oldValGetter();
      if (oldVal !== void 0) {
        ps.push(
          indexValue(
            lc,
            idx.map,
            Remove,
            key,
            oldVal,
            idx.meta.definition.jsonPointer,
            idx.meta.definition.allowEmpty ?? false
          )
        );
      }
      if (newVal !== void 0) {
        ps.push(
          indexValue(
            lc,
            idx.map,
            Add,
            key,
            newVal,
            idx.meta.definition.jsonPointer,
            idx.meta.definition.allowEmpty ?? false
          )
        );
      }
    }
  }
  await Promise.all(ps);
}
function readIndexesForWrite(commit, dagWrite, formatVersion) {
  const m = /* @__PURE__ */ new Map();
  for (const index of commit.indexes) {
    m.set(
      index.definition.name,
      new IndexWrite(
        index,
        new BTreeWrite(dagWrite, formatVersion, index.valueHash)
      )
    );
  }
  return m;
}
async function createIndexBTree(lc, dagWrite, valueMap, prefix, jsonPointer, allowEmpty, formatVersion) {
  const indexMap = new BTreeWrite(dagWrite, formatVersion);
  for await (const entry of valueMap.scan(prefix)) {
    const key = entry[0];
    if (!key.startsWith(prefix)) {
      break;
    }
    await indexValue(
      lc,
      indexMap,
      Add,
      key,
      entry[1],
      jsonPointer,
      allowEmpty
    );
  }
  return indexMap;
}
export {
  Write,
  createIndexBTree,
  newWriteLocal,
  newWriteSnapshotDD31,
  readIndexesForWrite,
  updateIndexes
};
//# sourceMappingURL=write.js.map
