local L = Gargul_L;
local LCG = LibStub("LibCustomGlowGargul-1.0");

---@type GL
local _, GL = ...;

---@type GDKP
local GDKP = GL.GDKP;

---@type GDKPPot
local Pot = GL.GDKP.Pot;

---@type Settings
local Settings = GL.Settings;

---@type Constants
local Constants = GL.Data.Constants;

---@type Interface
local Interface = GL.Interface;

---@type GDKPAuction
local Auction = GDKP.Auction;

---@type GDKPAuctioneer
local Auctioneer = GDKP.Auctioneer;

---@type GDKPSession
local Session = GDKP.Session;

GL.ScrollingTable = GL.ScrollingTable or LibStub("ScrollingTable");
Interface.GDKP = Interface.GDKP or {};

---@class GDKPAuctioneerInterface
Interface.GDKP.Auctioneer = {
    isVisible = false,

    --[[ INPUT VALUES ]]
    antiSnipe = nil,
    increment = nil,
    itemLink = nil,
    minimumBid = nil,
    time = nil,

    reorderItems = nil, -- Overwritten later
    queueWindowName = "Gargul.Interface.GDKP.Auctioneer.Queue",
    multiAuctionWindowName = "Gargul.Interface.GDKP.Auctioneer.MultiAuction",
    windowName = "Gargul.Interface.GDKP.Auctioneer.Window",
    auctioneerShortcutName = "Gargul.Interface.GDKP.Auctioneer.Shortcut",

    BidsTable = nil,
    ItemRows = {};
    CountdownTimer = nil,
};

---@type GDKPAuctioneerInterface
local AuctioneerUI = Interface.GDKP.Auctioneer;

--[[ CONSTANTS ]]
local DEFAULT_WINDOW_HEIGHT = 281;
local DEFAULT_WINDOW_WIDTH = 350;
local DEFAULT_PLAYER_COLUMN_WIDTH = 150;
local DEFAULT_TABLE_ROWS = 6;
local FONT;
local HEIGHT_PER_TABLE_ROW = 18;
local QUESTION_MARK_TEXTURE = "Interface/Icons/INV_Misc_QuestionMark";

local BIDS_TABLE_COLUMNS = {
    { --[[ Player ]]
        width = DEFAULT_PLAYER_COLUMN_WIDTH,
        color = { r = .5, g = .5, b = 1.0, a = 1.0 },
    },
    { --[[ Bid ]]
        width = 108,
        color = { r = .5, g = .5, b = 1.0, a = 1.0 },
        sort = GL.Data.Constants.ScrollingTable.descending,
    },
    { --[[ Delete ]]
        width = 18,
        align = "RIGHT",
        DoCellUpdate = GL.LibStImageButtonCellUpdate,
    },
    { --[[ BIDDER FQN ]]
        width = 0,
    },
};

---@return table|nil
function AuctioneerUI:open()
    if (GL.User.isInGroup
        and not GL.User.isMasterLooter
        and not GL.User.hasAssist
    ) then
        return GL:warning(L["You need to be the master looter or have an assist / lead role!"]);
    end

    self.isVisible = true;
    FONT = GL.FONT;

    local Window = _G[self.windowName] or self:build();

    Window:Show();
    return Window;
end

---@return nil
function AuctioneerUI:close()
    self.isVisible = false;
    return _G[self.windowName] and _G[self.windowName]:Hide();
end

