'use strict';

var scale = require('@pezkuwi/bizinikiwi-bindings');
var utils = require('@pezkuwi/papi-utils');

function _interopNamespaceDefault(e) {
  var n = Object.create(null);
  if (e) {
    Object.keys(e).forEach(function (k) {
      if (k !== 'default') {
        var d = Object.getOwnPropertyDescriptor(e, k);
        Object.defineProperty(n, k, d.get ? d : {
          enumerable: true,
          get: function () { return e[k]; }
        });
      }
    });
  }
  n.default = e;
  return Object.freeze(n);
}

var scale__namespace = /*#__PURE__*/_interopNamespaceDefault(scale);

const isBytes = (value, nBytes) => value.type === "array" && value.len === nBytes && value.value.type === "primitive" && value.value.value === "u8";
const _void = { type: "void" };
const _denormalizeLookup = (lookupData, customMap = () => null) => {
  const lookups = /* @__PURE__ */ new Map();
  const from = /* @__PURE__ */ new Set();
  const withCache = (fn) => {
    return (id) => {
      let entry = lookups.get(id);
      if (entry) return entry;
      if (from.has(id)) {
        const entry2 = {
          id
        };
        lookups.set(id, entry2);
        return entry2;
      }
      from.add(id);
      const value = fn(id);
      entry = lookups.get(id);
      if (entry) {
        Object.assign(entry, value);
      } else {
        entry = {
          id,
          ...value
        };
        lookups.set(id, entry);
      }
      from.delete(id);
      return entry;
    };
  };
  let isAccountId32SearchOn = true;
  let isAccountId20SearchOn = true;
  const getLookupEntryDef = withCache((id) => {
    const custom = customMap(lookupData[id]);
    if (custom) return custom;
    const { def, path, params } = lookupData[id];
    if (def.tag === "composite") {
      if (def.value.length === 0) return _void;
      if (def.value.length === 1) {
        const inner = getLookupEntryDef(def.value[0].type);
        if (isAccountId32SearchOn && path.at(-1) === "AccountId32" && isBytes(inner, 32)) {
          isAccountId32SearchOn = false;
          return { type: "AccountId32" };
        }
        if (isAccountId20SearchOn && path.at(-1) === "AccountId20" && isBytes(inner, 20)) {
          isAccountId20SearchOn = false;
          return { type: "AccountId20" };
        }
        return inner;
      }
      return getComplexVar(def.value);
    }
    if (def.tag === "variant") {
      if (path.length === 1 && path[0] === "Option" && params.length === 1 && params[0].name === "T") {
        const value = getLookupEntryDef(params[0].type);
        return value.type === "void" ? (
          // Option<void> would return a Codec<undefined> which makes no sense
          // Therefore, we better treat it as a bool
          { type: "primitive", value: "bool" }
        ) : {
          type: "option",
          value
        };
      }
      if (path.length === 1 && path[0] === "Result" && params.length === 2 && params[0].name === "T" && params[1].name === "E") {
        return {
          type: "result",
          value: {
            ok: getLookupEntryDef(params[0].type),
            ko: getLookupEntryDef(params[1].type)
          }
        };
      }
      if (def.value.length === 0) return _void;
      const enumValue = {};
      const enumDocs = {};
      def.value.forEach((x) => {
        const key = x.name;
        enumDocs[key] = x.docs;
        if (x.fields.length === 0) {
          enumValue[key] = { ..._void, idx: x.index };
          return;
        }
        if (x.fields.length === 1 && !x.fields[0].name) {
          enumValue[key] = {
            type: "lookupEntry",
            value: getLookupEntryDef(x.fields[0].type),
            idx: x.index
          };
          return;
        }
        enumValue[key] = { ...getComplexVar(x.fields), idx: x.index };
      });
      return {
        type: "enum",
        value: enumValue,
        innerDocs: enumDocs
      };
    }
    if (def.tag === "sequence")
      return {
        type: "sequence",
        value: getLookupEntryDef(def.value)
      };
    if (def.tag === "array") {
      const { len } = def.value;
      const value = getLookupEntryDef(def.value.type);
      return !len || value.type === "void" ? _void : len > 1 ? {
        type: "array",
        value,
        len: def.value.len
      } : value;
    }
    if (def.tag === "tuple") {
      if (def.value.length === 0) return _void;
      return def.value.length > 1 ? getArrayOrTuple(
        def.value.map((x) => getLookupEntryDef(x)),
        def.value.map((x) => lookupData[x].docs)
      ) : getLookupEntryDef(def.value[0]);
    }
    if (def.tag === "primitive") {
      return {
        type: "primitive",
        value: def.value.tag
      };
    }
    if (def.tag === "compact") {
      const translated = getLookupEntryDef(def.value);
      if (translated.type === "void") return _void;
      const isBig = Number(translated.value.slice(1)) > 32;
      return {
        type: "compact",
        isBig,
        size: translated.value
      };
    }
    return {
      type: def.tag,
      isLSB: (lookupData[def.value.bitOrderType].path.at(-1) ?? "LSB").toUpperCase().startsWith("LSB")
    };
  });
  const getComplexVar = (input) => {
    let allKey = true;
    const values = {};
    const innerDocs = {};
    input.forEach((x, idx) => {
      allKey = allKey && !!x.name;
      const key = x.name || idx;
      const value = getLookupEntryDef(x.type);
      if (value.type !== "void") {
        values[key] = value;
        innerDocs[key] = x.docs;
      }
    });
    return allKey ? {
      type: "struct",
      value: values,
      innerDocs
    } : getArrayOrTuple(Object.values(values), Object.values(innerDocs));
  };
  const getArrayOrTuple = (values, innerDocs) => {
    if (values.every((v) => v.id === values[0].id) && innerDocs.every((doc) => !doc.length)) {
      const [value] = values;
      return value.type === "void" ? _void : {
        type: "array",
        value: values[0],
        len: values.length
      };
    }
    return {
      type: "tuple",
      value: values,
      innerDocs
    };
  };
  return getLookupEntryDef;
};
const denormalizeLookup = (lookupData) => _denormalizeLookup(lookupData);
const getLookupFn = (metadata) => {
  const getLookupEntryDef = _denormalizeLookup(metadata.lookup, ({ def }) => {
    if (def.tag === "composite") {
      const moduleErrorLength = getModuleErrorLength(def);
      if (moduleErrorLength) {
        return {
          type: "enum",
          innerDocs: {},
          value: Object.fromEntries(
            metadata.pallets.map((p) => [
              p.name,
              p.errors == null ? { ..._void, idx: p.index } : {
                type: "lookupEntry",
                value: getLookupEntryDef(p.errors.type),
                idx: p.index
              }
            ])
          ),
          byteLength: moduleErrorLength
        };
      }
    }
    return null;
  });
  function getModuleErrorLength(def) {
    const preChecks = def.value.length === 2 && def.value[0].name === "index" && def.value[1].name === "error";
    if (!preChecks) return null;
    const index = getLookupEntryDef(def.value[0].type);
    const error = getLookupEntryDef(def.value[1].type);
    return index.type === "primitive" && index.value === "u8" && error.type === "array" && error.value.type === "primitive" && error.value.value === "u8" ? 1 + error.len : null;
  }
  const getCall = () => {
    if ("call" in metadata.extrinsic) {
      return metadata.extrinsic.call;
    }
    const extrinsic = metadata.lookup[metadata.extrinsic.type];
    const call = extrinsic?.params.find((p) => p.name === "Call");
    return call?.type ?? null;
  };
  return Object.assign(getLookupEntryDef, { metadata, call: getCall() });
};

