const EMPTY_STRING = '';
const strSplit = (str, separator = EMPTY_STRING, limit) =>
  str.split(separator, limit);

const getIfNotFunction =
  (predicate = isNullish) =>
  (value, then, otherwise) =>
    predicate(value) ? otherwise?.() : then(value);
const GLOBAL = globalThis;
const math = Math;
const mathMax = math.max;
const mathFloor = math.floor;
const isInstanceOf = (thing, cls) => thing instanceof cls;
const isNullish = (thing) => thing == null;
const isUndefined = (thing) => thing === void 0;
const ifNotUndefined = getIfNotFunction(isUndefined);

const arrayForEach = (array, cb) => array.forEach(cb);
const arrayMap = (array, cb) => array.map(cb);
const arrayReduce = (array, cb, initial) => array.reduce(cb, initial);

const object = Object;
const objEntries = object.entries;

const mapNew = (entries) => new Map(entries);
const mapGet = (map, key) => map?.get(key);

const MASK6 = 63;
const ENCODE = /* @__PURE__ */ strSplit(
  '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz',
);
const DECODE = /* @__PURE__ */ mapNew(
  /* @__PURE__ */ arrayMap(ENCODE, (char, index) => [char, index]),
);
const encode = (num) => ENCODE[num & MASK6];
const decode = (str, pos) => mapGet(DECODE, str[pos]) ?? 0;
const getRandomValues = GLOBAL.crypto
  ? (array) => GLOBAL.crypto.getRandomValues(array)
  : /* istanbul ignore next */
    (array) => arrayMap(array, () => mathFloor(math.random() * 256));
const getUniqueId = (length = 16) =>
  arrayReduce(
    getRandomValues(new Uint8Array(length)),
    (uniqueId, number) => uniqueId + encode(number),
    EMPTY_STRING,
  );

const jsonString = JSON.stringify;
const jsonStringWithMap = (obj) =>
  jsonString(obj, (_key, value) =>
    isInstanceOf(value, Map) ? object.fromEntries([...value]) : value,
  );

const textEncoder = /* @__PURE__ */ new GLOBAL.TextEncoder();
const getHash = (string) => {
  let hash = 2166136261;
  arrayForEach(textEncoder.encode(string), (char) => {
    hash ^= char;
    hash +=
      (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
  });
  return hash >>> 0;
};
const addOrRemoveHash = (hash1, hash2) => (hash1 ^ hash2) >>> 0;
const getValuesHash = (valueHashes) =>
  arrayReduce(
    objEntries(valueHashes),
    (valuesHash, [valueId, valueHash]) =>
      addOrRemoveHash(
        valuesHash,
        getValueInValuesHash(valueId, valueHash) ^
          getValueInValuesHash(valueId, 0),
        // legacy v5; remove in v7
      ),
    0,
    // legacy v5; valuesHlc in v7?
  );
const getValueInValuesHash = (valueId, valueHash) =>
  getHash(valueId + ':' + valueHash);
const getValueHash = (value, valueHlc) =>
  getHash(jsonStringWithMap(value ?? null) + ':' + valueHlc);
const getCellHash = getValueHash;
const getCellInRowHash = getValueInValuesHash;
const getRowHash = getValuesHash;
const getRowInTableHash = getValueInValuesHash;
const getTableHash = (rowHashes) =>
  // alias to getValuesHash in v7
  arrayReduce(
    objEntries(rowHashes),
    (valuesHash, [rowId, rowHash]) =>
      addOrRemoveHash(valuesHash, getValueInValuesHash(rowId, rowHash)),
    0,
    // legacy v5; rowHlc in v7?
  );
const getTableInTablesHash = getValueInValuesHash;
const getTablesHash = getTableHash;

const SHIFT36 = 2 ** 36;
const SHIFT30 = 2 ** 30;
const SHIFT24 = 2 ** 24;
const SHIFT18 = 2 ** 18;
const SHIFT12 = 2 ** 12;
const SHIFT6 = 2 ** 6;
const getClientIdFromUniqueId = (uniqueId) => {
  const clientHash30 = getHash(uniqueId);
  return (
    encode(clientHash30 / SHIFT24) +
    encode(clientHash30 / SHIFT18) +
    encode(clientHash30 / SHIFT12) +
    encode(clientHash30 / SHIFT6) +
    encode(clientHash30)
  );
};
const getHlcFunctions = (uniqueId, getNow = Date.now) => {
  let lastLogicalTime = 0;
  let lastCounter = -1;
  const thisClientId = ifNotUndefined(uniqueId, getClientIdFromUniqueId, () =>
    getUniqueId(5),
  );
  const getNextHlc = () => {
    seenHlc();
    return encodeHlc(lastLogicalTime, ++lastCounter);
  };
  const seenHlc = (hlc) => {
    const previousLogicalTime = lastLogicalTime;
    const [remoteLogicalTime, remoteCounter] =
      isUndefined(hlc) || hlc == '' ? [0, 0] : decodeHlc(hlc);
    lastLogicalTime = mathMax(previousLogicalTime, remoteLogicalTime, getNow());
    lastCounter =
      lastLogicalTime == previousLogicalTime
        ? lastLogicalTime == remoteLogicalTime
          ? mathMax(lastCounter, remoteCounter)
          : lastCounter
        : lastLogicalTime == remoteLogicalTime
          ? remoteCounter
          : -1;
  };
  const encodeHlc = (logicalTime42, counter24, clientId) =>
    encode(logicalTime42 / SHIFT36) +
    encode(logicalTime42 / SHIFT30) +
    encode(logicalTime42 / SHIFT24) +
    encode(logicalTime42 / SHIFT18) +
    encode(logicalTime42 / SHIFT12) +
    encode(logicalTime42 / SHIFT6) +
    encode(logicalTime42) +
    encode(counter24 / SHIFT18) +
    encode(counter24 / SHIFT12) +
    encode(counter24 / SHIFT6) +
    encode(counter24) +
    (isUndefined(clientId) ? thisClientId : getClientIdFromUniqueId(clientId));
  const decodeHlc = (hlc16) => [
    decode(hlc16, 0) * SHIFT36 +
      decode(hlc16, 1) * SHIFT30 +
      decode(hlc16, 2) * SHIFT24 +
      decode(hlc16, 3) * SHIFT18 +
      decode(hlc16, 4) * SHIFT12 +
      decode(hlc16, 5) * SHIFT6 +
      decode(hlc16, 6),
    decode(hlc16, 7) * SHIFT18 +
      decode(hlc16, 8) * SHIFT12 +
      decode(hlc16, 9) * SHIFT6 +
      decode(hlc16, 10),
    hlc16.slice(11),
  ];
  const getLastLogicalTime = () => lastLogicalTime;
  const getLastCounter = () => lastCounter;
  const getClientId = () => thisClientId;
  return [
    getNextHlc,
    seenHlc,
    encodeHlc,
    decodeHlc,
    getLastLogicalTime,
    getLastCounter,
    getClientId,
  ];
};

const defaultSorter = (sortKey1, sortKey2) =>
  (sortKey1 ?? 0) < (sortKey2 ?? 0) ? -1 : 1;

export {
  addOrRemoveHash,
  defaultSorter,
  getCellHash,
  getCellInRowHash,
  getHash,
  getHlcFunctions,
  getRowHash,
  getRowInTableHash,
  getTableHash,
  getTableInTablesHash,
  getTablesHash,
  getUniqueId,
  getValueHash,
  getValueInValuesHash,
  getValuesHash,
};
