import { Blake2128, Binary } from '@polkadot-api/substrate-bindings';
import { logger } from './logger.js';

const log = logger.child({ module: "storage" });
const TRIE_SIZE = 16;
const emptyHashBin = Blake2128(new Uint8Array());
const emptyHash = Array.from(emptyHashBin);
const getHash = (children, value) => {
  if (!value) {
    const realChildren = children.filter((v) => !!v);
    if (realChildren.length === 0) return emptyHashBin;
    if (realChildren.length === 1) return realChildren[0].hash;
  }
  const childHashes = new Array(TRIE_SIZE).fill(0).flatMap(
    (_, i) => children[i] ? Array.from(children[i].hash) : emptyHash
  );
  return Blake2128(
    new Uint8Array([
      ...childHashes,
      value ? 1 : 0,
      ...value ?? new Uint8Array()
    ])
  );
};
const getNibble = (key, offset) => {
  const byte = key[Math.floor(offset / 2)];
  if (byte == null) return null;
  return offset % 2 === 0 ? byte >> 4 : byte & 15;
};
const setNibble = (key, offset, value) => offset % 2 === 0 ? new Uint8Array([...key, value << 4]) : new Uint8Array(key.map((v, j) => j === key.length - 1 ? v | value : v));
const createRoot = () => {
  const children = new Array(TRIE_SIZE);
  return {
    hash: getHash(children),
    children
  };
};
const insertValue = (root, key, nibbles, value, offset = 0) => {
  if (nibbles === offset)
    return {
      ...root,
      hash: getHash(root.children, value),
      value
    };
  const nibble = getNibble(key, offset);
  if (nibble == null) throw new Error("Key overflow");
  const child = root.children[nibble];
  if (!child) {
    const children2 = [...root.children];
    children2[nibble] = createNodeValue(key, nibbles, value, offset + 1);
    return {
      hash: getHash(children2, root.value),
      children: children2,
      value: root.value,
      exhaustive: root.exhaustive
    };
  }
  const children = [...root.children];
  children[nibble] = insertValue(child, key, nibbles, value, offset + 1);
  return {
    hash: getHash(children, root.value),
    children,
    value: root.value,
    exhaustive: root.exhaustive
  };
};
const createNodeValue = (key, nibbles, value, offset) => {
  if (nibbles === offset)
    return {
      hash: getHash([], value),
      children: new Array(TRIE_SIZE),
      value
    };
  const nibble = getNibble(key, offset);
  if (nibble == null) throw new Error("Key overflow");
  const child = createNodeValue(key, nibbles, value, offset + 1);
  const children = new Array(TRIE_SIZE);
  children[nibble] = child;
  return {
    hash: getHash(children),
    children
  };
};
const deleteValue = (root, key, nibbles, offset = 0) => {
  if (offset === nibbles)
    return root.value ? {
      ...root,
      // Soft delete to have it marked as removed, otherwise we'd go back to the source
      value: null,
      hash: getHash(root.children, null)
    } : root;
  const nibble = getNibble(key, offset);
  if (nibble == null) throw new Error("Key overflow");
  const child = root.children[nibble];
  if (!child) return root;
  const newChild = deleteValue(child, key, offset + 1);
  if (newChild === child) return root;
  const children = [...root.children];
  children[nibble] = newChild;
  return {
    hash: getHash(children, root.value),
    children,
    value: root.value,
    exhaustive: root.exhaustive
  };
};
const getNode = (root, key, nibbles, offset = 0) => {
  if (offset === 0 && Binary.toHex(key) === "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96") {
    log.debug("Block.Weight requested");
  }
  if (offset === nibbles) return root;
  const nibble = getNibble(key, offset);
  if (nibble == null) throw new Error("Key overflow");
  const child = root.children[nibble];
  if (!child) return null;
  return getNode(child, key, nibbles, offset + 1);
};
const getDiff = (base, fallback, other, prefix = new Uint8Array(), nibbles = 0) => {
  const insert = {
    children: new Array(TRIE_SIZE)
  };
  const prev = {
    children: new Array(TRIE_SIZE)
  };
  let deleteNodes = new Array();
  let deleteValues = new Array();
  for (let i = 0; i < TRIE_SIZE; i++) {
    const fallbackNode = fallback?.children[i];
    const baseNode = base.children[i];
    const completeBase = baseNode ?? fallbackNode;
    const otherNode = other.children[i];
    const childPrefix = setNibble(prefix, nibbles, i);
    if (otherNode) {
      if (completeBase) {
        if (arrU8Eq(otherNode.hash, completeBase.hash)) continue;
        const childDiff = getDiff(
          completeBase,
          baseNode ? fallbackNode ?? null : null,
          otherNode,
          childPrefix,
          nibbles + 1
        );
        insert.children[i] = childDiff.insert;
        prev.children[i] = childDiff.prev;
        deleteNodes = [...deleteNodes, ...childDiff.deleteNodes];
        deleteValues = [...deleteValues, ...childDiff.deleteValues];
      } else {
        insert.children[i] = otherNode;
      }
    } else {
      if (completeBase) {
        deleteNodes.push({ key: prefix, nibbles: nibbles + 1 });
        prev.children[i] = completeBase;
      }
    }
  }
  const completeValue = base.value ?? fallback?.value;
  if (other.value) {
    if (!completeValue || completeValue && !arrU8Eq(completeValue, other.value)) {
      insert.value = other.value;
      prev.value = completeValue;
    }
  } else if (completeValue) {
    deleteValues.push({
      key: prefix,
      nibbles
    });
    prev.value = completeValue;
  }
  return {
    insert: {
      ...insert,
      hash: getHash(insert.children, insert.value)
    },
    prev: {
      ...prev,
      hash: getHash(prev.children, prev.value)
    },
    deleteNodes,
    deleteValues
  };
};
const getDescendantNodes = (node, prefix, nibbles) => {
  let result = new Array();
  if (node.value) {
    result.push({ key: prefix, nibbles, node });
  }
  node.children.forEach((child, i) => {
    if (!child) return;
    const childPrefix = setNibble(prefix, nibbles, i);
    result = [
      ...result,
      ...getDescendantNodes(child, childPrefix, nibbles + 1)
    ];
  });
  return result;
};
const getSoftDeletedDescendantKeys = (node, prefix, nibbles) => {
  let result = new Array();
  if (node.value === null) {
    result.push(prefix);
  }
  node.children.forEach((child, i) => {
    if (!child) return;
    const childPrefix = setNibble(prefix, nibbles, i);
    result = [
      ...result,
      ...getSoftDeletedDescendantKeys(child, childPrefix, nibbles + 1)
    ];
  });
  return result;
};
const forEachDescendant = (root, cb) => {
  root.children.forEach((child) => {
    if (!child) return;
    cb(child);
    forEachDescendant(child, cb);
  });
};
const arrU8Eq = (a, b) => a.length === b.length && a.every((v, i) => b[i] === v);

export { createRoot, deleteValue, forEachDescendant, getDescendantNodes, getDiff, getNode, getSoftDeletedDescendantKeys, insertValue };
//# sourceMappingURL=storage.js.map