--- Build the auctioneer UI. We only do this once per runtime
---
---@return Frame
function AuctioneerUI:build()
    if (_G[self.windowName]) then
        return _G[self.windowName];
    end

    --[[ THE MAIN AUCTIONEER WINDOW ]]
    ---@type Frame
    local Window = Interface:createWindow({
        name = self.windowName,
        width = DEFAULT_WINDOW_WIDTH,
        height = DEFAULT_WINDOW_HEIGHT,
        minWidth = 350,
        minHeight = 239,
        maxWidth = 500,
        maxHeight = 700,
    });
    Window:Hide();

    Window:HookScript("OnShow", function ()
        self:closeAuctioneerShortcut();
    end);

    --[[ ADD THE SETTINGS MENU IN THE TOP LEFT OF THE WINDOW ]]
    Interface:addWindowOptions(Window, {
        { text = L["Tutorial"], notCheckable = true, func = function ()
            GL:popupMessage((L["\n|c00A79EFF%s items in bags, loot windows or even on links in your chat to add them to the auction queue.\nWant to directly sell an item without bidding? Use |c00A79EFF%s\n\nYou can open the %s window directly by typing |c00A79EFF/gl auction\n\nGargul tracks |c00FF0000ALL gold traded. Don't want a trade to be a part of this GDKP session? Check the \"Exclude from GDKP\" checkbox when trading!\n\n|c00FFF569Did you know?\nAwarded items will automatically be added to the trade window\nGargul can also handle auto looting for you. Check it out with |c00A79EFF/gl pm\n"]):format(
                GL.Settings:get("ShortcutKeys.rollOffOrAuction"),
                GL.Settings:get("ShortcutKeys.award"),
                L["Auction"]
            ));
            CloseMenus();
        end },
        {
            text = function ()
                return ("%s (|cFF%s%s%s|r)"):format(
                    L["GDKP Session"],
                    Interface.Colors.ROGUE,
                    Pot:total(),
                    L["g"]
                );
            end,
            notCheckable = true,
            func = function ()
                Interface.GDKP.Overview:open();
                CloseMenus();
            end
        },
        "divider",
        {text = L["Auctions"], isTitle = true, notCheckable = true },
        {text = L["Auto award"], setting = "GDKP.autoAwardViaAuctioneer"},
        {text = L["When no one bids do:"], notCheckable = true, SubMenu = {
            {
                text = L["Nothing"],
                hideOnClick = true,
                isRadio = true,
                checked = function ()
                    return Settings:get("GDKP.queuedAuctionNoBidsAction") == "NOTHING";
                end,
                func = function (Entry)
                    Entry.checked = true;
                    Settings:set("GDKP.queuedAuctionNoBidsAction", "NOTHING");
                end,
            },
            {
                text = L["Skip"],
                hideOnClick = true,
                isRadio = true,
                checked = function ()
                    return Settings:get("GDKP.queuedAuctionNoBidsAction") == "SKIP";
                end,
                func = function (Entry)
                    Entry.checked = true;
                    Settings:set("GDKP.queuedAuctionNoBidsAction", "SKIP");
                end,
            },
            {
                text = L["Disenchant"],
                hideOnClick = true,
                isRadio = true,
                checked = function ()
                    return Settings:get("GDKP.queuedAuctionNoBidsAction") == "DISENCHANT";
                end,
                func = function (Entry)
                    Entry.checked = true;
                    Settings:set("GDKP.queuedAuctionNoBidsAction", "DISENCHANT");
                end,
            },
        }},
        {text = L["Auto trade"], notCheckable = true, SubMenu = {
            {
                text = L["Enable"],
                checked = function ()
                    return Settings:get("AwardingLoot.autoTradeAfterAwardingAnItem");
                end,
                func = function (Entry)
                    Settings:set("AwardingLoot.autoTradeAfterAwardingAnItem", Entry.checked);
                end,
            },
            {
                text = L["Disable for disenchanted"],
                checked = function ()
                    return not Settings:get("AwardingLoot.autoTradeDisenchanter");
                end,
                func = function (Entry)
                    Settings:set("AwardingLoot.autoTradeDisenchanter", not Entry.checked);
                end,
            },
            {
                text = L["Disable in combat"],
                checked = function ()
                    return not Settings:get("AwardingLoot.autoTradeInCombat");
                end,
                func = function (Entry)
                    Settings:set("AwardingLoot.autoTradeInCombat", not Entry.checked);
                end,
            },
        }},
        {text = L["Communication"], notCheckable = true, SubMenu = {
            {
                text = L["Announce auction start"],
                checked = function ()
                    return Settings:get("GDKP.announceAuctionStart");
                end,
                func = function (Entry)
                    Settings:set("GDKP.announceAuctionStart", Entry.checked);
                end,
            },
            {
                text = L["Announce pot after awarding item"],
                checked = function ()
                    return Settings:get("GDKP.announcePotAfterAuction");
                end,
                func = function (Entry)
                    Settings:set("GDKP.announcePotAfterAuction", Entry.checked);
                end,
            },
            {
                text = L["Whisper bidder if bid is too low"],
                checked = function ()
                    return Settings:get("GDKP.notifyIfBidTooLow");
                end,
                func = function (Entry)
                    Settings:set("GDKP.notifyIfBidTooLow", Entry.checked);
                end,
            },
            {
                text = L["Announce countdown in raid warning"],
                checked = function ()
                    return Settings:get("GDKP.announceCountdownInRW");
                end,
                func = function (Entry)
                    Settings:set("GDKP.announceCountdownInRW", Entry.checked);
                end,
            },
            {
                text = L["Announce incoming bids"],
                checked = function ()
                    return Settings:get("GDKP.announceNewBid");
                end,
                func = function (Entry)
                    Settings:set("GDKP.announceNewBid", Entry.checked);
                end,
            },
            {
                text = L["Announce incoming bids in raid warning"],
                checked = function ()
                    return Settings:get("GDKP.announceNewBidInRW");
                end,
                func = function (Entry)
                    Settings:set("GDKP.announceNewBidInRW", Entry.checked);
                end,
            },
        }},
        "divider",
        {text = L["Queue"], isTitle = true, notCheckable = true },
        {text = L["Add dropped loot to queue"], setting = "GDKP.addDropsToQueue", func = function (Entry, _, _, checked)
            Settings:set("GDKP.addDropsToQueue", checked);
            Entry.checked = checked;
        end},
        {text = L["Include BOEs"], setting = "GDKP.addBOEDropsToQueue", func = function (Entry, _, _, checked)
            Settings:set("GDKP.addBOEDropsToQueue", checked);
            Entry.checked = checked;
        end},
        "divider",
        {text = L["Window"], isTitle = true, notCheckable = true },
        {text = L["Adjust Scale"], notCheckable = true, func = function ()
            Interface:openScaler(Window);
            CloseMenus();
        end},
        {text = L["Minimize on start"], setting = "GDKP.minimizeAuctioneerOnStart"},
        {text = L["Minimize on award"], setting = "GDKP.minimizeAuctioneerOnAward"},
        "divider",
        {text = L["All Settings"], notCheckable = true, func = function ()
            Settings:draw("GDKP");
            CloseMenus();
        end },
    });

    --[[ PREPARE THE MINIMIZED VERSION OF THE WINDOW ]]
    Window.Minimized:SetHeight(70);
    Window.Minimized:SetWidth(150);

    ---@type Button
    local StopButton;

    ---@type FontString
    local CurrentPotLabel = Interface:createFontString(Window.Minimized, {
        text = function (self)
            self:SetText(("%s: |cFF%s%s%s|r"):format(
                L["Pot"],
                Interface.Colors.ROGUE,
                Pot:total(),
                L["g"]
            ));
        end,
        updateOn = { "GL.GDKP_AUCTION_CHANGED", "GL.GDKP_SESSION_CHANGED" },
    });
    CurrentPotLabel:SetFont(1, "OUTLINE");
    CurrentPotLabel:SetPoint("TOPLEFT", Window.Minimized, "TOPLEFT", 24, -18);

    ---@type Button
    local MinimizedStopButton = Interface:dynamicPanelButton(Window.Minimized, L["Stop"]);
    MinimizedStopButton:SetPoint("TOPLEFT", Window.Minimized, "TOPLEFT", 20, -34);
    MinimizedStopButton:SetPoint("TOPRIGHT", Window.Minimized, "TOPRIGHT", -20, 0);
    MinimizedStopButton:SetScript("OnClick", function ()
        StopButton:Click();
    end);
    MinimizedStopButton:Hide();
    Window.Minimized.StopButton = MinimizedStopButton;

    ---@type Button
    local MinimizedOpenButton = Interface:dynamicPanelButton(Window.Minimized, L["Open Auctioneer"]);
    MinimizedOpenButton:SetPoint("TOPLEFT", Window.Minimized, "TOPLEFT", 20, -34);
    MinimizedOpenButton:SetPoint("TOPRIGHT", Window.Minimized, "TOPRIGHT", -20, 0);
    MinimizedOpenButton:SetScript("OnClick", function ()
        self:open();
    end);
    Window.Minimized.OpenButton = MinimizedOpenButton;

    ---@type EditBox
    local ItemInput;

    ---@type Texture
    local ItemImage;

    do --[[ ITEM ICON / INPUT ]]
        ---@type Frame
        local Icon = CreateFrame("Frame",nil, Window);
        Icon:SetSize(40, 40);
        Icon:SetPoint("TOPLEFT", Window, "TOPLEFT", 28, -28);

        Interface:addTooltip(Icon, self.itemLink, "RIGHT");

        Icon:SetScript("OnMouseUp", function (_, mouseButtonPressed)
            HandleModifiedItemClick(self.itemLink, mouseButtonPressed);
        end);

        ItemImage = Icon:CreateTexture(nil, "BACKGROUND")
        ItemImage:SetWidth(40)
        ItemImage:SetHeight(40)
        ItemImage:SetPoint("TOP", 0, 0);
        ItemImage:SetTexture(QUESTION_MARK_TEXTURE);

        ItemInput = Interface:inputBox(Window);
        ItemInput:SetPoint("TOPLEFT", Icon, "TOPRIGHT", 18, -18);
        ItemInput:SetPoint("RIGHT", Window, "RIGHT", -24, 0);
    end

    do --[[ CURRENT POT ]]
        --[[ ICON ]]
        ---@type Frame
        local Icon = CreateFrame("Frame",nil, Window);
        Icon:SetSize(12, 12);
        Icon:SetPoint("BOTTOMLEFT", ItemInput, "TOPLEFT", -4, 4);

        local PotImage = Icon:CreateTexture(nil, "BACKGROUND")
        PotImage:SetWidth(12)
        PotImage:SetHeight(12)
        PotImage:SetPoint("TOP", 0, 0);
        PotImage:SetTexture("Interface/AddOns/Gargul/Assets/Icons/achievement_guildperk_cashflow");

        --[[ TEXT ]]
        ---@type FontString
        local CurrentPotLabel = Interface:createFontString(Window, {
            text = function (self)
                self:SetText(("%s: |cFF%s%s%s|r"):format(
                    L["Pot"],
                    Interface.Colors.ROGUE,
                    Pot:total(),
                    L["g"]
                ));
            end,
            updateOn = { "GL.GDKP_AUCTION_CHANGED", "GL.GDKP_SESSION_CHANGED" },
        });
        CurrentPotLabel:SetPoint("TOPLEFT", Icon, "TOPRIGHT", 4, 0);
    end

    --[[ MINIMUM BID ]]
    ---@type FontString
    local MinLabel = Interface:createFontString(Window, L["Min"]);
    MinLabel:SetPoint("TOPLEFT", Window, "TOPLEFT", 26, -82);

    ---@type EditBox
    local MinInput = Interface:numericInputBox(Window, nil, nil, Settings:get("GDKP.precision"));
    MinInput:SetWidth(42);
    MinInput:SetPoint("TOPLEFT", MinLabel, "TOPRIGHT", 8, 6);
    MinInput:HookScript("OnTextChanged", function ()
        self.minimumBid = tonumber(MinInput:GetText());
    end);
    Window.MinInput = MinInput;

    --[[ INCREMENT ]]
    ---@type FontString
    local IncLabel = Interface:createFontString(Window, L["Inc"]);
    IncLabel:SetPoint("TOPLEFT", MinLabel, "TOPRIGHT", MinInput:GetWidth() + 12, 0);

    ---@type EditBox
    local IncInput = Interface:numericInputBox(Window, nil, nil, Settings:get("GDKP.precision"));
    IncInput:SetWidth(36);
    IncInput:SetPoint("TOPLEFT", IncLabel, "TOPRIGHT", 8, 6);
    IncInput:HookScript("OnTextChanged", function ()
        self.increment = tonumber(IncInput:GetText());
    end);
    Window.IncInput = IncInput;

    --[[ TIME ]]
    ---@type FontString
    local TimeLabel = Interface:createFontString(Window, L["Time"]);
    TimeLabel:SetPoint("TOPLEFT", IncLabel, "TOPRIGHT", IncInput:GetWidth() + 12, 0);

    ---@type EditBox
    local TimeInput = Interface:numericInputBox(Window);
    TimeInput:SetText(Settings:get("GDKP.time"));
    TimeInput:SetWidth(20);
    TimeInput:SetPoint("TOPLEFT", TimeLabel, "TOPRIGHT", 8, 6);
    TimeInput:HookScript("OnTextChanged", function ()
        self.time = tonumber(TimeInput:GetText());
    end);
    Window.TimeInput = TimeInput;

    --[[ ANTI SNIPE ]]
    ---@type FontString
    local SnipeLabel = Interface:createFontString(Window, L["Anti Snipe"]);
    SnipeLabel:SetPoint("TOPLEFT", TimeLabel, "TOPRIGHT", TimeInput:GetWidth() + 12, 0);

    ---@type EditBox
    local SnipeInput = Interface:numericInputBox(Window);
    SnipeInput:SetText(Settings:get("GDKP.antiSnipe"));
    SnipeInput:SetWidth(20);
    SnipeInput:SetPoint("TOPLEFT", SnipeLabel, "TOPRIGHT", 8, 6);
    SnipeInput:HookScript("OnTextChanged", function ()
        self.antiSnipe = tonumber(SnipeInput:GetText());
    end);
    Window.SnipeInput = SnipeInput;

    do --[[ HELP ICON ]]
        local texturePath = "interface/friendsframe/informationicon";

        ---@type Frame
        local Icon = CreateFrame("Button","Gargul.Interface.GDKP.Auctioneer.HelpIcon", Window);
        Icon:SetSize(12, 12);
        Icon:SetPoint("TOPLEFT", SnipeInput, "TOPRIGHT", 4, -4);

        ---@type Texture
        local image = Icon:CreateTexture(nil, "BACKGROUND")
        image:SetWidth(12)
        image:SetHeight(12)
        image:SetAllPoints(Icon)
        image:SetTexture(texturePath);

        ---@type Texture
        local highlight = Icon:CreateTexture(nil, "HIGHLIGHT")
        highlight:SetAllPoints(Icon);
        highlight:SetTexture("Interface/PaperDollInfoFrame/UI-Character-Tab-Highlight")
        highlight:SetTexCoord(0, 1, 0.23, 0.77);
        highlight:SetBlendMode("ADD");

        Interface:addTooltip(Icon, L["\n\nAn Anti Snipe value of 10 means that any bid that comes in with\nless than 10 seconds left will reset the timer back to 10 seconds\n\nYou can leave this empty or set to 0 to disable Anti Snipe completely.\nAnti Snipe values less than 5 are not supported\n\n"]);
    end

    ---@type Frame
    local StoppedButtonFrame;

    do --[[ BUTTONS WHEN AUCTION STOPPED ]]
        StoppedButtonFrame = CreateFrame("Frame", nil, Window);
        StoppedButtonFrame:SetHeight(20);
        StoppedButtonFrame:SetPoint("TOPLEFT", Window, "TOPLEFT", 0, -108);
        StoppedButtonFrame:SetPoint("TOPRIGHT", Window, "TOPRIGHT", 0, -108);

        --[[ START ]]
        ---@type Button
        local StartButton = Interface:dynamicPanelButton(StoppedButtonFrame, L["Start"]);
        StartButton:SetPoint("TOPLEFT", StoppedButtonFrame, "TOPLEFT", 26, 0);
        StartButton:SetScript("OnClick", function ()
            Auctioneer:start(true);
        end);
        Window.StartButton = StartButton;

        --[[ DISENCHANT ]]
        ---@type Button
        local DisenchantButton = Interface:dynamicPanelButton(StoppedButtonFrame, L["Disenchant"]);
        DisenchantButton:SetPoint("TOPLEFT", StartButton, "TOPRIGHT", 1, 0);
        DisenchantButton:SetScript("OnClick", function ()
            Auctioneer:disenchant(self.itemLink);
        end);

        --[[ CLEAR ]]
        ---@type Button
        local ClearButton = Interface:dynamicPanelButton(StoppedButtonFrame, L["Clear"]);
        ClearButton:SetPoint("TOPLEFT", DisenchantButton, "TOPRIGHT", 1, 0);
        ClearButton:SetScript("OnClick", function ()
            Auctioneer:clear();
        end);

        --[[ NEXT ]]
        ---@type Button
        local NextButton = Interface:dynamicPanelButton(StoppedButtonFrame, L["Next"]);
        NextButton:SetPoint("TOPLEFT", ClearButton, "TOPRIGHT", 1, 0);
        NextButton:SetScript("OnClick", function ()
            Auctioneer:popFromQueue(true);
        end);

        --[[ AWARD ]]
        ---@type Button
        local AwardButton = Interface:dynamicPanelButton(StoppedButtonFrame, L["Award"]);
        AwardButton:SetPoint("TOPLEFT", NextButton, "TOPRIGHT", 1, 0);
        AwardButton:SetScript("OnClick", function ()
            Auctioneer:award();
        end);
    end

    ---@type Frame
    local StartedButtonFrame;

    do --[[ BUTTONS WHEN AUCTION STARTED ]]
        StartedButtonFrame = CreateFrame("Frame", nil, Window);
        StartedButtonFrame:SetHeight(StoppedButtonFrame:GetHeight());
        StartedButtonFrame:SetPoint("TOPLEFT", StoppedButtonFrame);
        StartedButtonFrame:SetPoint("TOPRIGHT", StoppedButtonFrame);
        StartedButtonFrame:Hide();

        --[[ START ]]
        ---@type Button
        StopButton = Interface:dynamicPanelButton(StartedButtonFrame, L["Stop"]);
        StopButton:SetPoint("TOPLEFT", StartedButtonFrame, "TOPLEFT", 26, 0);
        StopButton:SetScript("OnClick", function ()
            Auctioneer:stop();
        end);

        --[[ +10 ]]
        ---@type Button
        local ExtendButton = Interface:dynamicPanelButton(StartedButtonFrame, L["+1"]);
        ExtendButton:SetPoint("TOPLEFT", StopButton, "TOPRIGHT", 1, 0);
        ExtendButton:SetScript("OnClick", function ()
            if (Auctioneer:extend()) then
                ExtendButton:SetEnabled(false);

                GL.Ace:ScheduleTimer(function ()
                    ExtendButton:SetEnabled(true);
                end, 2);
            end
        end);

        --[[ -10 ]]
        ---@type Button
        local ShortenButton = Interface:dynamicPanelButton(StartedButtonFrame, L["-10"]);
        ShortenButton:SetPoint("TOPLEFT", ExtendButton, "TOPRIGHT", 1, 0);
        ShortenButton:SetScript("OnClick", function ()
            if (Auctioneer:shorten()) then
                ShortenButton:SetEnabled(false);

                GL.Ace:ScheduleTimer(function ()
                    ShortenButton:SetEnabled(true);
                end, 2);
            end
        end);

        --[[ FINAL CALL ]]
        ---@type Button
        local FinalCallButton = Interface:dynamicPanelButton(StartedButtonFrame, L["Final Call"]);
        FinalCallButton:SetPoint("TOPLEFT", ShortenButton, "TOPRIGHT", 1, 0);
        FinalCallButton:SetScript("OnClick", function ()
            if (Auctioneer:finalCall()) then
                FinalCallButton:SetEnabled(false);

                GL.Ace:ScheduleTimer(function ()
                    FinalCallButton:SetEnabled(true);
                end, 2);
            end
        end);
    end

    --[[ BIDS TABLE ]]
    local Table = self:buildBidsTable(Window);
    Table.frame:SetPoint("TOPLEFT", StoppedButtonFrame, "BOTTOMLEFT", 20, -10);
    self.BidsTable = Table;

    --[[ QUEUE (RIGHT SIDE) WINDOW ]]
    ---@type Frame
    local Queue = self:buildQueue(Window);

    do --[[ QUEUE TOGGLER BUTTON ]]
        local closedCoords = { 0.03125, 0.46875, 0.0078125, 0.9921875 };
        local openedCoords = { 0.53125, 0.96875, 0.0078125, 0.9921875 };

        ---@type Button
        local ToggleQueue = CreateFrame("Button", "Gargul.Interface.GDKP.Auctioneer.ToggleQueue", Window);
        ToggleQueue:SetSize(14,66);
        ToggleQueue:SetPoint("CENTER", Queue);
        ToggleQueue:SetScript("OnClick", function ()
            if (Settings:get("GDKP.disableQueues")) then
                GL:warning(L["You disabled GDKP queues"]);
                return;
            end

            if (Queue:IsVisible()) then
                Queue:Hide();
                ToggleQueue.texture:SetTexCoord(unpack(closedCoords));
                Settings:set("GDKP.showQueueWindow", false);
            else
                Queue:Show();
                ToggleQueue.texture:SetTexCoord(unpack(openedCoords));
                Settings:set("GDKP.showQueueWindow", true);
            end

            ToggleQueue:SetPoint("RIGHT", Queue:IsShown() and Queue or Window, "RIGHT", -10, 0);
        end);

        ToggleQueue.texture = ToggleQueue:CreateTexture();
        ToggleQueue.texture:SetAllPoints(ToggleQueue)
        ToggleQueue.texture:SetTexture("interface/raidframe/raidpanel-toggle");
        ToggleQueue.texture:SetTexCoord(unpack(closedCoords));
        ToggleQueue:SetPoint("RIGHT", Queue:IsShown() and Queue or Window, "RIGHT", -10, 0);

        if (Queue:IsShown()) then
            ToggleQueue.texture:SetTexCoord(unpack(openedCoords));
        end

        ToggleQueue.highlightTexture = ToggleQueue:CreateTexture(nil, "HIGHLIGHT");
        ToggleQueue.highlightTexture:SetPoint("TOPLEFT", ToggleQueue, "TOPLEFT", 3, -10);
        ToggleQueue.highlightTexture:SetPoint("BOTTOMRIGHT", ToggleQueue, "BOTTOMRIGHT", 2, 10);
        ToggleQueue.highlightTexture:SetTexture("Interface/PaperDollInfoFrame/UI-Character-Tab-Highlight");
        ToggleQueue.highlightTexture:SetTexCoord(0, 1, 0.23, 0.77);
        ToggleQueue.highlightTexture:SetBlendMode("ADD");
        ToggleQueue:SetHighlightTexture(ToggleQueue.highlightTexture);

        Interface:addTooltip(ToggleQueue, L["Show/Hide Queue"], "BOTTOMRIGHT");
    end

    --[[ MULTI AUCTION INTRODUCTION WINDOW ]]
    do
        ---@type Frame
        local MultiAuctionWindow = Interface:createWindow({
            name = self.multiAuctionWindowName,
            height = 50,
            hideAllButtons = true,
            hideWatermark = true,
            template = false,
            Parent = Window,
        });
        Window.MultiAuctionWindow = MultiAuctionWindow;
        MultiAuctionWindow:SetPoint("TOPLEFT", Window, "BOTTOMLEFT", 2, -4);
        MultiAuctionWindow:SetPoint("RIGHT", Window, "RIGHT", -2, 0);

        GL:stopHighlight(MultiAuctionWindow);
        LCG.PixelGlow_Start(MultiAuctionWindow, { .59, .5, .82, 1, }, Window:GetWidth() / 4, .02, 5, 2);

        local Texture = MultiAuctionWindow:CreateTexture(nil,"BACKGROUND");
        Texture:SetColorTexture(0, 0, 0, .6);
        Texture:SetAllPoints(MultiAuctionWindow)

        ---@type Button
        local MultiAuctionButton = Interface:dynamicPanelButton(MultiAuctionWindow);
        MultiAuctionButton:SetPoint("CENTER", MultiAuctionWindow, "CENTER");
        MultiAuctionButton:SetText(L["Check out Multi Auctions!"]);
        MultiAuctionButton:SetScript("OnClick", function ()
            GL.Commands:call("multiauction");
            self:close();
        end);

        Interface:addTooltip(MultiAuctionWindow, L["\n\nWith multi auctions (or batch auctions) you can auction off as many items as you want at once!\nThis speeds up your raid nights immensely and makes for a seamless experience for your raiders\n\nAll tradable items still in your inventory can automatically be auctioned with 'Fill from inventory'\nGive it a shot!\n\n|c00808080There is but one con: in order for people to partake in a batch auction raiders will need Gargul!\n\n"]);
        Interface:addTooltip(MultiAuctionButton, L["\n\nWith multi auctions (or batch auctions) you can auction off as many items as you want at once!\nThis speeds up your raid nights immensely and makes for a seamless experience for your raiders\n\nAll tradable items still in your inventory can automatically be auctioned with 'Fill from inventory'\nGive it a shot!\n\n|c00808080There is but one con: in order for people to partake in a batch auction raiders will need Gargul!\n\n"]);
    end

    --[[ ADJUST TABLE ROWS AFTER WINDOW RESIZE ]]
    Window:HookScript("OnSizeChanged", function (...)
        local extraVerticalRoomForTable = Window:GetHeight() - DEFAULT_WINDOW_HEIGHT;
        local extraHorizontalRoomForTable = Window:GetWidth() - DEFAULT_WINDOW_WIDTH;
        local additionalRows = math.floor(extraVerticalRoomForTable / HEIGHT_PER_TABLE_ROW);

        --[[ ADJUST ROWS ]]
        Table:SetDisplayRows(additionalRows + DEFAULT_TABLE_ROWS, HEIGHT_PER_TABLE_ROW);

        --[[ ADJUST COLUMN WIDTH ]]
        local newPlayerColumnWidth = math.floor(extraHorizontalRoomForTable + DEFAULT_PLAYER_COLUMN_WIDTH);
        BIDS_TABLE_COLUMNS[1].width = newPlayerColumnWidth
        Table:SetDisplayCols(BIDS_TABLE_COLUMNS);

        --[[ ADJUST MULTI AUCTION WINDOW GLOW ]]
        if (Window.MultiAuctionWindow) then
            -- We use a timer to not call this too often
            GL:after(1, "GDKP.Auctioneer.MultiAuctionWindow.Glow", function ()
                GL:stopHighlight(Window.MultiAuctionWindow);
                LCG.PixelGlow_Start(Window.MultiAuctionWindow, { .59, .5, .82, 1, }, Window:GetWidth() / 4, .02, 5, 2);
            end);
        end
    end);
    Window:GetScript("OnSizeChanged")();

    --[[ CHANGE THE CURRENTLY ACTIVE ITEM ]]

    ---@param _
    ---@param itemLink string
    ---@param minimum number
    ---@param increment number
    ---@return nil
    Window.setItemByLink = function (_, itemLink, minimum, increment)
        GL:onItemLoadDo(itemLink, function (Details)
            if (not Details) then
                return;
            end

            minimum = tonumber(minimum);
            increment = tonumber(increment);

            local PerItemSettings;
            if (not minimum or not increment) then
                PerItemSettings = GDKP:settingsForItemID(Details.id);
            end

            minimum = minimum or PerItemSettings.minimum;
            increment = increment or PerItemSettings.increment;

            ---@type table
            MinInput:SetText(minimum);
            IncInput:SetText(increment);

            self.itemLink = itemLink;
            self.minimumBid = minimum;
            self.increment = increment;
            ItemImage:SetTexture(Details.icon);
            ItemInput:SetText(Details.link);
        end);
    end;

    --[[ SHOW THE AUCTIONEER SHORTCUT WHEN CLOSING THE WINDOW DURING AN AUCTION ]]
    Window:SetScript("OnHide", function ()
        self.isVisible = false;

        if (Auction.inProgress and Auction:startedByMe()) then
            self:openAuctioneerShortcut();
        end
    end);

    --[[ CLEAR THE CURRENTLY ACTIVE ITEM ]]
    Window.clearItem = function ()
        self.itemLink = nil;
        MinInput:SetText("");
        IncInput:SetText("");
        ItemImage:SetTexture(QUESTION_MARK_TEXTURE);
        ItemInput:SetText("");
    end

    --[[ SWITCH BETWEEN STOP / START BUTTONS ]]
    Window.stop = function ()
        StoppedButtonFrame:Show();
        StartedButtonFrame:Hide();
    end;

    Window.start = function ()
        StoppedButtonFrame:Hide();
        StartedButtonFrame:Show();

        --[[ SHOW TIME REMAINING ON STOP BUTTON ]]
        GL.Ace:CancelTimer(self.CountdownTimer);
        self.CountdownTimer = GL.Ace:ScheduleRepeatingTimer(function ()
            local secondsRemaining = Auction:timeRemaining();

            if (not secondsRemaining
                or not Auction.inProgress
            ) then
                GL.Ace:CancelTimer(self.CountdownTimer);
                StopButton:SetText(L["Stop"]);
                return;
            end

            secondsRemaining = math.max(secondsRemaining, 0);
            StopButton:SetText(GL:strPadRight(("%s %s%s"):format(L["Stop"], secondsRemaining, L["s"]), " ", strlen(L["Stop"]) + 5));
            MinimizedStopButton:SetText(("%s %s%s"):format(L["Stop"], secondsRemaining, L["s"]));
            MinimizedOpenButton:Hide();
            MinimizedStopButton:Show();
        end, .2);
    end;

    _G[self.windowName] = Window;
    return Window;
