import { assert, assertObject } from "../../../shared/src/asserts.js";
import { readonlyObject, readonlyRecord, readonlyArray, assert as assert$1 } from "../../../shared/src/valita.js";
import { toRefs } from "../dag/chunk.js";
import { deepFreeze } from "../frozen-json.js";
import { hashSchema } from "../hash.js";
import { indexDefinitionsSchema, indexDefinitionsEqual } from "../index-defs.js";
import { boolean, record, number, string } from "@badrap/valita";
const clientGroupSchema = readonlyObject({
  /**
   * The hash of the commit in the perdag last persisted to this client group.
   * Should only be updated by clients assigned to this client group.
   */
  headHash: hashSchema,
  /**
   * Set of mutator names common to all clients assigned to this client group.
   */
  mutatorNames: readonlyArray(string()),
  /**
   * Index definitions common to all clients assigned to this client group.
   */
  indexes: indexDefinitionsSchema,
  /**
   * The highest mutation ID of every client assigned to this client group.
   * Should only be updated by clients assigned to this client group. Read by
   * other clients to determine if there are unacknowledged pending mutations
   * for them to try to recover. This is redundant with information in the
   * commit graph at `headHash`, but allows other clients to determine if there
   * are unacknowledged pending mutations without having to load the commit
   * graph.
   */
  mutationIDs: readonlyRecord(number()),
  /**
   * The highest lastMutationID received from the server for every client
   * assigned to this client group.
   *
   * Should be updated by the clients assigned to this client group whenever
   * they persist to this client group. Read by other clients to determine if
   * there are unacknowledged pending mutations for them to recover and
   * *updated* by other clients upon successfully recovering pending mutations
   * to avoid redundant pushes of pending mutations.
   *
   * Note: This will be the same as the `lastMutationIDs` of the base snapshot
   * of the client group's commit graph when written by clients assigned to this
   * client group.  However, when written by another client recovering mutations
   * it may be different because the other client does not update the commit
   * graph.
   */
  lastServerAckdMutationIDs: record(number()),
  /**
   * If the server deletes this client group it can signal that the client group
   * was deleted. If that happens we mark this client group as disabled so that
   * we do not use it again when creating new clients.
   */
  disabled: boolean()
});
const CLIENT_GROUPS_HEAD_NAME = "client-groups";
function assertClientGroup(value) {
  assert$1(value, clientGroupSchema);
}
function chunkDataToClientGroupMap(chunkData) {
  assertObject(chunkData);
  const clientGroups = /* @__PURE__ */ new Map();
  for (const [key, value] of Object.entries(chunkData)) {
    if (value !== void 0) {
      assertClientGroup(value);
      clientGroups.set(key, value);
    }
  }
  return clientGroups;
}
function clientGroupMapToChunkData(clientGroups, dagWrite) {
  const chunkData = {};
  for (const [clientGroupID, clientGroup] of clientGroups.entries()) {
    dagWrite.assertValidHash(clientGroup.headHash);
    chunkData[clientGroupID] = {
      ...clientGroup,
      mutatorNames: [...clientGroup.mutatorNames.values()]
    };
  }
  return deepFreeze(chunkData);
}
async function getClientGroupsAtHash(hash, dagRead) {
  const chunk = await dagRead.getChunk(hash);
  return chunkDataToClientGroupMap(chunk?.data);
}
async function getClientGroups(dagRead) {
  const hash = await dagRead.getHead(CLIENT_GROUPS_HEAD_NAME);
  if (!hash) {
    return /* @__PURE__ */ new Map();
  }
  return getClientGroupsAtHash(hash, dagRead);
}
async function setClientGroups(clientGroups, dagWrite) {
  const currClientGroups = await getClientGroups(dagWrite);
  for (const [clientGroupID, clientGroup] of clientGroups) {
    const currClientGroup = currClientGroups.get(clientGroupID);
    validateClientGroupUpdate(clientGroup, currClientGroup);
  }
  return setValidatedClientGroups(clientGroups, dagWrite);
}
async function setClientGroup(clientGroupID, clientGroup, dagWrite) {
  const currClientGroups = await getClientGroups(dagWrite);
  const currClientGroup = currClientGroups.get(clientGroupID);
  validateClientGroupUpdate(clientGroup, currClientGroup);
  const newClientGroups = new Map(currClientGroups);
  newClientGroups.set(clientGroupID, clientGroup);
  return setValidatedClientGroups(newClientGroups, dagWrite);
}
function validateClientGroupUpdate(clientGroup, currClientGroup) {
  const mutatorNamesSet = new Set(clientGroup.mutatorNames);
  assert(
    mutatorNamesSet.size === clientGroup.mutatorNames.length,
    "A client group's mutatorNames must be a set."
  );
  if (currClientGroup !== void 0) {
    assert(
      indexDefinitionsEqual(currClientGroup.indexes, clientGroup.indexes),
      "A client group's index definitions must never change."
    );
    assert(
      mutatorNamesEqual(mutatorNamesSet, currClientGroup.mutatorNames),
      "A client group's mutatorNames must never change."
    );
  }
}
async function setValidatedClientGroups(clientGroups, dagWrite) {
  const chunkData = clientGroupMapToChunkData(clientGroups, dagWrite);
  const refs = /* @__PURE__ */ new Set();
  for (const clientGroup of clientGroups.values()) {
    refs.add(clientGroup.headHash);
  }
  const chunk = dagWrite.createChunk(chunkData, toRefs(refs));
  await dagWrite.putChunk(chunk);
  await dagWrite.setHead(CLIENT_GROUPS_HEAD_NAME, chunk.hash);
  return clientGroups;
}
function mutatorNamesEqual(mutatorNamesSet, mutatorNames) {
  if (mutatorNames.length !== mutatorNamesSet.size) {
    return false;
  }
  for (const mutatorName of mutatorNames) {
    if (!mutatorNamesSet.has(mutatorName)) {
      return false;
    }
  }
  return true;
}
async function getClientGroup(id, dagRead) {
  const clientGroups = await getClientGroups(dagRead);
  return clientGroups.get(id);
}
function clientGroupHasPendingMutations(clientGroup) {
  for (const [clientID, mutationID] of Object.entries(
    clientGroup.mutationIDs
  )) {
    const lastServerAckdMutationID = clientGroup.lastServerAckdMutationIDs[clientID];
    if (lastServerAckdMutationID === void 0 && mutationID !== 0 || lastServerAckdMutationID < mutationID) {
      return true;
    }
  }
  return false;
}
async function disableClientGroup(clientGroupID, dagWrite) {
  const clientGroup = await getClientGroup(clientGroupID, dagWrite);
  if (!clientGroup) {
    return;
  }
  const disabledClientGroup = {
    ...clientGroup,
    disabled: true
  };
  await setClientGroup(clientGroupID, disabledClientGroup, dagWrite);
}
export {
  CLIENT_GROUPS_HEAD_NAME,
  clientGroupHasPendingMutations,
  disableClientGroup,
  getClientGroup,
  getClientGroups,
  mutatorNamesEqual,
  setClientGroup,
  setClientGroups
};
//# sourceMappingURL=client-groups.js.map
