export const recursiveFieldDecorator = (
  target: any,
  fieldDecorator: (target: any, key: string, path: string[]) => any,
  path: string[] = [],
) => {
  const proxyCache: Record<string, any> = {};
  const proxy = {
    set(target: any, key: string, value: any) {
      target[key] = value;
      delete proxyCache[key];
      return true;
    },
    get(target: any, key: string) {
      if (key === '__fieldDecorator' && !(key in target)) {
        return fieldDecorator;
      }
      // if "raw" value is requested, return the undecorated value.
      if (typeof key === 'string' && key.endsWith('__raw')) {
        // But first check the literal '<key>__raw' in case a) it actually exists, or b) (more likely) we have nested proxies
        return target[key] || target[key.substring(0, key.length - 5)];
      }

      let decoratedValue = fieldDecorator(target, key, path);
      if (decoratedValue === undefined) decoratedValue = target[key];

      if (typeof decoratedValue === 'object' && decoratedValue !== null) {
        const desc = Object.getOwnPropertyDescriptor(target, key);

        // don't proxy non-writables (e.g. prototypes)
        const preventReproxy = desc && !desc.writable && !desc.configurable;

        if (preventReproxy) {
          return decoratedValue;
        } else if (proxyCache[key]) {
          return proxyCache[key];
        } else {
          proxyCache[key] = recursiveFieldDecorator(
            decoratedValue,
            fieldDecorator,
            [...path, key],
          );
          return proxyCache[key];
        }
      }

      return decoratedValue;
    },
  };

  return window.Proxy ? new Proxy(target, proxy) : target;
};