end

---@param Window Frame
---@return Frame
function AuctioneerUI:buildQueue(Window)
    if (_G[self.queueWindowName]) then
        return _G[self.queueWindowName];
    end

    --[[ QUEUE WINDOW ]]
    ---@type Frame
    local Queue;

    do
        ---@type Frame
        local ItemHolder

        ---@type ScrollFrame
        local ScrollFrame;

        Queue = CreateFrame("Frame", self.queueWindowName, Window, "BackdropTemplate");
        Queue:SetPoint("TOPLEFT", Window, "TOPRIGHT", -2, 0);
        Queue:SetPoint("BOTTOMLEFT", Window, "BOTTOMRIGHT", -2, 0);
        Queue:SetResizable(true);
        Queue:SetBackdrop(_G.BACKDROP_DARK_DIALOG_32_32);
        Interface:restoreDimensions(Queue, nil, 300);
        Interface:resizeBounds(Queue, 240, 1);
        Interface:addResizer(Queue);
        Queue:Show();

        Queue:SetScript("OnSizeChanged", function ()
            Queue:SetWidth(math.min(600, Queue:GetWidth()));
            Queue:ClearAllPoints();
            Queue:SetPoint("TOPLEFT", Window, "TOPRIGHT", -2, 0);
            Queue:SetPoint("BOTTOMLEFT", Window, "BOTTOMRIGHT", -2, 0);
            Interface:storeDimensions(Queue);
            ItemHolder:SetSize(ScrollFrame:GetWidth(), ( ScrollFrame:GetHeight() * 2 ));
        end);

        if (Settings:get("GDKP.disableQueues")
            or not Settings:get("GDKP.showQueueWindow")
        ) then
            Queue:Hide();
        end

        do --[[ HELP FRAME ]]
            ---@type Frame
            local HelpFrame = CreateFrame("Frame", nil, Queue);
            HelpFrame:SetSize(50, 20);
            HelpFrame:SetPoint("TOPLEFT", Queue, "TOPLEFT", 16, -16);

            ---@type Texture
            local highlight = HelpFrame:CreateTexture(nil, "HIGHLIGHT")
            highlight:SetAllPoints(HelpFrame);
            highlight:SetTexture("Interface/PaperDollInfoFrame/UI-Character-Tab-Highlight")
            highlight:SetBlendMode("ADD");

            Interface:addTooltip(HelpFrame, { L["Queue"] .. " " .. L["Info"], (L["\n|c00A79EFF%s items in bags, loot windows or even on links in your chat to add them to this auction queue.\nItems in the queue will automatically be auctioned off once your current auction is done. Click the '%s' button to prevent this.\n\nWant Gargul to automatically award or disenchant auctions for you? Open the settings wheel on the left side!\n\n|c00FFF569Did you know?\nYou can move items around with drag and drop\nItems will be remembered, even when you |c00A79EFF/reload\nQueued items are automatically shown to raiders who have Gargul so they can prebid\nGargul can also handle auto looting for you. Check it out with |c00A79EFF/gl pm\n"]):format(
                GL.Settings:get("ShortcutKeys.rollOffOrAuction"),
                L["Halt"]
            ), }, "CURSOR");

            --[[ ICON ]]
            local texturePath = "interface/friendsframe/informationicon";

            ---@type Button
            local Icon = CreateFrame("Button",nil, Queue);
            Icon:SetSize(12, 12);
            Icon:SetPoint("TOPLEFT", HelpFrame, "TOPLEFT");

            ---@type Texture
            local image = Icon:CreateTexture(nil, "BACKGROUND")
            image:SetWidth(12)
            image:SetHeight(12)
            image:SetAllPoints(Icon)
            image:SetTexture(texturePath);

            --[[ INFO ]]
            ---@type FontString
            local InfoLabel = Interface:createFontString(HelpFrame, L["Info"]);
            InfoLabel:SetPoint("TOPLEFT", HelpFrame, "TOPLEFT", 13, -1);
        end

        --[[ MINIMUM BID ]]
        ---@type FontString
        local MinLabel = Interface:createFontString(Queue, L["Minimum"]);
        MinLabel:SetPoint("TOPRIGHT", Queue, "TOPRIGHT", -118, -18);

        --[[ INCREMENT ]]
        ---@type FontString
        local IncLabel = Interface:createFontString(Queue, L["Increment"]);
        IncLabel:SetPoint("TOPLEFT", MinLabel, "TOPRIGHT", 6, 0);

        --[[ SCROLLFRAME BOILERPLATE ]]
        ScrollFrame = CreateFrame("ScrollFrame", nil, Queue, "UIPanelScrollFrameTemplate")
        ScrollFrame:SetPoint("TOPLEFT", Queue, "TOPLEFT", 16, -32);
        ScrollFrame:SetPoint("BOTTOMRIGHT", Queue, "BOTTOMRIGHT", -44, 44);

        ItemHolder = CreateFrame("Frame");
        ScrollFrame:SetScrollChild(ItemHolder);
        ItemHolder:SetSize(ScrollFrame:GetWidth(), ( ScrollFrame:GetHeight() * 2 ));
        ItemHolder:SetPoint("TOPLEFT", ScrollFrame, "TOPLEFT");
        ItemHolder:SetPoint("BOTTOMRIGHT", ScrollFrame, "BOTTOMRIGHT");

        ---@type Frame
        local Items = CreateFrame("Frame", nil, ItemHolder);
        Items:SetAllPoints(ItemHolder);

        ---@type Frame
        local DeleteButton;

        do --[[ DELETE BUTTON ]]
            DeleteButton = CreateFrame("Button", nil, ItemHolder, "UIPanelButtonTemplate");
            DeleteButton:SetSize(18, 18);

            ---@type Texture
            local NormalTexture = DeleteButton.normalTexture or DeleteButton:CreateTexture();
            NormalTexture:SetTexture("Interface/AddOns/Gargul/Assets/Buttons/delete");
            NormalTexture:SetAllPoints(DeleteButton);
            DeleteButton:SetNormalTexture(NormalTexture);

            ---@type Texture
            local HighlightTexture = DeleteButton:CreateTexture();
            HighlightTexture:SetTexture("Interface/AddOns/Gargul/Assets/Buttons/delete");
            NormalTexture:SetAllPoints(DeleteButton);
            DeleteButton:SetHighlightTexture(HighlightTexture);

            Interface:addTooltip(DeleteButton, L["Delete"]);
        end

        --[[ REORDER QUEUE ENTRIES ]]
        self.reorderItems = function ()
            for key, ItemRow in pairs(self.ItemRows or {}) do
                if (not ItemRow or not ItemRow._itemLink or not Auction.Queue[ItemRow._identifier]) then
                    if (ItemRow and ItemRow.GetChildren) then
                        Interface:release(ItemRow);
                    end

                    self.ItemRows[key] = nil;
                end
            end

            local SortedQueue = GL:tableValues(Auction.Queue);
            table.sort(SortedQueue, function (a, b)
                if (a.order and b.order) then
                    return a.order < b.order;
                end

                return false;
            end);

            for _, QueuedItem in pairs(SortedQueue or {}) do
                local key = QueuedItem.identifier;
                local ItemRow = self.ItemRows[key];

                if (ItemRow and ItemRow._itemLink) then
                    ItemRow:ClearAllPoints();
                    ItemRow:Show();
                    ItemRow:SetPoint("TOPLEFT", ItemHolder, "TOPLEFT", 0, ((QueuedItem.order - 1) * 20) * -1);
                    ItemRow:SetPoint("TOPRIGHT", ItemHolder, "TOPRIGHT", not GL.elvUILoaded and 0 or -4, 0);
                    self.ItemRows[key] = ItemRow;
                else
                    self.ItemRows[key] = nil;
                end
            end
        end

        --[[ ADD AN ITEM TO THE QUEUE WINDOW ]]
        local rowHeight = 20;
        Queue.addItemByLink = function (_, link, identifier)
            GL:onItemLoadDo(link, function (Details)
                if (not Details or self.ItemRows[identifier]) then
                    return;
                end

                local bindOnPickup = GL:inTable({ Enum.ItemBind.OnAcquire, Enum.ItemBind.Quest }, Details.bindType);

                ---@type Frame
                local ItemRow = CreateFrame("Frame", nil, ItemHolder);

                ---@type table
                local PerItemSettings = GDKP:settingsForItemID(Details.id);
                ItemRow:SetHeight(rowHeight);
                ItemRow._itemLink = link;
                ItemRow._identifier = identifier;

                ItemRow:SetPoint("TOPLEFT", ItemHolder, "TOPLEFT", 0, (GL:count(self.ItemRows) * 20) * -1);
                ItemRow:SetPoint("TOPRIGHT", ItemHolder, "TOPRIGHT", not GL.elvUILoaded and 0 or -4, 0);

                --[[ TOGGLE DELETE ON HOVER ]]
                ItemRow:SetScript("OnEnter", function ()
                    DeleteButton:SetFrameLevel(ItemRow:GetFrameLevel() + 1);
                    DeleteButton:ClearAllPoints();
                    DeleteButton:SetPoint("TOPRIGHT", ItemRow, "TOPRIGHT", -2, 0);
                    DeleteButton:Show();

                    DeleteButton:SetScript("OnClick", function ()
                        DeleteButton:SetParent(ItemHolder);
                        DeleteButton:Hide();
                        Auction:removeFromQueue(ItemRow._identifier);
                        self:reorderItems();
                    end);
                end);

                ItemRow:SetScript("OnLeave", function ()
                    if (not Interface:mouseIsOnFrame(DeleteButton)) then
                        DeleteButton:Hide();
                    end
                end);

                local uiScale, mouseX, mouseY;
                ItemRow:SetScript("OnMouseDown", function ()
                    uiScale, mouseX, mouseY = UIParent:GetEffectiveScale(), GetCursorPosition();
                end);

                ItemRow:SetMovable(true);
                ItemRow:EnableMouse(true);
                ItemRow:RegisterForDrag("LeftButton");
                local indexWithGlow, RowWithGlow, MoveTimer, newPosition, originalPosition;
                ItemRow:SetScript("OnDragStart", function ()
                    local numberOfRows = GL:count(self.ItemRows);
                    originalPosition = Auction.Queue[ItemRow._identifier].order;
                    local itemRowHeight = ItemRow:GetHeight();
                    ItemRow:StartMoving();

                    MoveTimer = GL.Ace:ScheduleRepeatingTimer(function ()
                        local _, newMouseY = GetCursorPosition();

                        newPosition = math.ceil(originalPosition - ((newMouseY - mouseY) / (itemRowHeight * uiScale)));
                        newPosition = math.max(1, newPosition);
                        newPosition = math.min(numberOfRows, newPosition);
                        if (newPosition == indexWithGlow
                            or newPosition == originalPosition
                        ) then
                            return;
                        end

                        if (RowWithGlow) then
                            GL:stopHighlight(RowWithGlow);
                        end

                        RowWithGlow = Auction:QueuedItemByPosition(newPosition);
                        if (RowWithGlow) then
                            indexWithGlow = newPosition;
                            RowWithGlow = self.ItemRows[RowWithGlow.identifier];

                            LCG.PixelGlow_Start(RowWithGlow, { 1, 1, 1, 1, }, 20, .05, 5, 3);
                        end
                    end, .1);
                end);
                ItemRow:SetScript("OnDragStop", function ()
                    GL.Ace:CancelTimer(MoveTimer);
                    ItemRow:Hide();

                    if (RowWithGlow) then
                        GL:stopHighlight(RowWithGlow);
                    end

                    if (tonumber(newPosition)) then
                        local MoveTo = Auction:QueuedItemByPosition(newPosition);

                        if (MoveTo) then
                            Auction:reorderQueueItem(ItemRow._identifier, MoveTo.identifier);
                        end
                    end

                    self:reorderItems();
                end);

                --[[ ITEM ICON ]]
                ---@type Frame
                local Icon = CreateFrame("Frame",nil, ItemRow);
                Icon:SetPoint("TOPLEFT", ItemRow, 0, -2);
                Icon:SetSize(rowHeight - 4, rowHeight - 4);

                Interface:addTooltip(Icon, Details.link);

                --[[ ICON HIGHLIGHT ]]
                GL:highlightItem(Icon, Details.link, {
                    dots = 7,
                    speed = .2,
                    width = 1,
                    length = 5,
                });

                ItemRow.Icon = Icon;

                ---@type Texture
                local Image = Icon:CreateTexture(nil, "BACKGROUND")
                Image:SetAllPoints(Icon);
                Image:SetTexture(Details.icon);

                --[[ INCREMENT ]]
                ---@type EditBox
                local IncInput = Interface:numericInputBox(ItemRow, nil, nil, Settings:get("GDKP.precision"));
                IncInput:SetText(PerItemSettings.increment);
                IncInput:SetWidth(36);
                IncInput:SetPoint("TOPRIGHT", ItemRow, "TOPRIGHT", -24, 0);
                ItemRow.IncInput = IncInput;

                --[[ MINIMUM ]]
                ---@type EditBox
                local MinInput = Interface:numericInputBox(ItemRow, nil, nil, Settings:get("GDKP.precision"));
                MinInput:SetText(PerItemSettings.minimum);
                MinInput:SetWidth(42);
                MinInput:SetPoint("TOPRIGHT", IncInput, "TOPLEFT", -8, 0);
                ItemRow.MinInput = MinInput;

                --[[ ITEM LINK ]]
                local BOEString = "";
                if (not bindOnPickup) then
                    BOEString = L["BOE"] .. " ";
                end

                ---@type FontString
                local Name = Interface:createFontString(ItemRow, BOEString .. Details.link);
                Name:SetPoint("CENTER", Icon);
                Name:SetPoint("LEFT", Icon, "RIGHT", 4, 0);
                Name:SetPoint("RIGHT", MinInput, "LEFT", -6, 0);
                Name:SetHeight(20);

                ItemRow.order = GL:tableGet(Auction.Queue, identifier .. ".order", GL:count(Auction.Queue) + 1);

                self.ItemRows[identifier] = ItemRow;
                self:reorderItems();
            end);
        end;

        --[[ HALT/RESUME QUEUE BUTTON ]]
        ---@type Button
        local EnableQueue = Interface:dynamicPanelButton(Queue, L["Halt"]);
        EnableQueue:SetPoint("BOTTOMLEFT", Queue, "BOTTOMLEFT", 20, 16);
        EnableQueue:SetPoint("RIGHT", Queue, "CENTER", -2, 0);

        if (Settings:get("GDKP.queueIsHalted")) then
            EnableQueue:SetText(L["Resume"]);
        end

        EnableQueue:SetScript("OnClick", function ()
            Auctioneer:toggleQueueStatus();

            if (Settings:get("GDKP.queueIsHalted")) then
                EnableQueue:SetText(L["Resume"]);
            else
                EnableQueue:SetText(L["Halt"]);
            end
        end);

        --[[ CLEAR QUEUE ]]
        ---@type Button
        local ClearQueue = Interface:dynamicPanelButton(Queue, L["Clear"]);
        ClearQueue:SetPoint("BOTTOMRIGHT", Queue, "BOTTOMRIGHT", -20, 16);
        ClearQueue:SetPoint("LEFT", Queue, "CENTER", 2, 0);
        ClearQueue:SetScript("OnClick", function ()
            Auctioneer:clearQueue();

            for key, ItemRow in pairs(self.ItemRows or {}) do
                Interface:release(ItemRow);
                self.ItemRows[key] = nil;
            end

            self.ItemRows = {};
        end);
    end

    return Queue;
