importClass(net.minecraftforge.common.MinecraftForge) // I would have used the ct module but it is broken (line 78) (this is fixed verison)
importPackage(net.minecraftforge.fml.common.eventhandler)
importPackage(org.objectweb.asm)
importClass(java.lang.ClassLoader)
importClass(org.apache.commons.lang3.RandomStringUtils)
importClass(java.util.function.Consumer)
importClass(net.minecraftforge.fml.common.eventhandler.EventPriority)

const L = s => `L${s};`
const LoadedInsts = []

function defineClassBytes(name, bytes) {// Should support multymc? ty dawjaw https://canary.discord.com/channels/119493402902528000/688773480954855537/979959207124168744
  const classLoader = Packages.com.chattriggers.ctjs.CTJS.class.getClassLoader()

  let defClass;
  ClassLoader.class.getDeclaredMethods().forEach(m => {
    if (m.toString() === "protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int) throws java.lang.ClassFormatError") {
      defClass = m;
    }
  })

  defClass.setAccessible(true)

  const n = new java.lang.String(name)
  const o = new java.lang.Integer(0)
  const s = new java.lang.Integer(bytes.length)
  return defClass.invoke(classLoader, n, bytes, o, s)
}

const registerForge = (e, priority = EventPriority.NORMAL, cb) => {
  const cw = new ClassWriter(0)

  const event = Type.getType(e.class).internalName
  const name = RandomStringUtils.randomAlphabetic(7)

  const consumer = Type.getType(Consumer.class).internalName
  const mcForge = Type.getType(MinecraftForge.class).internalName
  const eventBus = Type.getType(EventBus.class).internalName
  const subscribeEvent = Type.getType(SubscribeEvent.class).internalName
  const obj = Type.getType(java.lang.Object.class).internalName

  cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name, null, obj, null)
  // Cw.visitInnerClass("net/minecraftforge/event/entity/player/PlayerEvent$BreakSpeed","net/minecraftforge/event/entity/player/PlayerEvent","BreakSpeed",ACC_PUBLIC+ACC_STATIC);
  {
    cw.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL, "callback", L(consumer), L(consumer + "<" + L(event) + ">"), null).visitEnd()
  }
  {
    const con = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "(" + L(consumer) + ")V", "(" + L(consumer + "<" + L(event) + ">") + ")V", null)
    con.visitCode()
    con.visitVarInsn(Opcodes.ALOAD, 0)
    con.visitMethodInsn(Opcodes.INVOKESPECIAL, obj, "<init>", "()V", false)

    con.visitVarInsn(Opcodes.ALOAD, 0)
    con.visitVarInsn(Opcodes.ALOAD, 1)
    con.visitFieldInsn(Opcodes.PUTFIELD, name, "callback", L(consumer))
    con.visitFieldInsn(Opcodes.GETSTATIC, mcForge, "EVENT_BUS", L(eventBus))
    con.visitVarInsn(Opcodes.ALOAD, 0)
    con.visitMethodInsn(Opcodes.INVOKEVIRTUAL, eventBus, "register", "(" + L(obj) + ")V", false)
    con.visitInsn(Opcodes.RETURN)
    con.visitMaxs(2, 2)
    con.visitEnd()
  }
  {
    const mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "on", "(" + L(event) + ")V", null, null)
    {
      const av = mv.visitAnnotation(L(subscribeEvent), true)
      av.visitEnum("priority", "Lnet/minecraftforge/fml/common/eventhandler/EventPriority;", priority.name());
      av.visitEnd()
    }
    mv.visitCode()
    mv.visitVarInsn(Opcodes.ALOAD, 0)
    mv.visitFieldInsn(Opcodes.GETFIELD, name, "callback", L(consumer))
    mv.visitVarInsn(Opcodes.ALOAD, 1)
    mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, consumer, "accept", "(" + L(obj) + ")V", true)
    mv.visitInsn(Opcodes.RETURN)
    mv.visitMaxs(2, 2)
    mv.visitEnd()
  }
  cw.visitEnd()

  const inst = defineClassBytes(name, cw.toByteArray())
    .getDeclaredConstructor(Consumer.class)
    .newInstance(new java.util.function.Consumer({
      accept: function (t) { cb(t) }
    }))
  LoadedInsts.push(inst)
  return inst;
}

const unregisterForge = inst => {
  MinecraftForge.EVENT_BUS.unregister(inst)
}

register("gameUnload", () => {
  LoadedInsts.forEach(unregisterForge)
  LoadedInsts.length = 0
})

export { registerForge, unregisterForge }