/* eslint-disable no-new,no-useless-call */
// mutableProxyFactory from https://stackoverflow.com/a/54460544
// (C) Alex Hall  https://stackoverflow.com/users/2482744/alex-hall
// License CC BY-SA 3.0
/* eslint-disable @typescript-eslint/ban-types */

export class PProxyHandler<T extends object> implements ProxyHandler<T> {
  getPrototypeOf?(target: T): object | null {
    return Reflect.getPrototypeOf(target);
  }

  setPrototypeOf?(target: T, v: any): boolean {
    return Reflect.setPrototypeOf(target, v);
  }

  isExtensible?(target: T): boolean {
    return Reflect.isExtensible(target);
  }

  preventExtensions?(target: T): boolean {
    return Reflect.preventExtensions(target);
  }

  getOwnPropertyDescriptor?(
    target: T,
    p: PropertyKey
  ): PropertyDescriptor | undefined {
    return Reflect.getOwnPropertyDescriptor(target, p);
  }

  has?(target: T, p: PropertyKey): boolean {
    return Reflect.has(target, p);
  }

  get?(target: T, p: PropertyKey, receiver: any): any {
    return Reflect.get(target, p, receiver);
  }

  set?(target: T, p: PropertyKey, value: any, receiver: any): boolean {
    return Reflect.set(target, p, value, receiver);
  }

  deleteProperty?(target: T, p: PropertyKey): boolean {
    return Reflect.deleteProperty(target, p);
  }

  defineProperty?(
    target: T,
    p: PropertyKey,
    attributes: PropertyDescriptor
  ): boolean {
    return Reflect.defineProperty(target, p, attributes);
  }

  enumerate?(target: T): PropertyKey[] {
    return Reflect.ownKeys(target);
  }

  // @ts-expect-error
  ownKeys?(target: T): PropertyKey[] {
    return Reflect.ownKeys(target);
  }

  apply?(target: T, thisArg: any, argArray?: any): any {
    return Reflect.apply(target as Function, thisArg, argArray);
  }

  construct?(target: T, argArray: any, newTarget?: any): object {
    return Reflect.construct(target as Function, argArray, newTarget);
  }
}

interface MutableProxy<T extends object> {
  setTarget(target: T): void;
  setHandler(handler: PProxyHandler<T>): void;
  getTarget(): T;
  getHandler(): ProxyHandler<T>;
  proxy: T;
}

export function mutableProxyFactory<T extends object>(
  mutableTarget: T,
  mutableHandler?: ProxyHandler<T>
): MutableProxy<T> {
  if (!mutableHandler) mutableHandler = new PProxyHandler() as any;
  return {
    setTarget(target: T): void {
      new Proxy(target, {}); // test target validity
      mutableTarget = target;
    },
    setHandler(handler: PProxyHandler<T>): void {
      new Proxy({}, handler as any); // test handler validity
      Object.keys(handler).forEach((key) => {
        const value = handler[key];
        if (Reflect[key] && typeof value !== "function") {
          throw new Error(`Trap "${key}: ${value}" is not a function`);
        }
      });
      mutableHandler = handler as any;
    },
    getTarget(): T {
      return mutableTarget;
    },
    // @ts-expect-error
    getHandler(): PProxyHandler<T> {
      return mutableHandler as any;
    },
    proxy: new Proxy(
      mutableTarget,
      new Proxy(
        {},
        {
          // Dynamically forward all the traps to the associated methods on the mutable handler
          get(target, property) {
            return (_target, ...args) =>
              mutableHandler[property].apply(mutableHandler, [
                mutableTarget,
                ...args,
              ]);
          },
        }
      )
    ),
  };
}
