import { assertObject, assertNumber, assertString, unreachable, assertArray, assertBoolean, assert } from "../../../shared/src/asserts.js";
import { assertJSONValue } from "../../../shared/src/json.js";
import { isProd } from "../../../shared/src/config.js";
import { compareCookies } from "../cookies.js";
import { toRefs } from "../dag/chunk.js";
import { mustGetHeadHash } from "../dag/store.js";
import { assertDeepFrozen, deepFreeze } from "../frozen-json.js";
import { assertHash } from "../hash.js";
import { LocalDD31, SnapshotDD31 } from "./meta-type-enum.js";
const DEFAULT_HEAD_NAME = "main";
function commitIsLocalDD31(commit) {
  return isLocalMetaDD31(commit.meta);
}
function commitIsLocal(commit) {
  return commitIsLocalDD31(commit);
}
function commitIsSnapshot(commit) {
  return isSnapshotMetaDD31(commit.meta);
}
class Commit {
  chunk;
  constructor(chunk) {
    this.chunk = chunk;
  }
  get meta() {
    return this.chunk.data.meta;
  }
  get valueHash() {
    return this.chunk.data.valueHash;
  }
  getMutationID(clientID, dagRead) {
    return getMutationID(clientID, dagRead, this.meta);
  }
  async getNextMutationID(clientID, dagRead) {
    return await this.getMutationID(clientID, dagRead) + 1;
  }
  get indexes() {
    return this.chunk.data.indexes;
  }
}
async function getMutationID(clientID, dagRead, meta) {
  switch (meta.type) {
    case SnapshotDD31:
      return meta.lastMutationIDs[clientID] ?? 0;
    case LocalDD31: {
      if (meta.clientID === clientID) {
        return meta.mutationID;
      }
      const { basisHash } = meta;
      const basisCommit = await commitFromHash(basisHash, dagRead);
      return getMutationID(clientID, dagRead, basisCommit.meta);
    }
    default:
      unreachable();
  }
}
async function localMutations(fromCommitHash, dagRead) {
  const commits = await commitChain(fromCommitHash, dagRead);
  return commits.filter((c) => commitIsLocal(c));
}
async function localMutationsDD31(fromCommitHash, dagRead) {
  const commits = await commitChain(fromCommitHash, dagRead);
  return commits.filter((c) => commitIsLocalDD31(c));
}
async function localMutationsGreaterThan(commit, mutationIDLimits, dagRead) {
  const commits = [];
  const remainingMutationIDLimits = new Map(Object.entries(mutationIDLimits));
  while (!commitIsSnapshot(commit) && remainingMutationIDLimits.size > 0) {
    if (commitIsLocalDD31(commit)) {
      const { meta } = commit;
      const mutationIDLowerLimit = remainingMutationIDLimits.get(meta.clientID);
      if (mutationIDLowerLimit !== void 0) {
        if (meta.mutationID <= mutationIDLowerLimit) {
          remainingMutationIDLimits.delete(meta.clientID);
        } else {
          commits.push(commit);
        }
      }
    }
    const { basisHash } = commit.meta;
    if (basisHash === null) {
      throw new Error(`Commit ${commit.chunk.hash} has no basis`);
    }
    commit = await commitFromHash(basisHash, dagRead);
  }
  return commits;
}
async function baseSnapshotFromHead(name, dagRead) {
  const hash = await dagRead.getHead(name);
  assert(hash, `Missing head ${name}`);
  return baseSnapshotFromHash(hash, dagRead);
}
async function baseSnapshotHashFromHash(hash, dagRead) {
  return (await baseSnapshotFromHash(hash, dagRead)).chunk.hash;
}
async function baseSnapshotFromHash(hash, dagRead) {
  const commit = await commitFromHash(hash, dagRead);
  return baseSnapshotFromCommit(commit, dagRead);
}
async function baseSnapshotFromCommit(commit, dagRead) {
  while (!commitIsSnapshot(commit)) {
    const { meta } = commit;
    if (isLocalMetaDD31(meta)) {
      commit = await commitFromHash(meta.baseSnapshotHash, dagRead);
    } else {
      const { basisHash } = meta;
      if (basisHash === null) {
        throw new Error(`Commit ${commit.chunk.hash} has no basis`);
      }
      commit = await commitFromHash(basisHash, dagRead);
    }
  }
  return commit;
}
function snapshotMetaParts(c, clientID) {
  const m = c.meta;
  const lmid = m.lastMutationIDs[clientID] ?? 0;
  return [lmid, m.cookieJSON];
}
function compareCookiesForSnapshots(a, b) {
  return compareCookies(a.meta.cookieJSON, b.meta.cookieJSON);
}
async function commitChain(fromCommitHash, dagRead) {
  let commit = await commitFromHash(fromCommitHash, dagRead);
  const commits = [];
  while (!commitIsSnapshot(commit)) {
    const { meta } = commit;
    const { basisHash } = meta;
    if (basisHash === null) {
      throw new Error(`Commit ${commit.chunk.hash} has no basis`);
    }
    commits.push(commit);
    commit = await commitFromHash(basisHash, dagRead);
  }
  commits.push(commit);
  return commits;
}
async function commitFromHash(hash, dagRead) {
  const chunk = await dagRead.mustGetChunk(hash);
  return fromChunk(chunk);
}
async function commitFromHead(name, dagRead) {
  const hash = await mustGetHeadHash(name, dagRead);
  return commitFromHash(hash, dagRead);
}
function assertLocalMetaDD31(v) {
  assertString(v.clientID);
  assertNumber(v.mutationID);
  assertString(v.mutatorName);
  if (!v.mutatorName) {
    throw new Error("Missing mutator name");
  }
  assertJSONValue(v.mutatorArgsJSON);
  if (v.originalHash !== null) {
    assertHash(v.originalHash);
  }
  assertNumber(v.timestamp);
}
function isLocalMetaDD31(meta) {
  return meta.type === LocalDD31;
}
function assertSnapshotMetaDD31(v) {
  if (v.basisHash !== null) {
    assertHash(v.basisHash);
  }
  assertJSONValue(v.cookieJSON);
  assertLastMutationIDs(v.lastMutationIDs);
}
function assertLastMutationIDs(v) {
  assertObject(v);
  for (const e of Object.values(v)) {
    assertNumber(e);
  }
}
function assertSnapshotCommitDD31(c) {
  assertSnapshotMetaDD31(c.meta);
}
function isSnapshotMetaDD31(meta) {
  return meta.type === SnapshotDD31;
}
function assertMeta(v) {
  assertObject(v);
  assertDeepFrozen(v);
  if (v.basisHash !== null) {
    assertString(v.basisHash);
  }
  assertNumber(v.type);
  switch (v.type) {
    case LocalDD31:
      assertLocalMetaDD31(v);
      break;
    case SnapshotDD31:
      assertSnapshotMetaDD31(v);
      break;
    default:
      throw new Error(`Invalid enum value ${v.type}`);
  }
}
function chunkIndexDefinitionEqualIgnoreName(a, b) {
  return a.jsonPointer === b.jsonPointer && (a.allowEmpty ?? false) === (b.allowEmpty ?? false) && a.keyPrefix === b.keyPrefix;
}
function assertChunkIndexDefinition(v) {
  assertObject(v);
  assertDeepFrozen(v);
  assertString(v.name);
  assertString(v.keyPrefix);
  assertString(v.jsonPointer);
  if (v.allowEmpty !== void 0) {
    assertBoolean(v.allowEmpty);
  }
}
function toChunkIndexDefinition(name, indexDefinition) {
  return {
    name,
    keyPrefix: indexDefinition.prefix ?? "",
    jsonPointer: indexDefinition.jsonPointer,
    allowEmpty: indexDefinition.allowEmpty ?? false
  };
}
function assertIndexRecord(v) {
  assertObject(v);
  assertDeepFrozen(v);
  assertChunkIndexDefinition(v.definition);
  assertString(v.valueHash);
}
function assertIndexRecords(v) {
  assertArray(v);
  assertDeepFrozen(v);
  for (const ir of v) {
    assertIndexRecord(ir);
  }
}
function newLocalDD31(createChunk, basisHash, baseSnapshotHash, mutationID, mutatorName, mutatorArgsJSON, originalHash, valueHash, indexes, timestamp, clientID) {
  const meta = {
    type: LocalDD31,
    basisHash,
    baseSnapshotHash,
    mutationID,
    mutatorName,
    mutatorArgsJSON,
    originalHash,
    timestamp,
    clientID
  };
  return commitFromCommitData(
    createChunk,
    makeCommitData(meta, valueHash, indexes)
  );
}
function newSnapshotDD31(createChunk, basisHash, lastMutationIDs, cookieJSON, valueHash, indexes) {
  return commitFromCommitData(
    createChunk,
    newSnapshotCommitDataDD31(
      basisHash,
      lastMutationIDs,
      cookieJSON,
      valueHash,
      indexes
    )
  );
}
function newSnapshotCommitDataDD31(basisHash, lastMutationIDs, cookieJSON, valueHash, indexes) {
  const meta = {
    type: SnapshotDD31,
    basisHash,
    lastMutationIDs,
    cookieJSON
  };
  return makeCommitData(meta, valueHash, indexes);
}
function fromChunk(chunk) {
  validateChunk(chunk);
  return new Commit(chunk);
}
function commitFromCommitData(createChunk, data) {
  return new Commit(createChunk(data, getRefs(data)));
}
function getRefs(data) {
  const refs = /* @__PURE__ */ new Set();
  refs.add(data.valueHash);
  const { meta } = data;
  switch (meta.type) {
    case LocalDD31:
      meta.basisHash && refs.add(meta.basisHash);
      break;
    case SnapshotDD31:
      break;
    default:
      unreachable();
  }
  for (const index of data.indexes) {
    refs.add(index.valueHash);
  }
  return toRefs(refs);
}
function makeCommitData(meta, valueHash, indexes) {
  return deepFreeze({
    meta,
    valueHash,
    indexes
  });
}
function assertCommitData(v) {
  if (isProd) {
    return;
  }
  assertObject(v);
  assertDeepFrozen(v);
  assertMeta(v.meta);
  assertString(v.valueHash);
  assertIndexRecords(v.indexes);
}
function validateChunk(chunk) {
  const { data } = chunk;
  assertCommitData(data);
  const seen = /* @__PURE__ */ new Set();
  for (const index of data.indexes) {
    const { name } = index.definition;
    if (seen.has(name)) {
      throw new Error(`Duplicate index ${name}`);
    }
    seen.add(name);
  }
}
export {
  Commit,
  DEFAULT_HEAD_NAME,
  assertCommitData,
  assertLocalMetaDD31,
  assertSnapshotCommitDD31,
  assertSnapshotMetaDD31,
  baseSnapshotFromCommit,
  baseSnapshotFromHash,
  baseSnapshotFromHead,
  baseSnapshotHashFromHash,
  chunkIndexDefinitionEqualIgnoreName,
  commitChain,
  commitFromHash,
  commitFromHead,
  commitIsLocal,
  commitIsLocalDD31,
  commitIsSnapshot,
  compareCookiesForSnapshots,
  fromChunk,
  getMutationID,
  getRefs,
  isLocalMetaDD31,
  localMutations,
  localMutationsDD31,
  localMutationsGreaterThan,
  makeCommitData,
  newLocalDD31,
  newSnapshotCommitDataDD31,
  newSnapshotDD31,
  snapshotMetaParts,
  toChunkIndexDefinition
};
//# sourceMappingURL=commit.js.map