const withCache = (fn, onEnterCircular, onExitCircular) => (input, cache, stack, ...rest) => {
  const { id } = input;
  if (cache.has(id)) return cache.get(id);
  if (stack.has(id)) {
    const res = onEnterCircular(() => cache.get(id), input, ...rest);
    cache.set(id, res);
    return res;
  }
  stack.add(id);
  let result = fn(input, cache, stack, ...rest);
  stack.delete(id);
  if (cache.has(id))
    result = onExitCircular(result, cache.get(id), input, ...rest);
  cache.set(id, result);
  return result;
};

const _bytes = scale__namespace.Bin();
const _buildCodec = (input, cache, stack, _accountId) => {
  if (input.type === "primitive") return scale__namespace[input.value];
  if (input.type === "void") return scale__namespace._void;
  if (input.type === "AccountId32") return _accountId;
  if (input.type === "AccountId20") return scale__namespace.ethAccount;
  if (input.type === "compact")
    return input.isBig ? scale__namespace.compactBn : scale__namespace.compactNumber;
  if (input.type === "bitSequence") return scale__namespace.BitSeq(input.isLSB);
  const buildNextCodec = (nextInput) => buildCodec(nextInput, cache, stack, _accountId);
  const buildVector = (inner2, len) => {
    const innerCodec = buildNextCodec(inner2);
    return len ? scale__namespace.Vector(innerCodec, len) : scale__namespace.Vector(innerCodec);
  };
  const buildTuple = (value) => scale__namespace.Tuple(...value.map(buildNextCodec));
  const buildStruct = (value) => {
    const inner2 = Object.fromEntries(
      Object.entries(value).map(([key, value2]) => [key, buildNextCodec(value2)])
    );
    return scale__namespace.Struct(inner2);
  };
  if (input.type === "sequence" && input.value.type === "primitive" && input.value.value === "u8") {
    return _bytes;
  }
  if (input.type === "array") {
    if (input.value.type === "primitive" && input.value.value === "u8")
      return scale__namespace.Bin(input.len);
    return buildVector(input.value, input.len);
  }
  if (input.type === "sequence") return buildVector(input.value);
  if (input.type === "tuple") return buildTuple(input.value);
  if (input.type === "struct") return buildStruct(input.value);
  if (input.type === "option") return scale__namespace.Option(buildNextCodec(input.value));
  if (input.type === "result")
    return scale__namespace.Result(
      buildNextCodec(input.value.ok),
      buildNextCodec(input.value.ko)
    );
  const dependencies = Object.values(input.value).map((v) => {
    switch (v.type) {
      case "void":
        return scale__namespace._void;
      case "lookupEntry":
        return buildNextCodec(v.value);
      case "tuple":
        return buildTuple(v.value);
      case "struct":
        return buildStruct(v.value);
      case "array":
        return buildVector(v.value, v.len);
    }
  });
  const inner = Object.fromEntries(
    Object.keys(input.value).map((key, idx) => {
      return [key, dependencies[idx]];
    })
  );
  const indexes = Object.values(input.value).map((x) => x.idx);
  const areIndexesSorted = indexes.every((idx, i) => idx === i);
  const variantCodec = areIndexesSorted ? scale__namespace.Variant(inner) : scale__namespace.Variant(inner, indexes);
  return input.byteLength ? fixedSizeCodec(variantCodec, input.byteLength) : variantCodec;
};
const buildCodec = withCache(_buildCodec, scale__namespace.Self, (res) => res);
const getLookupCodecBuilder = (lookup, accountId = scale__namespace.AccountId()) => {
  const cache = /* @__PURE__ */ new Map();
  const buildDefinition = (id) => buildCodec(lookup(id), cache, /* @__PURE__ */ new Set(), accountId);
  return (id) => buildDefinition(id);
};
const fixedSizeCodec = (codec, size) => {
  const allBytes = scale__namespace.Bytes(size);
  return scale__namespace.createCodec(
    (value) => allBytes.enc(codec.enc(value)),
    (data) => codec.dec(allBytes.dec(data))
  );
};

