function expandTokenInMap(token, tokenMap, opts, platform) {
  const uses$ = opts.usesDtcg;
  // create a copy of the token without the value/type, so that we have all the meta props
  // which have to be inherited in the expanded tokens.
  /** @type {Record<string, unknown>} */
  const copyMeta = {};
  Object.keys(token)
    // either filter $value & $type, or value and type depending on whether $ is used
    .filter(
      (key) =>
        !['$value', 'value', '$type', 'type']
          .filter((key) => (uses$ ? key.startsWith('$') : !key.startsWith('$')))
          .includes(key),
    )
    .forEach((key) => {
      copyMeta[key] = token[key];
    });

  const value = uses$ ? token.$value : token.value;
  // the $type and type may both be missing if the $type is coming from an ancestor token group,
  // however, prior to expand and preprocessors, we run a step so missing $type is added from the closest ancestor
  const compositionType = /** @type {string} */ (uses$ ? token.$type : token.type);
  /** @type {Expand['typesMap']} */
  let typesMap = {};
  const expand = platform?.expand ?? opts.expand;
  if (typeof expand === 'object') {
    typesMap = expand.typesMap ?? {};
  }

  // array of objects is also valid e.g. multi-shadow values
  // https://github.com/design-tokens/community-group/issues/100 there seems to be a consensus for this
  // so this code adds forward-compatibility with that
  const _value = Array.isArray(value) ? value : [value];

  _value.forEach((objectVal, index, arr) => {
    let key = /** @type {string} */ (token.key);
    // more than 1 means multi-value, meaning we should add nested token group
    // with index to the expanded result
    if (arr.length > 1) {
      key = key.replace('}', `.${index + 1}}`);
    }

    Object.entries(objectVal).forEach(([propKey, value]) => {
      const innerKey = key.replace('}', `.${propKey}}`);
      const expandedValue = {
        ...copyMeta,
        [`${uses$ ? '$' : ''}value`]: value,
        [`${uses$ ? '$' : ''}type`]: getTypeFromMap(propKey, compositionType, typesMap),
        key: innerKey,
      };
      // If the value is an object, we're dealing with a nested composite token and we have to recurse
      // TODO: do we need to also handle recursing array values? Items could be nested objects as well.
      // e.g. a multi-shadow inside a border as a (hypothetical, not realistic) example
      if (isPlainObject(value) /* || Array.isArray(value)*/) {
        // recurse, and make sure to pass the key so we don't lose the parent relation
        expandTokenInMap(expandedValue, tokenMap, opts, platform);
      } else {
        // if we're expanding on platform level, we already have a path property
        // we will need to adjust this by adding the new keys of the expanded tokens to the path array
        if (Array.isArray(expandedValue.path)) {
          if (arr.length > 1) {
            expandedValue.path = [...expandedValue.path, `${index + 1}`];
          }
          expandedValue.path = [...expandedValue.path, propKey];
        }
        tokenMap.set(innerKey, expandedValue);
      }
    });
  });

  // We've expanded it, now delete the original
  tokenMap.delete(/** @type {string} */ (token.key));
}
@param {DesignToken} token already resolved refs
@param {Map<string, DesignToken>} tokenMap
@param {Config} opts
@param {PlatformConfig} [platform]