text: // Copyright 2017-2021 @polkadot/types authors & contributors
  // SPDX-License-Identifier: Apache-2.0
  const BOUNDED = ['BTreeMap', 'BTreeSet', 'HashMap', 'Vec'];
  const ALLOWED_BOXES = BOUNDED.concat(['Compact', 'DoNotConstruct', 'Int', 'Linkage', 'Range', 'RangeInclusive', 'Result', 'Option', 'UInt', 'WrapperOpaque']);
  const BOX_PRECEDING = ['<', '(', '[', '"', ',', ' ']; // start of vec, tuple, fixed array, part of struct def or in tuple
  
  const mappings = [// alias <T::InherentOfflineReport as InherentOfflineReport>::Inherent -> InherentOfflineReport
  alias('<T::InherentOfflineReport as InherentOfflineReport>::Inherent', 'InherentOfflineReport', false), alias('VecDeque<', 'Vec<', false), // <T::Balance as HasCompact>
  cleanupCompact(), // Change BoundedVec<Type, Size> to Vec<Type>
  removeExtensions('Bounded', true), // Change WeakVec<Type> to Vec<Type>
  removeExtensions('Weak', false), // Remove all the trait prefixes
  removeTraits(), // remove PairOf<T> -> (T, T)
  removePairOf(), // remove boxing, `Box<Proposal>` -> `Proposal`
  removeWrap('Box<'), // remove generics, `MisbehaviorReport<Hash, BlockNumber>` -> `MisbehaviorReport`
  removeGenerics(), // alias String -> Text (compat with jsonrpc methods)
  alias('String', 'Text'), // alias Vec<u8> -> Bytes
  alias('Vec<u8>', 'Bytes'), alias('&\\[u8\\]', 'Bytes'), alias("&'static\\[u8\\]", 'Bytes'), // alias RawAddress -> Address
  alias('RawAddress', 'Address'), // lookups, mapped to Address/AccountId as appropriate in runtime
  alias('Lookup::Source', 'LookupSource'), alias('Lookup::Target', 'LookupTarget'), // HACK duplication between contracts & primitives, however contracts prefixed with exec
  alias('exec::StorageKey', 'ContractStorageKey'), // flattens tuples with one value, `(AccountId)` -> `AccountId`
  flattenSingleTuple(), // converts ::Type to Type, <T as Trait<I>>::Proposal -> Proposal
  removeColons()]; // given a starting index, find the closing >
  
  export function findClosing(value, start) {
    let depth = 0;
  
    for (let index = start; index < value.length; index++) {
      if (value[index] === '>') {
        if (!depth) {
          return index;
        }
  
        depth--;
      } else if (value[index] === '<') {
        depth++;
      }
    }
  
    throw new Error(`Unable to find closing matching <> on '${value}' (start ${start})`);
  }
  export function alias(src, dest, withChecks = true) {
    const from = new RegExp(`(^${src}|${BOX_PRECEDING.map(box => `\\${box}${src}`).join('|')})`, 'g');
  
    const to = src => {
      from.lastIndex = 0;
      return withChecks && BOX_PRECEDING.includes(src[0]) ? `${src[0]}${dest}` : dest;
    };
  
    return value => value.replace(from, to);
  }
  export function cleanupCompact() {
    return value => {
      for (let index = 0; index < value.length; index++) {
        if (value[index] === '<') {
          const end = findClosing(value, index + 1) - 14;
  
          if (value.substr(end, 14) === ' as HasCompact') {
            value = `Compact<${value.substr(index + 1, end - index - 1)}>`;
          }
        }
      }
  
      return value;
    };
  }
  export function flattenSingleTuple() {
    const from1 = /,\)/g;
    const from2 = /\(([^,]+)\)/;
    return value => {
      from1.lastIndex = 0;
      return value // tuples may have trailing commas, e.g. (u32, BlockNumber, )
      .replace(from1, ')') // change (u32) -> u32
      .replace(from2, '$1');
    };
  }
  
  function replaceTagWith(value, matcher, replacer) {
    let index = -1;
  
    while (true) {
      index = value.indexOf(matcher, index + 1);
  
      if (index === -1) {
        return value;
      }
  
      const start = index + matcher.length;
      const end = findClosing(value, start);
      value = `${value.substr(0, index)}${replacer(value.substr(start, end - start))}${value.substr(end + 1)}`;
    }
  } // remove the Bounded* or Weak* wrappers
  
  
  export function removeExtensions(type, isSized) {
    return value => BOUNDED.reduce((value, tag) => replaceTagWith(value, `${type}${tag}<`, inner => {
      const parts = inner.split(',').map(s => s.trim()).filter(s => s);
  
      if (isSized) {
        parts.pop();
      }
  
      return `${tag}<${parts.join(',')}>`;
    }), value);
  }
  export function removeColons() {
    return (value, {
      allowNamespaces
    } = {}) => {
      let index = 0;
  
      while (index !== -1) {
        index = value.indexOf('::');
  
        if (index === 0) {
          value = value.substr(2);
        } else if (index !== -1) {
          if (allowNamespaces) {
            return value;
          }
  
          let start = index;
  
          while (start !== -1 && !BOX_PRECEDING.includes(value[start])) {
            start--;
          }
  
          value = `${value.substr(0, start + 1)}${value.substr(index + 2)}`;
        }
      }
  
      return value;
    };
  }
  export function removeGenerics() {
    return value => {
      for (let index = 0; index < value.length; index++) {
        if (value[index] === '<') {
          // check against the allowed wrappers, be it Vec<..>, Option<...> ...
          const box = ALLOWED_BOXES.find(box => {
            const start = index - box.length;
            return start >= 0 && value.substr(start, box.length) === box && ( // make sure it is stand-alone, i.e. don't catch ElectionResult<...> as Result<...>
            start === 0 || BOX_PRECEDING.includes(value[start - 1]));
          }); // we have not found anything, unwrap generic innards
  
          if (!box) {
            const end = findClosing(value, index + 1);
            value = `${value.substr(0, index)}${value.substr(end + 1)}`;
          }
        }
      }
  
      return value;
    };
  } // remove the PairOf wrappers
  
  export function removePairOf() {
    const replacer = inner => `(${inner},${inner})`;
  
    return value => replaceTagWith(value, 'PairOf<', replacer);
  } // remove the type traits
  
  export function removeTraits() {
    const from1 = /\s/g;
    const from2 = /(T|Self)::/g;
    const from3 = /<(T|Self)asTrait>::/g;
    const from4 = /<Tas[a-z]+::Trait>::/g;
    const from5 = /<LookupasStaticLookup>/g;
    const from6 = /::Type/g;
    return value => {
      from1.lastIndex = 0;
      from2.lastIndex = 0;
      from3.lastIndex = 0;
      from4.lastIndex = 0;
      from5.lastIndex = 0;
      from6.lastIndex = 0;
      return value // remove all whitespaces
      .replace(from1, '') // anything `T::<type>` to end up as `<type>`
      .replace(from2, '') // replace `<T as Trait>::` (whitespaces were removed above)
      .replace(from3, '') // replace `<T as something::Trait>::` (whitespaces were removed above)
      .replace(from4, '') // replace <Lookup as StaticLookup>
      .replace(from5, 'Lookup') // replace `<...>::Type`
      .replace(from6, '');
    };
  } // remove wrapping values, i.e. Box<Proposal> -> Proposal
  
  export function removeWrap(check) {
    const replacer = inner => inner;
  
    return value => replaceTagWith(value, check, replacer);
  }
  const sanitizeMap = new Map(); // eslint-disable-next-line @typescript-eslint/ban-types
  
  export function sanitize(value, options) {
    let result = value.toString();
  
    if (!options) {
      const memoized = sanitizeMap.get(result);
  
      if (memoized) {
        return memoized;
      }
    }
  
    for (let i = 0; i < mappings.length; i++) {
      result = mappings[i](result, options);
    }
  
    result = result.trim();
  
    if (!options) {
      sanitizeMap.set(value.toString(), result);
    }
  
    return result;
  }