--- a/net/minecraft/world/inventory/EnchantmentMenu.java
+++ b/net/minecraft/world/inventory/EnchantmentMenu.java
@@ -31,19 +_,17 @@
 
 public class EnchantmentMenu extends AbstractContainerMenu {
     static final ResourceLocation EMPTY_SLOT_LAPIS_LAZULI = ResourceLocation.withDefaultNamespace("container/slot/lapis_lazuli");
-    private final Container enchantSlots = new SimpleContainer(2) {
-        @Override
-        public void setChanged() {
-            super.setChanged();
-            EnchantmentMenu.this.slotsChanged(this);
-        }
-    };
+    private final Container enchantSlots; // Paper - Add missing InventoryHolders - move down
     private final ContainerLevelAccess access;
     private final RandomSource random = RandomSource.create();
     private final DataSlot enchantmentSeed = DataSlot.standalone();
     public final int[] costs = new int[3];
     public final int[] enchantClue = new int[]{-1, -1, -1};
     public final int[] levelClue = new int[]{-1, -1, -1};
+    // CraftBukkit start
+    private org.bukkit.craftbukkit.inventory.view.CraftEnchantmentView bukkitEntity = null;
+    private final org.bukkit.entity.Player player;
+    // CraftBukkit end
 
     public EnchantmentMenu(int containerId, Inventory playerInventory) {
         this(containerId, playerInventory, ContainerLevelAccess.NULL);
@@ -51,6 +_,22 @@
 
     public EnchantmentMenu(int containerId, Inventory playerInventory, ContainerLevelAccess access) {
         super(MenuType.ENCHANTMENT, containerId);
+        // Paper start - Add missing InventoryHolders
+        this.enchantSlots = new SimpleContainer(this.createBlockHolder(access), 2) { // Paper - Add missing InventoryHolders
+            @Override
+            public void setChanged() {
+                super.setChanged();
+                EnchantmentMenu.this.slotsChanged(this);
+            }
+
+            // CraftBukkit start
+            @Override
+            public org.bukkit.Location getLocation() {
+                return access.getLocation();
+            }
+            // CraftBukkit end
+        };
+        // Paper end - Add missing InventoryHolders
         this.access = access;
         this.addSlot(new Slot(this.enchantSlots, 0, 15, 47) {
             @Override
@@ -80,13 +_,14 @@
         this.addDataSlot(DataSlot.shared(this.levelClue, 0));
         this.addDataSlot(DataSlot.shared(this.levelClue, 1));
         this.addDataSlot(DataSlot.shared(this.levelClue, 2));
+        this.player = (org.bukkit.entity.Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
     }
 
     @Override
     public void slotsChanged(Container inventory) {
         if (inventory == this.enchantSlots) {
             ItemStack item = inventory.getItem(0);
-            if (!item.isEmpty() && item.isEnchantable()) {
+            if (!item.isEmpty()) { // CraftBukkit - relax condition
                 this.access.execute((level, blockPos) -> {
                     IdMap<Holder<Enchantment>> holderIdMap = level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap();
                     int i1 = 0;
@@ -119,6 +_,42 @@
                         }
                     }
 
+                    // CraftBukkit start
+                    org.bukkit.craftbukkit.inventory.CraftItemStack craftItemStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item);
+                    org.bukkit.enchantments.EnchantmentOffer[] offers = new org.bukkit.enchantments.EnchantmentOffer[3];
+                    for (int j = 0; j < 3; ++j) {
+                        org.bukkit.enchantments.Enchantment enchantment = (this.enchantClue[j] >= 0) ? org.bukkit.craftbukkit.enchantments.CraftEnchantment.minecraftHolderToBukkit(holderIdMap.byId(this.enchantClue[j])) : null;
+                        offers[j] = (enchantment != null) ? new org.bukkit.enchantments.EnchantmentOffer(enchantment, this.levelClue[j], this.costs[j]) : null;
+                    }
+
+                    org.bukkit.event.enchantment.PrepareItemEnchantEvent event = new org.bukkit.event.enchantment.PrepareItemEnchantEvent(this.player, this.getBukkitView(), this.access.getLocation().getBlock(), craftItemStack, offers, i1);
+                    event.setCancelled(!item.isEnchantable());
+                    level.getCraftServer().getPluginManager().callEvent(event);
+
+                    if (event.isCancelled()) {
+                        for (int j = 0; j < 3; ++j) {
+                            this.costs[j] = 0;
+                            this.enchantClue[j] = -1;
+                            this.levelClue[j] = -1;
+                        }
+                        return;
+                    }
+
+                    for (int j = 0; j < 3; j++) {
+                        org.bukkit.enchantments.EnchantmentOffer offer = event.getOffers()[j];
+                        if (offer != null) {
+                            this.costs[j] = offer.getCost();
+                            this.enchantClue[j] = holderIdMap.getId(org.bukkit.craftbukkit.enchantments.CraftEnchantment
+                                .bukkitToMinecraftHolder(offer.getEnchantment()));
+                            this.levelClue[j] = offer.getEnchantmentLevel();
+                        } else {
+                            if (enchantClue[j] != -1) this.costs[j] = 0;
+                            this.enchantClue[j] = -1;
+                            this.levelClue[j] = -1;
+                        }
+                    }
+                    // CraftBukkit end
+
                     this.broadcastChanges();
                 });
             } else {
@@ -145,19 +_,53 @@
                 return false;
             } else {
                 this.access.execute((level, blockPos) -> {
-                    ItemStack itemStack = item;
+                    ItemStack itemStack = item; // Paper - diff on change
                     List<EnchantmentInstance> enchantmentList = this.getEnchantmentList(level.registryAccess(), item, id, this.costs[id]);
-                    if (!enchantmentList.isEmpty()) {
+                    // CraftBukkit start
+                    IdMap<Holder<Enchantment>> registry = level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap();
+                    if (true || !enchantmentList.isEmpty()) {
+                        // player.onEnchantmentPerformed(item, i); // Moved down
+                        java.util.Map<org.bukkit.enchantments.Enchantment, Integer> enchants = new java.util.HashMap<>();
+                        for (EnchantmentInstance instance : enchantmentList) {
+                            enchants.put(org.bukkit.craftbukkit.enchantments.CraftEnchantment.minecraftHolderToBukkit(instance.enchantment), instance.level);
+                        }
+                        org.bukkit.craftbukkit.inventory.CraftItemStack craftItemStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack);
+                        Holder<Enchantment> holder = registry.byId(this.enchantClue[id]);
+                        if (holder == null) return;
+                        org.bukkit.enchantments.Enchantment hintedEnchantment = org.bukkit.craftbukkit.enchantments.CraftEnchantment.minecraftHolderToBukkit(holder);
+                        int hintedEnchantmentLevel = this.levelClue[id];
+                        org.bukkit.event.enchantment.EnchantItemEvent event = new org.bukkit.event.enchantment.EnchantItemEvent((org.bukkit.entity.Player) player.getBukkitEntity(), this.getBukkitView(), this.access.getLocation().getBlock(), craftItemStack, this.costs[id], enchants, hintedEnchantment, hintedEnchantmentLevel, id);
+                        level.getCraftServer().getPluginManager().callEvent(event);
+                        int itemLevel = event.getExpLevelCost();
+                        if (event.isCancelled() || (itemLevel > player.experienceLevel && !player.getAbilities().instabuild) || event.getEnchantsToAdd().isEmpty()) {
+                            return;
+                        }
+                        // CraftBukkit end
+                        // Paper start
+                        itemStack = org.bukkit.craftbukkit.inventory.CraftItemStack.getOrCloneOnMutation(craftItemStack, event.getItem());
+                        if (itemStack != item) {
+                            this.enchantSlots.setItem(0, itemStack);
+                        }
+                        if (itemStack.is(Items.BOOK)) {
+                            itemStack = itemStack.transmuteCopy(Items.ENCHANTED_BOOK);
+                            this.enchantSlots.setItem(0, itemStack);
+                        }
+                        // Paper end
+
+                        // CraftBukkit start
+                        for (java.util.Map.Entry<org.bukkit.enchantments.Enchantment, Integer> entry : event.getEnchantsToAdd().entrySet()) {
+                            Holder<Enchantment> nms = org.bukkit.craftbukkit.enchantments.CraftEnchantment.bukkitToMinecraftHolder(entry.getKey());
+                            if (nms == null) {
+                                continue;
+                            }
+
+                            EnchantmentInstance weightedrandomenchant = new EnchantmentInstance(nms, entry.getValue());
+                            itemStack.enchant(weightedrandomenchant.enchantment, weightedrandomenchant.level);
+                        }
                         player.onEnchantmentPerformed(item, i);
-                        if (item.is(Items.BOOK)) {
-                            itemStack = item.transmuteCopy(Items.ENCHANTED_BOOK);
-                            this.enchantSlots.setItem(0, itemStack);
-                        }
-
-                        for (EnchantmentInstance enchantmentInstance : enchantmentList) {
-                            itemStack.enchant(enchantmentInstance.enchantment, enchantmentInstance.level);
-                        }
-
+                        // CraftBukkit end
+
+                        // CraftBukkit - TODO: let plugins change this
                         item1.consume(i, player);
                         if (item1.isEmpty()) {
                             this.enchantSlots.setItem(1, ItemStack.EMPTY);
@@ -214,6 +_,7 @@
 
     @Override
     public boolean stillValid(Player player) {
+        if (!this.checkReachable) return true; // CraftBukkit
         return stillValid(this.access, player, Blocks.ENCHANTING_TABLE);
     }
 
@@ -261,4 +_,23 @@
 
         return itemStack;
     }
+
+    // CraftBukkit start
+    @Override
+    public org.bukkit.craftbukkit.inventory.view.CraftEnchantmentView getBukkitView() {
+        if (this.bukkitEntity != null) {
+            return this.bukkitEntity;
+        }
+
+        org.bukkit.craftbukkit.inventory.CraftInventoryEnchanting inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryEnchanting(this.enchantSlots);
+        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.view.CraftEnchantmentView(this.player, inventory, this);
+        return this.bukkitEntity;
+    }
+    // CraftBukkit end
+
+    // Paper start - add enchantment seed update API
+    public void setEnchantmentSeed(int seed) {
+        this.enchantmentSeed.set(seed);
+    }
+    // Paper end - add enchantment seed update API
 }