const nullCodec = scale__namespace.enhanceCodec(
  scale__namespace._void,
  () => void 0,
  () => null
);
const getDynamicBuilder = (getLookupEntryDef) => {
  const { metadata } = getLookupEntryDef;
  let buildDefinition = getLookupCodecBuilder(getLookupEntryDef);
  const prefix = metadata.pallets.find((x) => x.name === "System")?.constants.find((x) => x.name === "SS58Prefix");
  let ss58Prefix;
  if (prefix) {
    try {
      const prefixVal = buildDefinition(prefix.type).dec(prefix.value);
      if (typeof prefixVal === "number") {
        ss58Prefix = prefixVal;
        buildDefinition = getLookupCodecBuilder(
          getLookupEntryDef,
          scale__namespace.AccountId(prefixVal)
        );
      }
    } catch (_) {
    }
  }
  const storagePallets = /* @__PURE__ */ new Map();
  const buildStorage = (pallet, entry) => {
    let storagePallet = storagePallets.get(pallet);
    if (!storagePallet)
      storagePallets.set(pallet, storagePallet = scale__namespace.Storage(pallet));
    const storageEntry = metadata.pallets.find((x) => x.name === pallet).storage.items.find((s) => s.name === entry);
    const withNullVoid = (codec) => codec === scale__namespace._void ? nullCodec : codec;
    const storageWithFallback = (len, value2, ...args) => {
      const keys = storagePallet(...args);
      const [, ...encodersWithHash] = args;
      return {
        args: scale__namespace.Tuple(...encodersWithHash.map(([codec]) => codec)),
        keys,
        value: value2,
        len,
        fallback: storageEntry.modifier === 1 ? value2.dec(storageEntry.fallback) : void 0
      };
    };
    if (storageEntry.type.tag === "plain")
      return storageWithFallback(
        0,
        withNullVoid(buildDefinition(storageEntry.type.value)),
        entry
      );
    const { key, value, hashers } = storageEntry.type.value;
    const val = withNullVoid(buildDefinition(value));
    const hashes = hashers.map((x) => scale__namespace[x.tag]);
    const hashArgs = (() => {
      if (hashes.length === 1) {
        return [[buildDefinition(key), hashes[0]]];
      }
      const keyDef = getLookupEntryDef(key);
      switch (keyDef.type) {
        case "array":
          return hashes.map((hash) => [buildDefinition(keyDef.value.id), hash]);
        case "tuple":
          return keyDef.value.map((x, idx) => [
            buildDefinition(x.id),
            hashes[idx]
          ]);
        default:
          throw new Error("Invalid key type");
      }
    })();
    return storageWithFallback(hashes.length, val, entry, ...hashArgs);
  };
  const buildEnumEntry = (entry) => {
    switch (entry.type) {
      case "void":
        return scale__namespace._void;
      case "lookupEntry":
        return buildDefinition(entry.value.id);
      case "tuple":
        return scale__namespace.Tuple(
          ...Object.values(entry.value).map((l) => buildDefinition(l.id))
        );
      case "struct":
        return scale__namespace.Struct(
          utils.mapObject(entry.value, (x) => buildDefinition(x.id))
        );
      case "array":
        return scale__namespace.Vector(buildDefinition(entry.value.id), entry.len);
    }
  };
  const buildConstant = (pallet, constantName) => {
    const storageEntry = metadata.pallets.find((x) => x.name === pallet).constants.find((s) => s.name === constantName);
    return buildDefinition(storageEntry.type);
  };
  const buildVariant = (type) => (pallet, name) => {
    const palletEntry = metadata.pallets.find((x) => x.name === pallet);
    const lookup = getLookupEntryDef(palletEntry[type].type);
    if (lookup.type !== "enum") throw null;
    const entry = lookup.value[name];
    return {
      location: [palletEntry.index, entry.idx],
      codec: buildEnumEntry(lookup.value[name])
    };
  };
  const buildViewFn = (pallet, entry) => {
    const fn = metadata.pallets.find((x) => x.name === pallet)?.viewFns.find((x) => x.name === entry);
    if (!fn) throw null;
    return {
      args: scale__namespace.Tuple(...fn.inputs.map((x) => buildDefinition(x.type))),
      value: buildDefinition(fn.output)
    };
  };
  const buildRuntimeCall = (api, method) => {
    const entry = metadata.apis.find((x) => x.name === api)?.methods.find((x) => x.name === method);
    if (!entry) throw null;
    return {
      args: scale__namespace.Tuple(...entry.inputs.map((x) => buildDefinition(x.type))),
      value: buildDefinition(entry.output)
    };
  };
  return {
    buildDefinition,
    buildStorage,
    buildEvent: buildVariant("events"),
    buildError: buildVariant("errors"),
    buildViewFn,
    buildRuntimeCall,
    buildCall: buildVariant("calls"),
    buildConstant,
    ss58Prefix
  };
};

