--- a/net/minecraft/world/inventory/ContainerEnchantTable.java
+++ b/net/minecraft/world/inventory/ContainerEnchantTable.java
@@ -20,6 +20,21 @@
 import net.minecraft.world.item.enchantment.WeightedRandomEnchant;
 import net.minecraft.world.level.block.Blocks;
 
+// CraftBukkit start
+import java.util.Map;
+import net.minecraft.world.item.enchantment.Enchantment;
+import org.bukkit.Location;
+import org.bukkit.NamespacedKey;
+import org.bukkit.craftbukkit.inventory.CraftInventoryEnchanting;
+import org.bukkit.craftbukkit.inventory.CraftInventoryView;
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
+import org.bukkit.craftbukkit.util.CraftNamespacedKey;
+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 {
 
     private final IInventory enchantSlots;
@@ -29,6 +44,10 @@
     public final int[] costs;
     public final int[] enchantClue;
     public final int[] levelClue;
+    // CraftBukkit start
+    private CraftInventoryView bukkitEntity = null;
+    private Player player;
+    // CraftBukkit end
 
     public ContainerEnchantTable(int i, PlayerInventory playerinventory) {
         this(i, playerinventory, ContainerAccess.NULL);
@@ -42,6 +61,13 @@
                 super.update();
                 ContainerEnchantTable.this.a((IInventory) this);
             }
+
+            // CraftBukkit start
+            @Override
+            public Location getLocation() {
+                return containeraccess.getLocation();
+            }
+            // CraftBukkit end
         };
         this.random = new Random();
         this.enchantmentSeed = ContainerProperty.a();
@@ -89,6 +115,9 @@
         this.a(ContainerProperty.a(this.levelClue, 0));
         this.a(ContainerProperty.a(this.levelClue, 1));
         this.a(ContainerProperty.a(this.levelClue, 2));
+        // CraftBukkit start
+        player = (Player) playerinventory.player.getBukkitEntity();
+        // CraftBukkit end
     }
 
     @Override
@@ -96,7 +125,7 @@
         if (iinventory == this.enchantSlots) {
             ItemStack itemstack = iinventory.getItem(0);
 
-            if (!itemstack.isEmpty() && itemstack.canEnchant()) {
+            if (!itemstack.isEmpty()) { // CraftBukkit - relax condition
                 this.access.a((world, blockposition) -> {
                     int i = 0;
 
@@ -158,6 +187,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) ? org.bukkit.enchantments.Enchantment.getByKey(CraftNamespacedKey.fromMinecraft(IRegistry.ENCHANTMENT.getKey(IRegistry.ENCHANTMENT.fromId(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.canEnchant());
+                    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] = IRegistry.ENCHANTMENT.getId(IRegistry.ENCHANTMENT.get(CraftNamespacedKey.toMinecraft(offer.getEnchantment().getKey())));
+                            this.levelClue[j] = offer.getEnchantmentLevel();
+                        } else {
+                            this.costs[j] = 0;
+                            this.enchantClue[j] = -1;
+                            this.levelClue[j] = -1;
+                        }
+                    }
+                    // CraftBukkit end
+
                     this.d();
                 });
             } else {
@@ -184,9 +248,24 @@
                 ItemStack itemstack2 = itemstack;
                 List<WeightedRandomEnchant> list = this.a(itemstack, i, this.costs[i]);
 
-                if (!list.isEmpty()) {
-                    entityhuman.enchantDone(itemstack, j);
+                // CraftBukkit start
+                if (true || !list.isEmpty()) {
+                    // entityhuman.enchantDone(itemstack, j); // Moved down
                     boolean flag = itemstack.a(Items.BOOK);
+                    Map<org.bukkit.enchantments.Enchantment, Integer> enchants = new java.util.HashMap<org.bukkit.enchantments.Enchantment, Integer>();
+                    for (Object obj : list) {
+                        WeightedRandomEnchant instance = (WeightedRandomEnchant) obj;
+                        enchants.put(org.bukkit.enchantments.Enchantment.getByKey(CraftNamespacedKey.fromMinecraft(IRegistry.ENCHANTMENT.getKey(instance.enchantment))), instance.level);
+                    }
+                    CraftItemStack item = CraftItemStack.asCraftMirror(itemstack2);
+
+                    EnchantItemEvent event = new EnchantItemEvent((Player) entityhuman.getBukkitEntity(), this.getBukkitView(), access.getLocation().getBlock(), item, this.costs[i], enchants, i);
+                    world.getCraftServer().getPluginManager().callEvent(event);
+
+                    int level = event.getExpLevelCost();
+                    if (event.isCancelled() || (level > entityhuman.experienceLevel && !entityhuman.getAbilities().instabuild) || event.getEnchantsToAdd().isEmpty()) {
+                        return;
+                    }
 
                     if (flag) {
                         itemstack2 = new ItemStack(Items.ENCHANTED_BOOK);
@@ -199,16 +278,29 @@
                         this.enchantSlots.setItem(0, itemstack2);
                     }
 
-                    for (int k = 0; k < list.size(); ++k) {
-                        WeightedRandomEnchant weightedrandomenchant = (WeightedRandomEnchant) list.get(k);
+                    for (Map.Entry<org.bukkit.enchantments.Enchantment, Integer> entry : event.getEnchantsToAdd().entrySet()) {
+                        try {
+                            if (flag) {
+                                NamespacedKey enchantId = entry.getKey().getKey();
+                                Enchantment nms = IRegistry.ENCHANTMENT.get(CraftNamespacedKey.toMinecraft(enchantId));
+                                if (nms == null) {
+                                    continue;
+                                }
 
-                        if (flag) {
-                            ItemEnchantedBook.a(itemstack2, weightedrandomenchant);
-                        } else {
-                            itemstack2.addEnchantment(weightedrandomenchant.enchantment, weightedrandomenchant.level);
+                                WeightedRandomEnchant weightedrandomenchant = new WeightedRandomEnchant(nms, entry.getValue());
+                                ItemEnchantedBook.a(itemstack2, weightedrandomenchant);
+                            } else {
+                                item.addUnsafeEnchantment(entry.getKey(), entry.getValue());
+                            }
+                        } catch (IllegalArgumentException e) {
+                            /* Just swallow invalid enchantments */
                         }
                     }
 
+                    entityhuman.enchantDone(itemstack, j);
+                    // CraftBukkit end
+
+                    // CraftBukkit - TODO: let plugins change this
                     if (!entityhuman.getAbilities().instabuild) {
                         itemstack1.subtract(j);
                         if (itemstack1.isEmpty()) {
@@ -265,6 +357,7 @@
 
     @Override
     public boolean canUse(EntityHuman entityhuman) {
+        if (!this.checkReachable) return true; // CraftBukkit
         return a(this.access, entityhuman, Blocks.ENCHANTING_TABLE);
     }
 
@@ -316,4 +409,17 @@
 
         return itemstack;
     }
+
+    // CraftBukkit start
+    @Override
+    public CraftInventoryView getBukkitView() {
+        if (bukkitEntity != null) {
+            return bukkitEntity;
+        }
+
+        CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.enchantSlots);
+        bukkitEntity = new CraftInventoryView(this.player, inventory, this);
+        return bukkitEntity;
+    }
+    // CraftBukkit end
 }
