--- a/net/minecraft/world/inventory/ContainerEnchantTable.java
+++ b/net/minecraft/world/inventory/ContainerEnchantTable.java
@@ -30,6 +30,19 @@
 import net.minecraft.world.level.block.BlockEnchantmentTable;
 import net.minecraft.world.level.block.Blocks;
 
+// CraftBukkit start
+import java.util.Map;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.enchantments.CraftEnchantment;
+import org.bukkit.craftbukkit.inventory.CraftInventoryEnchanting;
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
+import org.bukkit.craftbukkit.inventory.view.CraftEnchantmentView;
+import org.bukkit.enchantments.EnchantmentOffer;
+import org.bukkit.event.enchantment.EnchantItemEvent;
+import org.bukkit.event.enchantment.PrepareItemEnchantEvent;
+import org.bukkit.entity.Player;
+// CraftBukkit end
+
 public class ContainerEnchantTable extends Container {
 
     static final MinecraftKey EMPTY_SLOT_LAPIS_LAZULI = MinecraftKey.withDefaultNamespace("container/slot/lapis_lazuli");
@@ -40,6 +53,10 @@
     public final int[] costs;
     public final int[] enchantClue;
     public final int[] levelClue;
+    // CraftBukkit start
+    private CraftEnchantmentView bukkitEntity = null;
+    private Player player;
+    // CraftBukkit end
 
     public ContainerEnchantTable(int i, PlayerInventory playerinventory) {
         this(i, playerinventory, ContainerAccess.NULL);
@@ -53,6 +70,13 @@
                 super.setChanged();
                 ContainerEnchantTable.this.slotsChanged(this);
             }
+
+            // CraftBukkit start
+            @Override
+            public Location getLocation() {
+                return containeraccess.getLocation();
+            }
+            // CraftBukkit end
         };
         this.random = RandomSource.create();
         this.enchantmentSeed = ContainerProperty.standalone();
@@ -60,13 +84,13 @@
         this.enchantClue = new int[]{-1, -1, -1};
         this.levelClue = new int[]{-1, -1, -1};
         this.access = containeraccess;