function buildLookupGraph(lookupFn, lookupLength) {
  const result = /* @__PURE__ */ new Map();
  const visited = /* @__PURE__ */ new Set();
  const addEdge = (from, to) => {
    if (!result.has(from))
      result.set(from, {
        entry: lookupFn(from),
        backRefs: /* @__PURE__ */ new Set(),
        refs: /* @__PURE__ */ new Set()
      });
    if (!result.has(to))
      result.set(to, {
        entry: lookupFn(to),
        backRefs: /* @__PURE__ */ new Set(),
        refs: /* @__PURE__ */ new Set()
      });
    result.get(from).refs.add(to);
    result.get(to).backRefs.add(from);
  };
  for (let i = 0; i < lookupLength; i++) {
    const entry = lookupFn(i);
    if (i !== entry.id) {
      addEdge(i, entry.id);
    }
    if (visited.has(entry.id)) continue;
    visited.add(entry.id);
    switch (entry.type) {
      case "array":
      case "option":
      case "sequence":
        addEdge(entry.id, entry.value.id);
        break;
      case "enum":
        Object.values(entry.value).forEach((enumEntry) => {
          switch (enumEntry.type) {
            case "array":
            case "lookupEntry":
              addEdge(entry.id, enumEntry.value.id);
              break;
            case "struct":
            case "tuple":
              Object.values(enumEntry.value).forEach(
                (v) => addEdge(entry.id, v.id)
              );
              break;
          }
        });
        break;
      case "result":
        addEdge(entry.id, entry.value.ok.id);
        addEdge(entry.id, entry.value.ko.id);
        break;
      case "struct":
      case "tuple":
        Object.values(entry.value).forEach((v) => addEdge(entry.id, v.id));
        break;
    }
    if (!result.has(entry.id)) {
      result.set(entry.id, {
        backRefs: /* @__PURE__ */ new Set(),
        refs: /* @__PURE__ */ new Set(),
        entry
      });
    }
  }
  return result;
}
const subgraphCache = /* @__PURE__ */ new WeakMap();
function _getSubgraph(id, graph, result, cache) {
  if (result.has(id)) return;
  const node = graph.get(id);
  result.set(id, node);
  cache.set(id, result);
  node.refs.forEach((ref) => _getSubgraph(ref, graph, result, cache));
  node.backRefs.forEach((ref) => _getSubgraph(ref, graph, result, cache));
}
function getSubgraph(id, graph) {
  if (!subgraphCache.has(graph)) {
    subgraphCache.set(graph, /* @__PURE__ */ new Map());
  }
  const cache = subgraphCache.get(graph);
  if (cache.has(id)) return cache.get(id);
  const result = /* @__PURE__ */ new Map();
  _getSubgraph(id, graph, result, cache);
  return result;
}
function getStronglyConnectedComponents(graph) {
  const tarjanState = /* @__PURE__ */ new Map();
  let index = 0;
  const stack = [];
  const result = [];
  function strongConnect(v) {
    const state = {
      index,
      lowLink: index,
      onStack: true
    };
    tarjanState.set(v, state);
    index++;
    stack.push(v);
    const edges = graph.get(v).refs;
    for (let w of edges) {
      const edgeState = tarjanState.get(w);
      if (!edgeState) {
        strongConnect(w);
        state.lowLink = Math.min(state.lowLink, tarjanState.get(w).lowLink);
      } else if (edgeState.onStack) {
        state.lowLink = Math.min(state.lowLink, edgeState.index);
      }
    }
    if (state.lowLink === state.index) {
      const component = /* @__PURE__ */ new Set();
      let poppedNode = -1;
      do {
        poppedNode = stack.pop();
        tarjanState.get(poppedNode).onStack = false;
        component.add(poppedNode);
      } while (poppedNode !== v);
      result.push(component);
    }
  }
  for (const node of graph.keys()) {
    if (!tarjanState.has(node)) {
      strongConnect(node);
    }
  }
  return result;
}
function mergeSCCsWithCommonNodes(stronglyConnectedComponents) {
  const scc = stronglyConnectedComponents;
  const ungroupedCycles = new Set(scc.map((_, i) => i));
  const edges = new Map(scc.map((_, i) => [i, /* @__PURE__ */ new Set()]));
  scc.forEach((cycle, i) => {
    scc.slice(i + 1).forEach((otherCycle, _j) => {
      const j = _j + i + 1;
      const combined = /* @__PURE__ */ new Set([...cycle, ...otherCycle]);
      if (combined.size !== cycle.size + otherCycle.size) {
        edges.get(i).add(j);
        edges.get(j).add(i);
      }
    });
  });
  const groups = [];
  while (ungroupedCycles.size) {
    const group = /* @__PURE__ */ new Set();
    const toVisit = [ungroupedCycles.values().next().value];
    while (toVisit.length) {
      const idx = toVisit.pop();
      if (!ungroupedCycles.has(idx)) continue;
      ungroupedCycles.delete(idx);
      const cycle = scc[idx];
      cycle.forEach((v) => group.add(Number(v)));
      edges.get(idx).forEach((n) => toVisit.push(n));
    }
    groups.push(group);
  }
  return groups;
}

