generateFunctionMocks = (proto, itemMainProp, dataArray) => ({
  item: utils.createProxy(proto.item, {
    apply(target, ctx, args) {
      if (!args.length) {
        throw new TypeError(
          `Failed to execute 'item' on '${proto[Symbol.toStringTag]}': 1 argument required, but only 0 present.`
        );
      }
      // Special behavior alert:
      // - Vanilla tries to cast strings to Numbers (only integers!) and use them as property index lookup
      // - If anything else than an integer (including as string) is provided it will return the first entry
      const isInteger = args[0] && Number.isInteger(Number(args[0])); // Cast potential string to number first, then check for integer
      // Note: Vanilla never returns `undefined`
      return (isInteger ? dataArray[Number(args[0])] : dataArray[0]) || null;
    }
  }),
  /** Returns the MimeType object with the specified name. */
  namedItem: utils.createProxy(proto.namedItem, {
    apply(target, ctx, args) {
      if (!args.length) {
        throw new TypeError(
          `Failed to execute 'namedItem' on '${proto[Symbol.toStringTag]}': 1 argument required, but only 0 present.`
        );
      }
      return dataArray.find((mt) => mt[itemMainProp] === args[0]) || null; // Not `undefined`!
    }
  }),
  /** Does nothing and shall return nothing */
  refresh: proto.refresh
    ? utils.createProxy(proto.refresh, {
      apply(target, ctx, args) {
        return undefined;
      }
    })
    : undefined
});

function generateMagicArray(
  dataArray = [],
  proto = MimeTypeArray.prototype,
  itemProto = MimeType.prototype,
  itemMainProp = "type"
) {
  // Quick helper to set props with the same descriptors vanilla is using
  const defineProp = (obj, prop, value) =>
    Object.defineProperty(obj, prop, {
      value,
      writable: false,
      enumerable: false, // Important for mimeTypes & plugins: `JSON.stringify(navigator.mimeTypes)`
      configurable: false
    });

  // Loop over our fake data and construct items
  const makeItem = (data) => {
    const item = {};
    for (const prop of Object.keys(data)) {
      if (prop.startsWith("__")) {
        continue;
      }
      defineProp(item, prop, data[prop]);
    }
    // navigator.plugins[i].length should always be 1
    if (itemProto === Plugin.prototype) {
      defineProp(item, "length", 1);
    }
    // We need to spoof a specific `MimeType` or `Plugin` object
    return Object.create(itemProto, Object.getOwnPropertyDescriptors(item));
  };

  const magicArray = [];

  // Loop through our fake data and use that to create convincing entities
  dataArray.forEach((data) => {
    magicArray.push(makeItem(data));
  });

  // Add direct property access  based on types (e.g. `obj['application/pdf']`) afterwards
  magicArray.forEach((entry) => {
    defineProp(magicArray, entry[itemMainProp], entry);
  });

  // This is the best way to fake the type to make sure this is false: `Array.isArray(navigator.mimeTypes)`
  const magicArrayObj = Object.create(proto, {
    ...Object.getOwnPropertyDescriptors(magicArray),

    // There's one ugly quirk we unfortunately need to take care of:
    // The `MimeTypeArray` prototype has an enumerable `length` property,
    // but headful Chrome will still skip it when running `Object.getOwnPropertyNames(navigator.mimeTypes)`.
    // To strip it we need to make it first `configurable` and can then overlay a Proxy with an `ownKeys` trap.
    length: {
      value: magicArray.length,
      writable: false,
      enumerable: false,
      configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip `length`
    }
  });

  // Generate our functional function mocks :-)
  const functionMocks = generateFunctionMocks(proto, itemMainProp, magicArray);

  // Override custom object with proxy
  return new Proxy(magicArrayObj, {
    get(target, key = "") {
      // Redirect function calls to our custom proxied versions mocking the vanilla behavior
      if (key === "item") {
        return functionMocks.item;
      }
      if (key === "namedItem") {
        return functionMocks.namedItem;
      }
      if (proto === PluginArray.prototype && key === "refresh") {
        return functionMocks.refresh;
      }
      // Everything else can pass through as normal
      return utils.cache.Reflect.get(...arguments);
    },
    ownKeys(target) {
      // There are a couple of quirks where the original property demonstrates "magical" behavior that makes no sense
      // This can be witnessed when calling `Object.getOwnPropertyNames(navigator.mimeTypes)` and the absense of `length`
      // My guess is that it has to do with the recent change of not allowing data enumeration and this being implemented weirdly
      // For that reason we just completely fake the available property names based on our data to match what regular Chrome is doing
      // Specific issues when not patching this: `length` property is available, direct `types` props (e.g. `obj['application/pdf']`) are missing
      const keys = [];
      const typeProps = magicArray.map((mt) => mt[itemMainProp]);
      typeProps.forEach((_, i) => keys.push(`${i}`));
      typeProps.forEach((propName) => keys.push(propName));
      return keys;
    }
  });
}