end

---@param Window Frame
---@return table
function AuctioneerUI:buildBidsTable(Window)
    local Table = GL.ScrollingTable:CreateST(BIDS_TABLE_COLUMNS, DEFAULT_TABLE_ROWS, HEIGHT_PER_TABLE_ROW, nil, Window);
    Table:EnableSelection(true);
    Table.head:Hide(); -- Remove the table header

    Table:RegisterEvents({
        --[[ RECENTLY WON BY BIDDER TOOLTIP ]]
        OnEnter = function (rowFrame, _, data, _, _, realrow)
            -- Make sure something is actually highlighted, better safe than lua error
            if (not GL:higherThanZero(realrow)
                or type(data) ~= "table"
                or not data[realrow]
                or not data[realrow].cols
                or not data[realrow].cols[1]
            ) then
                return;
            end

            local bidder = data[realrow].cols[1].value;
            local ItemsWonByRollerInTheLast8Hours = GL.AwardedLoot:byWinner(bidder, GetServerTime() - (8 * 60 * 60));

            if (GL:empty(ItemsWonByRollerInTheLast8Hours)) then
                return;
            end

            Interface:addTooltip(rowFrame, {
                (L["Items won by %s:"]):format(bidder),
                " ",
                ItemsWonByRollerInTheLast8Hours,
            });
        end,
    });

    return Table;