-        this.addSlot(new Slot(this, this.enchantSlots, 0, 15, 47) {
+        this.addSlot(new Slot(this.enchantSlots, 0, 15, 47) { // CraftBukkit - decompile error
             @Override
             public int getMaxStackSize() {
                 return 1;
             }
         });
-        this.addSlot(new Slot(this, this.enchantSlots, 1, 35, 47) {
+        this.addSlot(new Slot(this.enchantSlots, 1, 35, 47) { // CraftBukkit - decompile error
             @Override
             public boolean mayPlace(ItemStack itemstack) {
                 return itemstack.is(Items.LAPIS_LAZULI);
@@ -88,6 +112,9 @@
         this.addDataSlot(ContainerProperty.shared(this.levelClue, 0));
         this.addDataSlot(ContainerProperty.shared(this.levelClue, 1));
         this.addDataSlot(ContainerProperty.shared(this.levelClue, 2));
+        // CraftBukkit start
+        player = (Player) playerinventory.player.getBukkitEntity();
+        // CraftBukkit end
     }
 
     @Override
@@ -95,7 +122,7 @@
         if (iinventory == this.enchantSlots) {
             ItemStack itemstack = iinventory.getItem(0);
 
-            if (!itemstack.isEmpty() && itemstack.isEnchantable()) {
+            if (!itemstack.isEmpty()) { // CraftBukkit - relax condition
                 this.access.execute((world, blockposition) -> {
                     Registry<Holder<Enchantment>> registry = world.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap();
                     int i = 0;
@@ -135,6 +162,41 @@
                         }
                     }
 
+                    // CraftBukkit start
+                    CraftItemStack item = CraftItemStack.asCraftMirror(itemstack);
+                    org.bukkit.enchantments.EnchantmentOffer[] offers = new EnchantmentOffer[3];
+                    for (j = 0; j < 3; ++j) {
+                        org.bukkit.enchantments.Enchantment enchantment = (this.enchantClue[j] >= 0) ? CraftEnchantment.minecraftHolderToBukkit(registry.byId(this.enchantClue[j])) : null;
+                        offers[j] = (enchantment != null) ? new EnchantmentOffer(enchantment, this.levelClue[j], this.costs[j]) : null;
+                    }
+
+                    PrepareItemEnchantEvent event = new PrepareItemEnchantEvent(player, this.getBukkitView(), access.getLocation().getBlock(), item, offers, i);
+                    event.setCancelled(!itemstack.isEnchantable());
+                    world.getCraftServer().getPluginManager().callEvent(event);
+
+                    if (event.isCancelled()) {
+                        for (j = 0; j < 3; ++j) {
+                            this.costs[j] = 0;
+                            this.enchantClue[j] = -1;
+                            this.levelClue[j] = -1;
+                        }
+                        return;
+                    }
+
+                    for (j = 0; j < 3; j++) {
+                        EnchantmentOffer offer = event.getOffers()[j];
+                        if (offer != null) {
+                            this.costs[j] = offer.getCost();
+                            this.enchantClue[j] = registry.getId(CraftEnchantment.bukkitToMinecraftHolder(offer.getEnchantment()));
+                            this.levelClue[j] = offer.getEnchantmentLevel();
+                        } else {
+                            this.costs[j] = 0;
+                            this.enchantClue[j] = -1;
+                            this.levelClue[j] = -1;
+                        }
+                    }
+                    // CraftBukkit end
+
                     this.broadcastChanges();
                 });
             } else {
@@ -162,21 +224,46 @@
                     ItemStack itemstack2 = itemstack;
                     List<WeightedRandomEnchant> list = this.getEnchantmentList(world.registryAccess(), itemstack, i, this.costs[i]);
 
-                    if (!list.isEmpty()) {
-                        entityhuman.onEnchantmentPerformed(itemstack, j);
+                    // CraftBukkit start
+                    Registry<Holder<Enchantment>> registry = world.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap();
+                    if (true || !list.isEmpty()) {
+                        // entityhuman.onEnchantmentPerformed(itemstack, j); // Moved down
+                        Map<org.bukkit.enchantments.Enchantment, Integer> enchants = new java.util.HashMap<org.bukkit.enchantments.Enchantment, Integer>();
+                        for (WeightedRandomEnchant instance : list) {
+                            enchants.put(CraftEnchantment.minecraftHolderToBukkit(instance.enchantment), instance.level);
+                        }
+                        CraftItemStack item = CraftItemStack.asCraftMirror(itemstack2);
+
+                        org.bukkit.enchantments.Enchantment hintedEnchantment = CraftEnchantment.minecraftHolderToBukkit(registry.byId(enchantClue[i]));
+                        int hintedEnchantmentLevel = levelClue[i];
+                        EnchantItemEvent event = new EnchantItemEvent((Player) entityhuman.getBukkitEntity(), this.getBukkitView(), access.getLocation().getBlock(), item, this.costs[i], enchants, hintedEnchantment, hintedEnchantmentLevel, i);
+                        world.getCraftServer().getPluginManager().callEvent(event);
+
+                        int level = event.getExpLevelCost();
+                        if (event.isCancelled() || (level > entityhuman.experienceLevel && !entityhuman.getAbilities().instabuild) || event.getEnchantsToAdd().isEmpty()) {
+                            return;
+                        }
+                        // CraftBukkit end
                         if (itemstack.is(Items.BOOK)) {
                             itemstack2 = itemstack.transmuteCopy(Items.ENCHANTED_BOOK);
                             this.enchantSlots.setItem(0, itemstack2);
                         }
 
-                        Iterator iterator = list.iterator();
-
-                        while (iterator.hasNext()) {
-                            WeightedRandomEnchant weightedrandomenchant = (WeightedRandomEnchant) iterator.next();
+                        // CraftBukkit start
+                        for (Map.Entry<org.bukkit.enchantments.Enchantment, Integer> entry : event.getEnchantsToAdd().entrySet()) {
+                            Holder<Enchantment> nms = CraftEnchantment.bukkitToMinecraftHolder(entry.getKey());
+                            if (nms == null) {
+                                continue;
+                            }
 
+                            WeightedRandomEnchant weightedrandomenchant = new WeightedRandomEnchant(nms, entry.getValue());
                             itemstack2.enchant(weightedrandomenchant.enchantment, weightedrandomenchant.level);
                         }
 
+                        entityhuman.onEnchantmentPerformed(itemstack, j);
+                        // CraftBukkit end
+
+                        // CraftBukkit - TODO: let plugins change this
                         itemstack1.consume(j, entityhuman);
                         if (itemstack1.isEmpty()) {
                             this.enchantSlots.setItem(1, ItemStack.EMPTY);
@@ -243,6 +330,7 @@
 
     @Override
     public boolean stillValid(EntityHuman entityhuman) {
+        if (!this.checkReachable) return true; // CraftBukkit
         return stillValid(this.access, entityhuman, Blocks.ENCHANTING_TABLE);
     }
 
@@ -293,4 +381,17 @@
 
         return itemstack;
     }
+
+    // CraftBukkit start
+    @Override
+    public CraftEnchantmentView getBukkitView() {
+        if (bukkitEntity != null) {
+            return bukkitEntity;
+        }
+
+        CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.enchantSlots);
+        bukkitEntity = new CraftEnchantmentView(this.player, inventory, this);
+        return bukkitEntity;
+    }
+    // CraftBukkit end
 }