const textEncoder = new TextEncoder();
const encodeText = textEncoder.encode.bind(textEncoder);
const getChecksum = (values) => {
  const res = new Uint8Array(values.length * 8);
  const dv = new DataView(res.buffer);
  for (let i = 0; i < values.length; i++) dv.setBigUint64(i * 8, values[i]);
  return scale.h64(res);
};
const getStringChecksum = (values) => getChecksum(values.map((v) => scale.h64(encodeText(v))));
const shapeIds = {
  primitive: 0n,
  vector: 1n,
  tuple: 2n,
  struct: 3n,
  option: 4n,
  result: 5n,
  enum: 6n,
  void: 7n
};
const runtimePrimitiveIds = {
  undefined: 0n,
  number: 1n,
  string: 2n,
  bigint: 3n,
  boolean: 4n,
  bitSequence: 5n,
  // {bitsLen: number, bytes: Uint8Array}
  byteSequence: 6n,
  // Binary
  accountId32: 7n,
  // SS58String
  accountId20: 8n
  // EthAccount
};
const metadataPrimitiveIds = {
  bool: runtimePrimitiveIds.boolean,
  char: runtimePrimitiveIds.string,
  str: runtimePrimitiveIds.string,
  u8: runtimePrimitiveIds.number,
  u16: runtimePrimitiveIds.number,
  u32: runtimePrimitiveIds.number,
  u64: runtimePrimitiveIds.bigint,
  u128: runtimePrimitiveIds.bigint,
  u256: runtimePrimitiveIds.bigint,
  i8: runtimePrimitiveIds.number,
  i16: runtimePrimitiveIds.number,
  i32: runtimePrimitiveIds.number,
  i64: runtimePrimitiveIds.bigint,
  i128: runtimePrimitiveIds.bigint,
  i256: runtimePrimitiveIds.bigint
};
const structLikeBuilder = (shapeId, input, innerChecksum) => {
  const sortedEntries = Object.entries(input).sort(
    ([a], [b]) => a.localeCompare(b)
  );
  const keysChecksum = getStringChecksum(sortedEntries.map(([key]) => key));
  const valuesChecksum = getChecksum(
    sortedEntries.map(([, entry]) => innerChecksum(entry))
  );
  return getChecksum([shapeId, keysChecksum, valuesChecksum]);
};
const _buildChecksum = (input, buildNextChecksum) => {
  if (input.type === "primitive")
    return getChecksum([shapeIds.primitive, metadataPrimitiveIds[input.value]]);
  if (input.type === "void") return getChecksum([shapeIds.void]);
  if (input.type === "compact")
    return getChecksum([
      shapeIds.primitive,
      runtimePrimitiveIds[input.isBig ? "bigint" : "number"]
    ]);
  if (input.type === "bitSequence")
    return getChecksum([shapeIds.primitive, runtimePrimitiveIds.bitSequence]);
  if (input.type === "AccountId32") {
    return getChecksum([shapeIds.primitive, runtimePrimitiveIds.accountId32]);
  }
  if (input.type === "AccountId20") {
    return getChecksum([shapeIds.primitive, runtimePrimitiveIds.accountId20]);
  }
  const buildVector = (entry, length) => {
    const innerChecksum = buildNextChecksum(entry);
    return getChecksum(
      length !== void 0 ? [shapeIds.vector, innerChecksum, BigInt(length)] : [shapeIds.vector, innerChecksum]
    );
  };
  if (input.type === "array") {
    const innerValue = input.value;
    if (innerValue.type === "primitive" && innerValue.value === "u8") {
      return getChecksum([
        shapeIds.primitive,
        runtimePrimitiveIds.byteSequence,
        BigInt(input.len)
      ]);
    }
    return buildVector(innerValue, input.len);
  }
  if (input.type === "sequence") {
    const innerValue = input.value;
    if (innerValue.type === "primitive" && innerValue.value === "u8") {
      return getChecksum([shapeIds.primitive, runtimePrimitiveIds.byteSequence]);
    }
    return buildVector(innerValue);
  }
  const buildTuple = (entries) => getChecksum([shapeIds.tuple, ...entries.map(buildNextChecksum)]);
  const buildStruct = (entries) => structLikeBuilder(shapeIds.struct, entries, buildNextChecksum);
  if (input.type === "tuple") return buildTuple(input.value);
  if (input.type === "struct") return buildStruct(input.value);
  if (input.type === "option")
    return getChecksum([shapeIds.option, buildNextChecksum(input.value)]);
  if (input.type === "result")
    return getChecksum([
      shapeIds.result,
      buildNextChecksum(input.value.ok),
      buildNextChecksum(input.value.ko)
    ]);
  return structLikeBuilder(shapeIds.enum, input.value, (entry) => {
    if (entry.type === "lookupEntry") return buildNextChecksum(entry.value);
    switch (entry.type) {
      case "void":
        return getChecksum([shapeIds.void]);
      case "tuple":
        return buildTuple(entry.value);
      case "struct":
        return buildStruct(entry.value);
      case "array":
        return buildVector(entry.value, entry.len);
    }
  });
};
const sortCyclicGroups = (groups, graph) => {
  const getReachableNodes = (group) => {
    const result2 = /* @__PURE__ */ new Set();
    const toVisit = Array.from(group);
    while (toVisit.length) {
      const id = toVisit.pop();
      if (result2.has(id)) continue;
      result2.add(id);
      graph.get(id)?.refs.forEach((id2) => toVisit.push(id2));
    }
    return Array.from(result2);
  };
  const result = new Array();
  function dependentsFirst(group) {
    if (result.includes(group)) return;
    const dependents = groups.filter(
      (candidate) => candidate !== group && getReachableNodes(group).some((node) => candidate.has(node))
    );
    dependents.forEach((group2) => dependentsFirst(group2));
    if (result.includes(group)) return;
    result.push(group);
  }
  groups.forEach((group) => dependentsFirst(group));
  return result;
};
function iterateChecksums(group, iterations, cache, graph) {
  const groupReadCache = new Map([...group].map((id) => [id, 0n]));
  const groupWriteCache = /* @__PURE__ */ new Map();
  const recursiveBuildChecksum = (entry, skipCache = true) => {
    if (!skipCache && (groupReadCache.has(entry.id) || cache.has(entry.id))) {
      return groupReadCache.get(entry.id) ?? cache.get(entry.id);
    }
    const result = _buildChecksum(
      entry,
      (nextEntry) => recursiveBuildChecksum(nextEntry, false)
    );
    if (group.has(entry.id)) {
      groupWriteCache.set(entry.id, result);
    } else {
      cache.set(entry.id, result);
    }
    return result;
  };
  for (let i = 0; i < iterations; i++) {
    group.forEach((id) => recursiveBuildChecksum(graph.get(id).entry));
    group.forEach((id) => groupReadCache.set(id, groupWriteCache.get(id)));
  }
  return groupReadCache;
}
function getMirroredNodes(cyclicGroups, graph) {
  const maxSize = cyclicGroups.reduce(
    (acc, group) => Math.max(acc, group.size),
    0
  );
  const allEntries = new Set([...graph.values()].map((v) => v.entry.id));
  const resultingChecksums = iterateChecksums(
    allEntries,
    maxSize,
    // Cache won't be used, since it's using the internal one for every node.
    /* @__PURE__ */ new Map(),
    graph
  );
  const checksumToNodes = /* @__PURE__ */ new Map();
  for (const id of allEntries) {
    const checksum = resultingChecksums.get(id);
    if (checksum == void 0) throw new Error("Unreachable");
    if (!checksumToNodes.has(checksum)) {
      checksumToNodes.set(checksum, []);
    }
    checksumToNodes.get(checksum).push(id);
  }
  const checksumsWithDuplicates = [...checksumToNodes.entries()].filter(
    ([, nodes]) => nodes.length > 1
  );
  const duplicatesMap = {};
  checksumsWithDuplicates.forEach(([, nodes]) => {
    nodes.forEach((n) => duplicatesMap[n] = nodes);
  });
  return duplicatesMap;
}
const buildChecksum = (entry, cache, graph) => {
  if (cache.has(entry.id)) return cache.get(entry.id);
  const subGraph = getSubgraph(entry.id, graph);
  const cycles = getStronglyConnectedComponents(subGraph).filter(
    // SCCs can be of length=1, but for those we're only interested with those that are circular with themselves
    (group) => group.size > 1 || isSelfCircular(group, subGraph)
  );
  const cyclicGroups = mergeSCCsWithCommonNodes(cycles).filter((group) => {
    return !cache.has(group.values().next().value);
  });
  const mirrored = getMirroredNodes(cyclicGroups, subGraph);
  const sortedCyclicGroups = sortCyclicGroups(cyclicGroups, subGraph);
  sortedCyclicGroups.forEach((group) => {
    if (cache.has(group.values().next().value)) {
      return;
    }
    const result = iterateChecksums(group, group.size, cache, graph);
    group.forEach((id) => {
      const checksum = result.get(id);
      if (id in mirrored) {
        mirrored[id].forEach((id2) => cache.set(id2, checksum));
      } else {
        cache.set(id, checksum);
      }
    });
  });
  const getChecksum2 = (entry2) => {
    if (cache.has(entry2.id)) return cache.get(entry2.id);
    return _buildChecksum(entry2, getChecksum2);
  };
  return getChecksum2(entry);
};
const isSelfCircular = (group, graph) => {
  if (group.size !== 1) return false;
  const [id] = group;
  return graph.get(id).refs.has(id);
};
const getChecksumBuilder = (getLookupEntryDef) => {
  const { metadata } = getLookupEntryDef;
  const graph = buildLookupGraph(getLookupEntryDef, metadata.lookup.length);
  const cache = /* @__PURE__ */ new Map();
  const buildDefinition = (id) => buildChecksum(getLookupEntryDef(id), cache, graph);
  const buildStorage = (pallet, entry) => {
    try {
      const storageEntry = metadata.pallets.find((x) => x.name === pallet).storage.items.find((s) => s.name === entry);
      if (storageEntry.type.tag === "plain")
        return buildDefinition(storageEntry.type.value);
      const { key, value } = storageEntry.type.value;
      const val = buildDefinition(value);
      const returnKey = buildDefinition(key);
      return getChecksum([val, returnKey]);
    } catch (_) {
      return null;
    }
  };
  const buildViewFns = (pallet, entry) => {
    try {
      const viewFn = metadata.pallets.find((x) => x.name === pallet)?.viewFns.find((x) => x.name === entry);
      if (!viewFn) throw null;
      const argNamesChecksum = getStringChecksum(
        viewFn.inputs.map((x) => x.name)
      );
      const argValuesChecksum = getChecksum(
        viewFn.inputs.map((x) => buildDefinition(x.type))
      );
      const outputChecksum = buildDefinition(viewFn.output);
      return getChecksum([argNamesChecksum, argValuesChecksum, outputChecksum]);
    } catch (_) {
      return null;
    }
  };
  const buildRuntimeCall = (api, method) => {
    try {
      const entry = metadata.apis.find((x) => x.name === api)?.methods.find((x) => x.name === method);
      if (!entry) throw null;
      const argNamesChecksum = getStringChecksum(
        entry.inputs.map((x) => x.name)
      );
      const argValuesChecksum = getChecksum(
        entry.inputs.map((x) => buildDefinition(x.type))
      );
      const outputChecksum = buildDefinition(entry.output);
      return getChecksum([argNamesChecksum, argValuesChecksum, outputChecksum]);
    } catch (_) {
      return null;
    }
  };
  const buildComposite = (input) => {
    if (input.type === "void") return getChecksum([0n]);
    if (input.type === "tuple") {
      const values = Object.values(input.value).map(
        (entry) => buildDefinition(entry.id)
      );
      return getChecksum([shapeIds.tuple, ...values]);
    }
    if (input.type === "array") {
      return getChecksum([
        shapeIds.vector,
        buildDefinition(input.value.id),
        BigInt(input.len)
      ]);
    }
    return structLikeBuilder(
      shapeIds.struct,
      input.value,
      (entry) => buildDefinition(entry.id)
    );
  };
  const buildNamedTuple = (input) => {
    return structLikeBuilder(
      shapeIds.tuple,
      input.value,
      (entry) => buildDefinition(entry.id)
    );
  };
  const variantShapeId = {
    errors: 1n,
    events: 2n,
    calls: 3n
  };
  const buildVariant = (variantType) => (pallet, name) => {
    try {
      const palletEntry = metadata.pallets.find((x) => x.name === pallet);
      const enumLookup = getLookupEntryDef(palletEntry[variantType].type);
      buildDefinition(enumLookup.id);
      if (enumLookup.type !== "enum") throw null;
      const entry = enumLookup.value[name];
      const valueChecksum = entry.type === "lookupEntry" ? buildDefinition(entry.value.id) : buildComposite(entry);
      return getChecksum([variantShapeId[variantType], valueChecksum]);
    } catch (_) {
      return null;
    }
  };
  const buildConstant = (pallet, constantName) => {
    try {
      const storageEntry = metadata.pallets.find((x) => x.name === pallet).constants.find((s) => s.name === constantName);
      return buildDefinition(storageEntry.type);
    } catch (_) {
      return null;
    }
  };
  const toStringEnhancer = (fn) => (...args) => fn(...args)?.toString(32) ?? null;
  return {
    buildDefinition: toStringEnhancer(buildDefinition),
    buildRuntimeCall: toStringEnhancer(buildRuntimeCall),
    buildStorage: toStringEnhancer(buildStorage),
    buildViewFns: toStringEnhancer(buildViewFns),
    buildCall: toStringEnhancer(buildVariant("calls")),
    buildEvent: toStringEnhancer(buildVariant("events")),
    buildError: toStringEnhancer(buildVariant("errors")),
    buildConstant: toStringEnhancer(buildConstant),
    buildComposite: toStringEnhancer(buildComposite),
    buildNamedTuple: toStringEnhancer(buildNamedTuple),
    getAllGeneratedChecksums: () => Array.from(cache.values()).map((v) => v.toString(32))
  };
};

exports.denormalizeLookup = denormalizeLookup;
exports.getChecksumBuilder = getChecksumBuilder;
exports.getDynamicBuilder = getDynamicBuilder;
exports.getLookupCodecBuilder = getLookupCodecBuilder;
exports.getLookupFn = getLookupFn;
//# sourceMappingURL=index.js.map