end

---@param buildWhenMissing boolean
---@return table|nil
function AuctioneerUI:getBidsTable(buildWhenMissing)
    if (not self.BidsTable and buildWhenMissing) then
        self:build();
    end

    return self.BidsTable;
end

---@return Frame
function AuctioneerUI:getWindow()
    return _G[self.windowName];
end

---@param buildWhenMissing boolean
---@return Frame|nil
function AuctioneerUI:getQueueWindow(buildWhenMissing)
    local Window = _G[self.queueWindowName];

    if (not Window and buildWhenMissing) then
        Window = self:build();
        self:close();
    end

    return _G[self.queueWindowName];
end

---@return Frame
function AuctioneerUI:openAuctioneerShortcut()
    if (self.isVisible) then
        return;
    end

    local Shortcut = _G[self.auctioneerShortcutName] or self:buildAuctioneerShortcut();
    Shortcut:SetNormalTexture(Auction.Current.itemIcon);
    Shortcut:Show();

    return Shortcut;
end

---@return Frame
function AuctioneerUI:closeAuctioneerShortcut()
    local Shortcut = _G[self.auctioneerShortcutName];

    if (Shortcut) then
        Shortcut:Hide();
    end

    return Shortcut;
end

---@return Frame
function AuctioneerUI:buildAuctioneerShortcut()
    if (_G[self.auctioneerShortcutName]) then
        return _G[self.auctioneerShortcutName];
    end

    local Button = CreateFrame("Button", self.auctioneerShortcutName, UIParent);
    Button:SetSize(42, 42);
    Button:SetNormalTexture(Auction.Current.itemIcon);
    Button:SetText(" ");
    Interface:restorePosition(Button);

    Button:SetMovable(true);
    Button:EnableMouse(true);
    Button:SetClampedToScreen(true);
    Button:SetFrameStrata("FULLSCREEN_DIALOG");
    Button:RegisterForDrag("LeftButton");
    Button:SetScript("OnDragStart", Button.StartMoving);
    Button:SetScript("OnDragStop", function ()
        Button:StopMovingOrSizing();

        -- Store the frame's last position for future play sessions
        Interface:storePosition(Button);
    end);

    local ButtonHighlight = Button:CreateTexture(nil, "HIGHLIGHT");
    ButtonHighlight:SetAllPoints(Button);
    ButtonHighlight:SetTexture("Interface/PaperDollInfoFrame/UI-Character-Tab-Highlight");
    ButtonHighlight:SetTexCoord(0, 1, 0.23, 0.77);
    ButtonHighlight:SetBlendMode("ADD");
    Button.ButtonHighlight = ButtonHighlight;

    Button:SetScript("OnMouseUp", function (_, button)
        if (button == "LeftButton") then
            self:open();
            Button:Hide();
        end
    end);

    Interface:addTooltip(Button, "Open auctioneer window");

    return Button;
end

---@param Row Frame
---@return nil
function AuctioneerUI:deleteRowFromQueue(Row)
    GL:stopHighlight(Row);
    GL:stopHighlight(Row.Icon);
    Row:Hide();
    Row._itemLink = nil;
    Row._identifier = nil;
    Interface:release(Row);
    Row = nil;

    self:reorderItems();
end

---@return nil
function AuctioneerUI:refreshIconGlows()
    for _, ItemRow in pairs(self.ItemRows or {}) do
        if (ItemRow and ItemRow._itemLink) then
            GL:highlightItem(ItemRow, ItemRow._itemLink);
        end
    end
end
