package com.yourname.premiumah.gui;

import com.yourname.premiumah.config.ButtonConfig;
import com.yourname.premiumah.config.ConfigManager;
import com.yourname.premiumah.config.MessageManager;
import com.yourname.premiumah.manager.AuctionHouseManager;
import com.yourname.premiumah.manager.ClaimManager;
import com.yourname.premiumah.manager.ListingManager;
import com.yourname.premiumah.model.ActionResult;
import com.yourname.premiumah.model.ClaimRecord;
import com.yourname.premiumah.model.ClaimType;
import com.yourname.premiumah.model.Listing;
import com.yourname.premiumah.model.ListingCreationResult;
import com.yourname.premiumah.model.SortMode;
import com.yourname.premiumah.util.InventoryUtil;
import com.yourname.premiumah.util.MaterialUtil;
import com.yourname.premiumah.util.TextUtil;
import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.Sound;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.plugin.java.JavaPlugin;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public final class GuiManager {
    private static final int SELL_ITEM_SLOT = 22;

    private final JavaPlugin plugin;
    private final ConfigManager config;
    private final MessageManager messages;
    private final ListingManager listings;
    private final ClaimManager claims;
    private final AuctionHouseManager auctionHouse;
    private final Map<UUID, BrowseState> browseStates = new ConcurrentHashMap<>();
    private final Map<UUID, SellSession> sellSessions = new ConcurrentHashMap<>();
    private final Map<UUID, Long> clickDebounce = new ConcurrentHashMap<>();

    public GuiManager(JavaPlugin plugin,
                      ConfigManager config,
                      MessageManager messages,
                      ListingManager listings,
                      ClaimManager claims,
                      AuctionHouseManager auctionHouse) {
        this.plugin = plugin;
        this.config = config;
        this.messages = messages;
        this.listings = listings;
        this.claims = claims;
        this.auctionHouse = auctionHouse;
    }

    public void openMain(Player player) {
        GuiHolder holder = new GuiHolder(GuiType.MAIN, player.getUniqueId(), 0, null, null, null);
        Inventory inventory = create(holder, config.guiSize("main"), config.guiTitle("main"), Map.of());
        fill(inventory);
        inventory.setItem(11, button("browse", Map.of()));
        inventory.setItem(13, button("sell", Map.of()));
        inventory.setItem(15, button("my-listings", Map.of()));
        inventory.setItem(29, button("claims", Map.of()));
        if (player.hasPermission("premiumah.admin")) {
            inventory.setItem(31, button("admin", Map.of()));
        }
        inventory.setItem(33, button("close", Map.of()));
        player.openInventory(inventory);
        play(player, "open");
    }

    public void openBrowse(Player player, int requestedPage) {
        BrowseState state = browseState(player);
        List<Listing> active = listings.activeListings(state.sortMode(), state.filter());
        openListingView(player, GuiType.BROWSE, active, requestedPage, "browse", null, state.sortMode(), state.filter());
    }

    public void openMyListings(Player player, int requestedPage) {
        List<Listing> own = listings.activeListingsBySeller(player.getUniqueId(), SortMode.NEWEST);
        openListingView(player, GuiType.MY_LISTINGS, own, requestedPage, "my-listings", null, SortMode.NEWEST, null);
    }

    public void openAdmin(Player player, int requestedPage) {
        BrowseState state = browseState(player);
        List<Listing> active = listings.activeListings(state.sortMode(), state.filter());
        openListingView(player, GuiType.ADMIN, active, requestedPage, "admin", null, state.sortMode(), state.filter());
    }

    public void openAdminForSeller(Player player, String sellerName, int requestedPage) {
        List<Listing> active = listings.activeListingsBySellerName(sellerName, SortMode.NEWEST);
        openListingView(player, GuiType.ADMIN, active, requestedPage, "admin", sellerName, SortMode.NEWEST, null);
    }

    public void openClaims(Player player, int requestedPage) {
        List<ClaimRecord> playerClaims = claims.claimsFor(player.getUniqueId());
        int page = normalizePage(requestedPage, playerClaims.size());
        GuiHolder holder = new GuiHolder(GuiType.CLAIMS, player.getUniqueId(), page, null, SortMode.NEWEST, null);
        Inventory inventory = create(holder, config.guiSize("claims"), config.guiTitle("claims"), Map.of("page", String.valueOf(page + 1)));
        fill(inventory);
        List<Integer> slots = config.listingSlots();
        int start = page * slots.size();
        for (int i = 0; i < slots.size(); i++) {
            int index = start + i;
            if (index >= playerClaims.size()) {
                break;
            }
            ClaimRecord claim = playerClaims.get(index);
            int slot = slots.get(i);
            holder.claimSlots().put(slot, claim.id());
            inventory.setItem(slot, claimItem(claim));
        }
        addPagedControls(inventory, page, playerClaims.size(), slots.size(), "back");
        player.openInventory(inventory);
        play(player, "open");
    }

    public void openSell(Player player, boolean takeHeldItem) {
        if (!player.hasPermission("premiumah.sell")) {
            messages.send(player, "no-permission");
            play(player, "fail");
            return;
        }
        SellSession session = sellSessions.computeIfAbsent(player.getUniqueId(), ignored -> new SellSession());
        if (takeHeldItem && InventoryUtil.isAir(session.rawItem())) {
            ItemStack held = player.getInventory().getItemInMainHand();
            if (!InventoryUtil.isAir(held)) {
                session.item(held);
                player.getInventory().setItemInMainHand(null);
            }
        }
        session.awaitingPrice(false);
        session.completed(false);

        GuiHolder holder = new GuiHolder(GuiType.SELL, player.getUniqueId(), 0, null, null, null);
        Inventory inventory = create(holder, config.guiSize("sell"), config.guiTitle("sell"), Map.of());
        fill(inventory);
        inventory.setItem(SELL_ITEM_SLOT, session.item());
        inventory.setItem(20, customItem(Material.GOLD_INGOT, "<#f5d58a>Set Price", List.of(
                "<#9ca3af>Current: <#ffffff>" + (session.price() <= 0.0D ? "Not set" : auctionHouse.formatMoney(session.price())),
                "<#6ee7b7>Click to type a price."
        ), Map.of()));
        inventory.setItem(24, button("confirm", Map.of()));
        inventory.setItem(36, button("back", Map.of()));
        inventory.setItem(40, button("cancel", Map.of()));
        player.openInventory(inventory);
        play(player, "open");
    }

    public void openConfirmBuy(Player player, String listingId) {
        Listing listing = listings.get(listingId).orElse(null);
        if (listing == null || !listing.isActive(System.currentTimeMillis())) {
            messages.send(player, "listing-no-longer-available");
            play(player, "fail");
            openBrowse(player, 0);
            return;
        }
        GuiHolder holder = new GuiHolder(GuiType.CONFIRM_BUY, player.getUniqueId(), 0, listing.id(), null, null);
        Inventory inventory = create(holder, config.guiSize("confirm-buy"), config.guiTitle("confirm-buy"), Map.of());
        fill(inventory);
        inventory.setItem(11, button("confirm", Map.of()));
        inventory.setItem(13, listingItem(listing, "confirm-buy"));
        inventory.setItem(15, button("cancel", Map.of()));
        player.openInventory(inventory);
        play(player, "open");
    }

    public void handleClick(InventoryClickEvent event) {
        if (!(event.getWhoClicked() instanceof Player player) || !(event.getView().getTopInventory().getHolder() instanceof GuiHolder holder)) {
            return;
        }
        int topSize = event.getView().getTopInventory().getSize();
        int rawSlot = event.getRawSlot();

        if (holder.type() != GuiType.SELL && rawSlot >= topSize) {
            if (event.isShiftClick()) {
                event.setCancelled(true);
            }
            return;
        }
        if (holder.type() == GuiType.SELL && rawSlot >= topSize) {
            if (event.isShiftClick()) {
                event.setCancelled(true);
            }
            return;
        }

        if (holder.type() == GuiType.SELL && rawSlot == SELL_ITEM_SLOT) {
            event.setCancelled(false);
            Bukkit.getScheduler().runTask(plugin, () -> syncSellSlot(player, event.getView().getTopInventory()));
            return;
        }

        event.setCancelled(true);
        if (rawSlot < 0 || !canClick(player)) {
            return;
        }

        switch (holder.type()) {
            case MAIN -> handleMainClick(player, rawSlot);
            case BROWSE -> handleBrowseClick(player, holder, rawSlot, event.getClick());
            case MY_LISTINGS -> handleMyListingsClick(player, holder, rawSlot);
            case CLAIMS -> handleClaimsClick(player, holder, rawSlot);
            case SELL -> handleSellClick(player, event.getView().getTopInventory(), rawSlot);
            case CONFIRM_BUY -> handleConfirmClick(player, holder, rawSlot);
            case ADMIN -> handleAdminClick(player, holder, rawSlot, event.getClick());
        }
    }

    public void handleDrag(InventoryDragEvent event) {
        if (!(event.getWhoClicked() instanceof Player player) || !(event.getView().getTopInventory().getHolder() instanceof GuiHolder holder)) {
            return;
        }
        int topSize = event.getView().getTopInventory().getSize();
        boolean touchesTop = event.getRawSlots().stream().anyMatch(slot -> slot < topSize);
        if (!touchesTop) {
            return;
        }
        if (holder.type() == GuiType.SELL && event.getRawSlots().stream().allMatch(slot -> slot == SELL_ITEM_SLOT || slot >= topSize)) {
            Bukkit.getScheduler().runTask(plugin, () -> syncSellSlot(player, event.getView().getTopInventory()));
            return;
        }
        event.setCancelled(true);
    }

    public void handleClose(InventoryCloseEvent event) {
        if (!(event.getPlayer() instanceof Player player) || !(event.getInventory().getHolder() instanceof GuiHolder holder)) {
            return;
        }
        if (holder.type() != GuiType.SELL) {
            return;
        }
        SellSession session = sellSessions.get(player.getUniqueId());
        if (session == null || session.awaitingPrice() || session.completed()) {
            return;
        }
        ItemStack item = event.getInventory().getItem(SELL_ITEM_SLOT);
        if (InventoryUtil.isAir(item)) {
            item = session.item();
        }
        returnItem(player, item);
        sellSessions.remove(player.getUniqueId());
    }

    public void handleChat(AsyncChatEvent event) {
        Player player = event.getPlayer();
        SellSession session = sellSessions.get(player.getUniqueId());
        if (session == null || !session.awaitingPrice()) {
            return;
        }
        event.setCancelled(true);
        String input = PlainTextComponentSerializer.plainText().serialize(event.message()).trim();
        Bukkit.getScheduler().runTask(plugin, () -> completePricePrompt(player, input));
    }

    public void setSort(Player player, SortMode sortMode) {
        browseState(player).sortMode(sortMode);
    }

    public void shutdown() {
        for (Player player : Bukkit.getOnlinePlayers()) {
            if (player.getOpenInventory().getTopInventory().getHolder() instanceof GuiHolder) {
                player.closeInventory();
            }
        }
        for (UUID uuid : new ArrayList<>(sellSessions.keySet())) {
            Player player = Bukkit.getPlayer(uuid);
            SellSession session = sellSessions.remove(uuid);
            if (player != null && session != null && !InventoryUtil.isAir(session.rawItem())) {
                returnItem(player, session.rawItem());
            }
            if (session != null) {
                session.cancelTimeout();
            }
        }
        sellSessions.clear();
        browseStates.clear();
        clickDebounce.clear();
    }

    public void handleQuit(Player player) {
        SellSession session = sellSessions.remove(player.getUniqueId());
        if (session != null) {
            session.cancelTimeout();
            if (!InventoryUtil.isAir(session.rawItem())) {
                returnItem(player, session.rawItem());
            }
        }
        browseStates.remove(player.getUniqueId());
        clickDebounce.remove(player.getUniqueId());
    }

    private void handleMainClick(Player player, int slot) {
        play(player, "click");
        if (slot == 11) {
            openBrowse(player, 0);
        } else if (slot == 13) {
            openSell(player, true);
        } else if (slot == 15) {
            openMyListings(player, 0);
        } else if (slot == 29) {
            openClaims(player, 0);
        } else if (slot == 31 && player.hasPermission("premiumah.admin")) {
            openAdmin(player, 0);
        } else if (slot == 33) {
            player.closeInventory();
        }
    }

    private void handleBrowseClick(Player player, GuiHolder holder, int slot, ClickType click) {
        if (holder.listingSlots().containsKey(slot)) {
            Listing listing = listings.get(holder.listingSlots().get(slot)).orElse(null);
            if (listing == null) {
                messages.send(player, "listing-no-longer-available");
                play(player, "fail");
                openBrowse(player, holder.page());
                return;
            }
            if (click.isRightClick() && player.hasPermission("premiumah.admin.remove")) {
                ActionResult result = auctionHouse.adminRemoveListing(listing.id());
                messages.send(player, result.messageKey(), result.placeholders());
                play(player, result.success() ? "success" : "fail");
                openBrowse(player, holder.page());
                return;
            }
            if (click.isShiftClick()) {
                browseState(player).filter(listing.rawItem().getType());
                openBrowse(player, 0);
                return;
            }
            openConfirmBuy(player, listing.id());
            return;
        }
        handlePagedControl(player, holder, slot, GuiType.BROWSE);
    }

    private void handleMyListingsClick(Player player, GuiHolder holder, int slot) {
        if (holder.listingSlots().containsKey(slot)) {
            ActionResult result = auctionHouse.cancelListing(player, holder.listingSlots().get(slot));
            messages.send(player, result.messageKey(), result.placeholders());
            play(player, result.success() ? "success" : "fail");
            openMyListings(player, holder.page());
            return;
        }
        handlePagedControl(player, holder, slot, GuiType.MY_LISTINGS);
    }

    private void handleClaimsClick(Player player, GuiHolder holder, int slot) {
        if (holder.claimSlots().containsKey(slot)) {
            ActionResult result = auctionHouse.claim(player, holder.claimSlots().get(slot));
            messages.send(player, result.messageKey(), result.placeholders());
            play(player, result.success() ? "success" : "fail");
            openClaims(player, holder.page());
            return;
        }
        handlePagedControl(player, holder, slot, GuiType.CLAIMS);
    }

    private void handleSellClick(Player player, Inventory inventory, int slot) {
        play(player, "click");
        if (slot == 20) {
            beginPricePrompt(player, inventory);
        } else if (slot == 24) {
            confirmSell(player, inventory);
        } else if (slot == 36) {
            closeSellReturningItem(player, inventory, true);
            openMain(player);
        } else if (slot == 40) {
            closeSellReturningItem(player, inventory, false);
        }
    }

    private void handleConfirmClick(Player player, GuiHolder holder, int slot) {
        if (slot == 11) {
            ActionResult result = auctionHouse.buyListing(player, holder.context());
            messages.send(player, result.messageKey(), result.placeholders());
            play(player, result.success() ? "success" : "fail");
            if (result.success()) {
                openBrowse(player, 0);
            } else {
                player.closeInventory();
            }
        } else if (slot == 15) {
            play(player, "click");
            openBrowse(player, 0);
        }
    }

    private void handleAdminClick(Player player, GuiHolder holder, int slot, ClickType click) {
        if (holder.listingSlots().containsKey(slot)) {
            Listing listing = listings.get(holder.listingSlots().get(slot)).orElse(null);
            if (listing == null) {
                messages.send(player, "listing-no-longer-available");
                openAdmin(player, holder.page());
                return;
            }
            if (click.isRightClick() || click.isShiftClick()) {
                ActionResult result = auctionHouse.adminRemoveListing(listing.id());
                messages.send(player, result.messageKey(), result.placeholders());
                play(player, result.success() ? "success" : "fail");
                openAdmin(player, holder.page());
            } else {
                openConfirmBuy(player, listing.id());
            }
            return;
        }
        handlePagedControl(player, holder, slot, GuiType.ADMIN);
    }

    private void handlePagedControl(Player player, GuiHolder holder, int slot, GuiType type) {
        play(player, "click");
        if (slot == 45) {
            openByType(player, type, Math.max(0, holder.page() - 1), holder.context());
        } else if (slot == 49) {
            openMain(player);
        } else if (slot == 53) {
            openByType(player, type, holder.page() + 1, holder.context());
        } else if (slot == 50 && (type == GuiType.BROWSE || type == GuiType.ADMIN)) {
            BrowseState state = browseState(player);
            state.sortMode(state.sortMode().next());
            openByType(player, type, 0, holder.context());
        } else if (slot == 48 && (type == GuiType.BROWSE || type == GuiType.ADMIN)) {
            browseState(player).filter(null);
            openByType(player, type, 0, holder.context());
        }
    }

    private void openByType(Player player, GuiType type, int page, String context) {
        switch (type) {
            case BROWSE -> openBrowse(player, page);
            case MY_LISTINGS -> openMyListings(player, page);
            case CLAIMS -> openClaims(player, page);
            case ADMIN -> {
                if (context == null || context.isBlank()) {
                    openAdmin(player, page);
                } else {
                    openAdminForSeller(player, context, page);
                }
            }
            default -> openMain(player);
        }
    }

    private void openListingView(Player player,
                                 GuiType type,
                                 List<Listing> source,
                                 int requestedPage,
                                 String titleKey,
                                 String context,
                                 SortMode sortMode,
                                 Material filter) {
        int page = normalizePage(requestedPage, source.size());
        GuiHolder holder = new GuiHolder(type, player.getUniqueId(), page, context, sortMode, filter);
        Inventory inventory = create(holder, config.guiSize(titleKey), config.guiTitle(titleKey), Map.of("page", String.valueOf(page + 1)));
        fill(inventory);
        List<Integer> slots = config.listingSlots();
        int start = page * slots.size();
        String loreKey = type == GuiType.MY_LISTINGS ? "my-listing" : "listing";
        for (int i = 0; i < slots.size(); i++) {
            int index = start + i;
            if (index >= source.size()) {
                break;
            }
            Listing listing = source.get(index);
            int slot = slots.get(i);
            holder.listingSlots().put(slot, listing.id());
            inventory.setItem(slot, listingItem(listing, loreKey));
        }
        addPagedControls(inventory, page, source.size(), slots.size(), "back");
        if (type == GuiType.BROWSE || type == GuiType.ADMIN) {
            BrowseState state = browseState(player);
            inventory.setItem(48, button("filter", Map.of("filter", state.filter() == null ? "All" : MaterialUtil.pretty(state.filter()))));
            inventory.setItem(50, button("sort", Map.of("sort", state.sortMode().displayName())));
        }
        player.openInventory(inventory);
        play(player, "open");
    }

    private void addPagedControls(Inventory inventory, int page, int totalItems, int pageSize, String backKey) {
        if (page > 0) {
            inventory.setItem(45, button("previous", Map.of()));
        }
        inventory.setItem(49, button(backKey, Map.of()));
        if ((page + 1) * pageSize < totalItems) {
            inventory.setItem(53, button("next", Map.of()));
        }
    }

    private Inventory create(GuiHolder holder, int size, String title, Map<String, String> placeholders) {
        Inventory inventory = Bukkit.createInventory(holder, size, messages.component(messages.apply(title, placeholders)));
        holder.inventory(inventory);
        return inventory;
    }

    private void fill(Inventory inventory) {
        if (!config.fillerEnabled()) {
            return;
        }
        ItemStack filler = customItem(config.fillerMaterial(), config.fillerName(), List.of(), Map.of());
        for (int i = 0; i < inventory.getSize(); i++) {
            if (inventory.getItem(i) == null) {
                inventory.setItem(i, filler);
            }
        }
    }

    private ItemStack button(String key, Map<String, String> placeholders) {
        ButtonConfig button = config.button(key);
        return customItem(button.material(), button.name(), button.lore(), placeholders);
    }

    private ItemStack customItem(Material material, String name, List<String> lore, Map<String, String> placeholders) {
        ItemStack item = new ItemStack(material);
        ItemMeta meta = item.getItemMeta();
        if (meta != null) {
            meta.displayName(messages.component(messages.apply(name, placeholders)));
            if (lore != null && !lore.isEmpty()) {
                meta.lore(lore.stream().map(line -> messages.component(messages.apply(line, placeholders))).toList());
            }
            meta.addItemFlags(ItemFlag.values());
            item.setItemMeta(meta);
        }
        return item;
    }

    private ItemStack listingItem(Listing listing, String loreKey) {
        ItemStack item = listing.item();
        ItemMeta meta = item.getItemMeta();
        if (meta != null) {
            List<net.kyori.adventure.text.Component> lore = new ArrayList<>();
            if (meta.lore() != null && !meta.lore().isEmpty()) {
                lore.addAll(meta.lore());
                lore.add(TextUtil.component(""));
            }
            lore.addAll(messages.guiLore(loreKey, listingPlaceholders(listing)));
            meta.lore(lore);
            item.setItemMeta(meta);
        }
        return item;
    }

    private ItemStack claimItem(ClaimRecord claim) {
        ItemStack item;
        if (claim.type() == ClaimType.MONEY) {
            item = customItem(Material.EMERALD, "<#6ee7b7>Sale Payment <#ffffff>{amount}", messages.rawGuiLore("claim"), claimPlaceholders(claim));
        } else {
            item = claim.item();
            ItemMeta meta = item.getItemMeta();
            if (meta != null) {
                List<net.kyori.adventure.text.Component> lore = new ArrayList<>();
                if (meta.lore() != null && !meta.lore().isEmpty()) {
                    lore.addAll(meta.lore());
                    lore.add(TextUtil.component(""));
                }
                lore.addAll(messages.guiLore("claim", claimPlaceholders(claim)));
                meta.lore(lore);
                item.setItemMeta(meta);
            }
        }
        return item;
    }

    private Map<String, String> listingPlaceholders(Listing listing) {
        return Map.of(
                "seller", listing.sellerName(),
                "price", auctionHouse.formatMoney(listing.price()),
                "remaining", com.yourname.premiumah.util.TimeUtil.remaining(listing.expiresAt()),
                "id", listing.id()
        );
    }

    private Map<String, String> claimPlaceholders(ClaimRecord claim) {
        String itemName = claim.type() == ClaimType.MONEY ? auctionHouse.formatMoney(claim.moneyAmount()) : auctionHouse.itemName(claim.item());
        Map<String, String> placeholders = new HashMap<>();
        placeholders.put("reason", claim.reason().displayName());
        placeholders.put("id", claim.listingId() == null ? "N/A" : claim.listingId());
        placeholders.put("age", com.yourname.premiumah.util.TimeUtil.age(claim.createdAt()));
        placeholders.put("item", itemName);
        placeholders.put("amount", itemName);
        return placeholders;
    }

    private BrowseState browseState(Player player) {
        return browseStates.computeIfAbsent(player.getUniqueId(), ignored -> new BrowseState(config.defaultSort(), null));
    }

    private int normalizePage(int requestedPage, int totalItems) {
        int pageSize = Math.max(1, config.listingSlots().size());
        int maxPage = Math.max(0, (int) Math.ceil(totalItems / (double) pageSize) - 1);
        return Math.max(0, Math.min(requestedPage, maxPage));
    }

    private boolean canClick(Player player) {
        long now = System.currentTimeMillis();
        long last = clickDebounce.getOrDefault(player.getUniqueId(), 0L);
        if (now - last < config.clickDebounceMillis()) {
            return false;
        }
        clickDebounce.put(player.getUniqueId(), now);
        return true;
    }

    private void syncSellSlot(Player player, Inventory inventory) {
        SellSession session = sellSessions.get(player.getUniqueId());
        if (session != null) {
            session.item(inventory.getItem(SELL_ITEM_SLOT));
        }
    }

    private void beginPricePrompt(Player player, Inventory inventory) {
        SellSession session = sellSessions.computeIfAbsent(player.getUniqueId(), ignored -> new SellSession());
        session.item(inventory.getItem(SELL_ITEM_SLOT));
        inventory.setItem(SELL_ITEM_SLOT, null);
        session.awaitingPrice(true);
        session.completed(false);
        session.cancelTimeout();
        session.timeoutTask(Bukkit.getScheduler().runTaskLater(plugin, () -> {
            SellSession current = sellSessions.get(player.getUniqueId());
            if (current != null && current.awaitingPrice()) {
                current.awaitingPrice(false);
                returnItem(player, current.rawItem());
                current.cancelTimeout();
                sellSessions.remove(player.getUniqueId());
                messages.send(player, "price-prompt-timeout");
                play(player, "fail");
            }
        }, config.chatPriceTimeoutSeconds() * 20L));
        player.closeInventory();
        messages.send(player, "price-prompt");
    }

    private void completePricePrompt(Player player, String input) {
        SellSession session = sellSessions.get(player.getUniqueId());
        if (session == null || !session.awaitingPrice()) {
            return;
        }
        session.cancelTimeout();
        if (input.equalsIgnoreCase("cancel")) {
            session.awaitingPrice(false);
            returnItem(player, session.rawItem());
            sellSessions.remove(player.getUniqueId());
            messages.send(player, "price-prompt-cancelled");
            play(player, "click");
            return;
        }
        double price;
        try {
            price = Double.parseDouble(input.replace(",", ""));
        } catch (NumberFormatException exception) {
            messages.send(player, "invalid-price");
            play(player, "fail");
            beginPricePrompt(player, createTempSellInventory(player, session));
            return;
        }
        if (!Double.isFinite(price) || price <= 0.0D) {
            messages.send(player, "invalid-price");
            play(player, "fail");
            beginPricePrompt(player, createTempSellInventory(player, session));
            return;
        }
        session.price(price);
        session.awaitingPrice(false);
        messages.send(player, "price-set", Map.of("price", auctionHouse.formatMoney(price)));
        openSell(player, false);
    }

    private Inventory createTempSellInventory(Player player, SellSession session) {
        GuiHolder holder = new GuiHolder(GuiType.SELL, player.getUniqueId(), 0, null, null, null);
        Inventory inventory = create(holder, config.guiSize("sell"), config.guiTitle("sell"), Map.of());
        inventory.setItem(SELL_ITEM_SLOT, session.item());
        return inventory;
    }

    private void confirmSell(Player player, Inventory inventory) {
        SellSession session = sellSessions.computeIfAbsent(player.getUniqueId(), ignored -> new SellSession());
        ItemStack item = inventory.getItem(SELL_ITEM_SLOT);
        if (InventoryUtil.isAir(item)) {
            messages.send(player, "no-item-in-hand");
            play(player, "fail");
            return;
        }
        if (session.price() <= 0.0D) {
            messages.send(player, "invalid-price");
            play(player, "fail");
            return;
        }
        inventory.setItem(SELL_ITEM_SLOT, null);
        ListingCreationResult result = auctionHouse.createListing(player, item, session.price());
        if (!result.success()) {
            inventory.setItem(SELL_ITEM_SLOT, item);
            messages.send(player, result.messageKey(), result.placeholders());
            play(player, "fail");
            return;
        }
        session.completed(true);
        session.cancelTimeout();
        sellSessions.remove(player.getUniqueId());
        messages.send(player, result.messageKey(), result.placeholders());
        play(player, "success");
        openBrowse(player, 0);
    }

    private void closeSellReturningItem(Player player, Inventory inventory, boolean keepOpen) {
        SellSession session = sellSessions.remove(player.getUniqueId());
        ItemStack item = inventory.getItem(SELL_ITEM_SLOT);
        inventory.setItem(SELL_ITEM_SLOT, null);
        if (session != null) {
            session.completed(true);
            session.cancelTimeout();
            if (InventoryUtil.isAir(item)) {
                item = session.rawItem();
            }
        }
        returnItem(player, item);
        if (!keepOpen) {
            player.closeInventory();
        }
    }

    private void returnItem(Player player, ItemStack item) {
        if (InventoryUtil.isAir(item)) {
            return;
        }
        Map<Integer, ItemStack> leftovers = InventoryUtil.addItem(player.getInventory(), item);
        for (ItemStack leftover : leftovers.values()) {
            player.getWorld().dropItemNaturally(player.getLocation(), leftover);
        }
    }

    private void play(Player player, String key) {
        if (!config.soundsEnabled()) {
            return;
        }
        String soundName = config.soundName(key);
        if (soundName == null || soundName.isBlank()) {
            return;
        }
        try {
            String soundKey = soundName.toLowerCase(Locale.ROOT).replace('_', '.');
            Sound sound = Registry.SOUNDS.get(NamespacedKey.minecraft(soundKey));
            if (sound == null) {
                return;
            }
            player.playSound(player.getLocation(), sound, 0.7F, 1.0F);
        } catch (IllegalArgumentException ignored) {
        }
    }
}
