local _, addon = ...
local API = addon.API;

local _G = _G;

local MERCHANT_ITEMS_PER_PAGE = 10;
local BUYBACK_ITEMS_PER_PAGE = 12;
local MAX_MERCHANT_CURRENCIES = 6;
local MerchantFrame = MerchantFrame;
local MERCHANT_FRAME = "MerchantFrame";

local PRICE_FRAME_OFFSET_X = 45;

local GetMerchantNumItems = GetMerchantNumItems;
local GetMerchantItemCostInfo = GetMerchantItemCostInfo;
local GetMerchantItemCostItem = GetMerchantItemCostItem;
local GetNumBuybackItems = GetNumBuybackItems;
local GetBuybackItemInfo = GetBuybackItemInfo;
local GetMoney = GetMoney;

local ceil = math.ceil;
local match = string.match;
local tonumber = tonumber;
local tsort = table.sort;
local type = type;

local IsItemContextToken = API.IsItemContextToken;

local ShopUI;
local Controller = CreateFrame("Frame");
local TokenDisplay;
local VendorItemPriceFrame = {};

local GetMerchantItemPrice;
if C_MerchantFrame.GetItemInfo then
    local GetMerchantItemInfo = C_MerchantFrame.GetItemInfo;
    function GetMerchantItemPrice(index)
        local info = GetMerchantItemInfo(index);
        if info then
            return info.price, info.hasExtendedCost
        end
    end
else
    local GetMerchantItemInfo = GetMerchantItemInfo;
    function GetMerchantItemPrice(index)
        local _, price, hasExtendedCost;
        _, _, price, _, _, _, _, hasExtendedCost = GetMerchantItemInfo(index);
        return price, hasExtendedCost
    end
end

local function HidePlumberWidgets()
    for _, priceFrame in pairs(VendorItemPriceFrame) do
        priceFrame:Hide();
    end

    if TokenDisplay then
        TokenDisplay:Hide();
    end
end


local function HideBlizzardUITexture()
    MerchantMoneyInset.Bg:SetTexture(nil);
    MerchantMoneyBgLeft:SetTexture(nil);
    MerchantMoneyBgMiddle:SetTexture(nil);
    MerchantMoneyBgRight:SetTexture(nil);
    MerchantMoneyInset:Hide();

    MerchantExtraCurrencyInset.Bg:SetTexture(nil);
    MerchantExtraCurrencyInset.NineSlice:Hide();
    MerchantExtraCurrencyBgLeft:SetTexture(nil);
    MerchantExtraCurrencyBgMiddle:SetTexture(nil);
    MerchantExtraCurrencyBgRight:SetTexture(nil);
end

local InvisibleContainer = {};
do
    InvisibleContainer.objects = {
        --[GlobalName] = OriginalParent
        ["MerchantMoneyInset"] = MERCHANT_FRAME,
        ["MerchantMoneyBg"] = MERCHANT_FRAME,
        ["MerchantExtraCurrencyInset"] = MERCHANT_FRAME,
        ["MerchantExtraCurrencyBg"] = MERCHANT_FRAME,
        ["MerchantMoneyFrame"] = MERCHANT_FRAME,
    };

    function InvisibleContainer:HideObjects(noChangeItemPrice)
        if not ShopUI then return end;

        if not self.f then
            self.f = CreateFrame("Frame", nil, ShopUI);
            self.f:Hide();
        end

        for name in pairs(self.objects) do
            if _G[name] then
                _G[name]:SetParent(self.f);
            end
        end

        if not noChangeItemPrice then
            local merchantMoney, merchantAltCurrency;

            for i = 1, BUYBACK_ITEMS_PER_PAGE do
                merchantMoney = _G["MerchantItem"..i.."MoneyFrame"];
                merchantAltCurrency = _G["MerchantItem"..i.."AltCurrencyFrame"];
                if merchantMoney then
                    merchantMoney:SetParent(self.f);
                end
                if merchantAltCurrency then
                    merchantAltCurrency:SetParent(self.f);
                end
            end
        end

        self.objectsHidden = true;
    end

    function InvisibleContainer:RestoreObjects()
        if not self.objectsHidden then return end;

        for name, parentName in pairs(self.objects) do
            if _G[name] and _G[parentName] then
                _G[name]:SetParent(_G[parentName]);
            end
        end

        local merchantButton, merchantMoney, merchantAltCurrency;

        for i = 1, BUYBACK_ITEMS_PER_PAGE do
            merchantButton = _G["MerchantItem"..i];
            merchantMoney = _G["MerchantItem"..i.."MoneyFrame"];
            merchantAltCurrency = _G["MerchantItem"..i.."AltCurrencyFrame"];
            if merchantButton then
                if merchantMoney then
                    merchantMoney:SetParent(merchantButton);
                end
                if merchantAltCurrency then
                    merchantAltCurrency:SetParent(merchantButton);
                end
            end
        end
    end

    function InvisibleContainer:HideBlizzardMerchantTokens()
        for i = 1, MAX_MERCHANT_CURRENCIES do
            local tokenButton = _G["MerchantToken"..i];
            if tokenButton then
                tokenButton:Hide();
            end
        end
    end
end


local function SortFunc_CurrencyType(a, b)
    if a[1] ~= b[1] then
        return a[1] < b[1]
    end

    if type(a) ~= type(b) then
        return type(a) == "number"
    end

    return a[2] < b[2]
end

function Controller:OnUpdate(elapsed)
    self.t = self.t + elapsed;
    if self.t >= 0 then
        self:SetScript("OnUpdate", nil);
        self.t = nil;
        self:UpdateShopUI();
    end
end

function Controller:UpdateMoneyChange() --Unused
    if self.playerCopper and self.moneyDirty then
        self.moneyDirty = nil;

        local newCopper = GetMoney();
        local diff = newCopper - self.playerCopper;
        self.playerCopper = newCopper;

        if diff > 0 then
            
        elseif diff < 0 then

        end
    end
end

function Controller:UpdateShopUI()
    if not ShopUI:IsVisible() then return end;

    local merchantMode = ShopUI.selectedTab == 1;
    local buybackMode = ShopUI.selectedTab == 2;    --1 Buy, 2 Buyback

    self.buybackMode = buybackMode;

    if buybackMode then
        self:UpdateBuybackInfo();
    elseif merchantMode then
        self:UpdateMerchantInfo();
    else
        if TokenDisplay then
            TokenDisplay:Hide();
        end
    end

    --self:UpdateMoneyChange();
end

function Controller:SetupTokenDisplay()
    if not TokenDisplay then
        TokenDisplay = addon.CreateTokenDisplay(ShopUI, "CoinBox");
        TokenDisplay.numberFont = "NumberFontNormal";
        TokenDisplay:SetIncludeBank(true);
        TokenDisplay:ShowMoneyFrame(true);
        TokenDisplay:SetButtonClickable(true);
    end
end

function Controller:UpdateMerchantInfo()
    InvisibleContainer:HideBlizzardMerchantTokens();

    local page = ShopUI.page;

    local name, texture, price, stackCount, numAvailable, isPurchasable, isUsable, extendedCost, currencyID, spellID;
    local numCost;
    local itemTexture, itemValue, itemLink;
    local id, currencyType;
    local priceFrame;

    local numMerchantItems = GetMerchantNumItems();
    local fromIndex = (page - 1) * MERCHANT_ITEMS_PER_PAGE;
    local merchantButton;
    local anyGold;
    local altCurreny;
    local hasAnyContextToken = false;
    local slotIndexList = {};

    local playerMoney = GetMoney();

    local buttonIndex = 0;
    local numPages = ceil(numMerchantItems / MERCHANT_ITEMS_PER_PAGE);

    local numItemsThisPage;

    if page < numPages then
        numItemsThisPage = MERCHANT_ITEMS_PER_PAGE;
    else
        numItemsThisPage = numMerchantItems - (numPages - 1) * MERCHANT_ITEMS_PER_PAGE;
    end

    for buttonIndex = numItemsThisPage + 1, MERCHANT_ITEMS_PER_PAGE do
        priceFrame = VendorItemPriceFrame[buttonIndex];
        if priceFrame then
            priceFrame:Hide();
        end
    end

    for i = fromIndex + 1, fromIndex + numItemsThisPage do
        price, extendedCost = GetMerchantItemPrice(i);

        buttonIndex = buttonIndex + 1;
        merchantButton = _G["MerchantItem"..buttonIndex];

        priceFrame = VendorItemPriceFrame[buttonIndex];
        if not priceFrame then
            priceFrame = addon.CreatePriceDisplay(merchantButton);
            VendorItemPriceFrame[buttonIndex] = priceFrame;
            priceFrame:SetPoint("BOTTOMLEFT", merchantButton, "BOTTOMLEFT", PRICE_FRAME_OFFSET_X, 0);
        end

        if price and price > 0 then
            anyGold = true;
        end

        local requiredCurrency;

        if extendedCost then
            numCost = GetMerchantItemCostInfo(i);
            requiredCurrency = {};

            for n = 1, numCost do
                itemTexture, itemValue, itemLink = GetMerchantItemCostItem(i, n);
                --uncached item's link may be nil

                if itemLink then
                    id = match(itemLink, "currency:(%d+)");
                    if id then
                        currencyType = 0;
                    else
                        id = match(itemLink, "item:(%d+)");
                        if id then
                            currencyType = 1;
                        end
                    end

                    if id and currencyType then
                        id = tonumber(id);

                        if not altCurreny then
                            altCurreny = {};
                        end

                        if id and not altCurreny[id] then
                            --assume itemID and currencyID don't accidently overlap
                            altCurreny[id] = currencyType;

                            if (not hasAnyContextToken) and IsItemContextToken(id) then
                                hasAnyContextToken = true;
                            end
                        end

                        requiredCurrency[n] = {currencyType, id, itemValue, itemTexture, itemLink, i, n};
                    end
                else
                    self:RequestUpdate(0.2);
                end
            end
        else
            numCost = 0;
        end

        slotIndexList[buttonIndex] = {i, numCost};

        priceFrame:SetFrameOwner(merchantButton, "BOTTOMLEFT", PRICE_FRAME_OFFSET_X, 0, "MEDIUM");
        priceFrame:SetMoneyAndAltCurrency(price, requiredCurrency, playerMoney);
        priceFrame:Show();
    end

    self:SetupTokenDisplay();

    if anyGold or not altCurreny then
        TokenDisplay:ShowMoneyFrame(true);
    else
        TokenDisplay:ShowMoneyFrame(false);
    end

    local tokens = {};
    if altCurreny then
        TokenDisplay.MoneyFrame:SetSimplified(true);

        local n = 0;

        for id, currencyType in pairs(altCurreny) do
            n = n + 1;
            tokens[n] = {currencyType, id};
        end

        tsort(tokens, SortFunc_CurrencyType);
    else
        TokenDisplay.MoneyFrame:SetSimplified(false);
    end

    --TokenDisplay:DisplayCurrencyOnFrame(tokens, ShopUI, "BOTTOMLEFT", 4, 6);
    if not self.otherTabShown then
        if not TokenDisplay:DisplayMerchantPriceOnFrame(tokens, ShopUI, -5, 6, hasAnyContextToken and slotIndexList or nil) then
            self:RequestUpdate(0.2);
        end
    end
end

function Controller:UpdateBuybackInfo()
    local _, buybackPrice;
    local merchantButton;
    local priceFrame;

    local numItems = GetNumBuybackItems();

    for i = 1, BUYBACK_ITEMS_PER_PAGE do
        priceFrame = VendorItemPriceFrame[i];

        if i <= numItems then
            _, _, buybackPrice = GetBuybackItemInfo(i);

            merchantButton = _G["MerchantItem"..i];

            if not priceFrame then
                priceFrame = addon.CreatePriceDisplay(merchantButton);
                VendorItemPriceFrame[i] = priceFrame;
                priceFrame:SetPoint("BOTTOMLEFT", merchantButton, "BOTTOMLEFT", PRICE_FRAME_OFFSET_X, 0);
            end

            priceFrame:SetMoneyAndAltCurrency(buybackPrice);
            priceFrame:Show();
        else
            if priceFrame then
                priceFrame:Hide();
            end
        end
    end

    self:SetupTokenDisplay();
    TokenDisplay:ShowMoneyFrame(true);
    TokenDisplay.MoneyFrame:SetSimplified(false);
    if not self.otherTabShown then
        TokenDisplay:DisplayCurrencyOnFrame(nil, ShopUI, "BOTTOMRIGHT", -5, 6);
    end
end

function Controller:RequestUpdate(delay)
    if not self.t then
        self.t = 0;
    end

    self.t = self.t -(delay or 0);
    if self.t < -0.25 then
        self.t = -0.25;
    end

    self:SetScript("OnUpdate", self.OnUpdate);
end


function Controller:ListenEvents(state)
    if state then
        self:RegisterEvent("BAG_UPDATE");
        self:RegisterEvent("CURRENCY_DISPLAY_UPDATE");
        self:RegisterEvent("PLAYER_MONEY");
    else
        self:UnregisterEvent("BAG_UPDATE");
        self:UnregisterEvent("CURRENCY_DISPLAY_UPDATE");
        self:UnregisterEvent("PLAYER_MONEY");
    end
end

function Controller:OnShow()
    self:ListenEvents(true);
    self.playerCopper = GetMoney();
end

function Controller:OnHide()
    self:ListenEvents(false);
    self:SetScript("OnUpdate", nil);
    self.t = nil;
end

function Controller:OnEvent(event, ...)
    if event == "PLAYER_MONEY" then
        self.moneyDirty = true;
    end

    if self.buybackMode then
        self:RequestUpdate(0);
    else
        self:RequestUpdate(0.2);
    end
end

local function MerchantFrame_Update_Callback()
    if Controller.isEnabled then
        Controller:RequestUpdate();
    end
end

local MerchantFrameOpenedOnce = false;
local function PanelTemplates_SelectTab_Callback(tab)
    if tab:GetParent() == MerchantFrame then
        if tab:GetID() == 0 then
            HidePlumberWidgets();
        end

        if not MerchantFrameOpenedOnce then
            MerchantFrameOpenedOnce = true;
            if MerchantFrameCoverTab then
                local f = CreateFrame("Frame", nil, MerchantFrameCoverTab);
                f:SetScript("OnShow", function()
                    Controller.otherTabShown = true;
                end);
                f:SetScript("OnHide", function()
                    Controller.otherTabShown = nil;
                    MerchantFrame_Update_Callback();
                end);
                if f:IsVisible() then
                    Controller.otherTabShown = true;
                end
            end
        end
    end
end

function Controller:EnableModule(state)
    if state then
        if MerchantFrame_Update and _G[MERCHANT_FRAME] then
            self.isEnabled = true;
            ShopUI = _G[MERCHANT_FRAME];
            Controller:SetParent(ShopUI);

            if not self.isHooked then
                self.isHooked = true;
                hooksecurefunc("MerchantFrame_Update", MerchantFrame_Update_Callback);

                if C_AddOns.IsAddOnLoaded("Scrap") then
                    --Scrap (lib SecureTabs-2.0) create a overlay on the stock UI to "hide" default objects
                    hooksecurefunc("PanelTemplates_SelectTab", PanelTemplates_SelectTab_Callback);
                end
            end

            local noChangeItemPrice = C_AddOns.IsAddOnLoaded("Krowi_ExtendedVendorUI") or C_AddOns.IsAddOnLoaded("ElvUI_WindTools");
            if noChangeItemPrice then
                Controller.UpdateMerchantInfo = Controller._UpdateMerchantInfo;
                Controller.UpdateBuybackInfo = Controller._UpdateBuybackInfo;
            end

            InvisibleContainer:HideObjects(noChangeItemPrice);

            self:SetScript("OnShow", self.OnShow);
            self:SetScript("OnHide", self.OnHide);
            self:SetScript("OnEvent", self.OnEvent);

            self:Show();

            if self:IsVisible() then
                self:OnShow();
            end
        end
    else
        if self.isEnabled then
            self:Hide();
            self:ListenEvents(false);
            InvisibleContainer:RestoreObjects();
            HidePlumberWidgets();
        end
    end
end


do  --For some Merchant UI addon users we only update the token frame
    function Controller:GetShopPage()
        return ShopUI.page or 1
    end

    function Controller:GetMaxItemsPerPage()
        --Some addons may change this global
        return _G.MERCHANT_ITEMS_PER_PAGE or MERCHANT_ITEMS_PER_PAGE;
    end

    function Controller:_UpdateMerchantInfo()
        InvisibleContainer:HideBlizzardMerchantTokens();

        local page = self:GetShopPage();

        local name, texture, price, stackCount, numAvailable, isPurchasable, isUsable, extendedCost, currencyID, spellID;
        local numCost;
        local itemTexture, itemValue, itemLink;
        local id, currencyType;
        local itemPerPage = self:GetMaxItemsPerPage();
        local numMerchantItems = GetMerchantNumItems();
        local fromIndex = (page - 1) * itemPerPage;
        local anyGold;
        local altCurreny;
        local hasAnyContextToken = false;
        local slotIndexList = {};

        local buttonIndex = 0;
        local numPages = ceil(numMerchantItems / itemPerPage);

        local numItemsThisPage;

        if page < numPages then
            numItemsThisPage = itemPerPage;
        else
            numItemsThisPage = numMerchantItems - (numPages - 1) * itemPerPage;
        end

        for i = fromIndex + 1, fromIndex + numItemsThisPage do
            price, extendedCost = GetMerchantItemPrice(i);

            buttonIndex = buttonIndex + 1;

            if price and price > 0 then
                anyGold = true;
            end

            local requiredCurrency;

            if extendedCost then
                numCost = GetMerchantItemCostInfo(i);
                requiredCurrency = {};

                for n = 1, numCost do
                    itemTexture, itemValue, itemLink = GetMerchantItemCostItem(i, n);
                    --uncached item's link may be nil

                    if itemLink then
                        id = match(itemLink, "currency:(%d+)");
                        if id then
                            currencyType = 0;
                        else
                            id = match(itemLink, "item:(%d+)");
                            if id then
                                currencyType = 1;
                            end
                        end

                        if id and currencyType then
                            id = tonumber(id);

                            if not altCurreny then
                                altCurreny = {};
                            end

                            if id and not altCurreny[id] then
                                --assume itemID and currencyID don't accidently overlap
                                altCurreny[id] = currencyType;

                                if (not hasAnyContextToken) and IsItemContextToken(id) then
                                    hasAnyContextToken = true;
                                end
                            end

                            requiredCurrency[n] = {currencyType, id, itemValue, itemTexture};
                        end
                    else
                        self:RequestUpdate(0.2);
                    end
                end
            else
                numCost = 0;
            end

            slotIndexList[buttonIndex] = {i, numCost};
        end

        self:SetupTokenDisplay();

        if anyGold or not altCurreny then
            TokenDisplay:ShowMoneyFrame(true);
        else
            TokenDisplay:ShowMoneyFrame(false);
        end

        local tokens = {};
        if altCurreny then
            TokenDisplay.MoneyFrame:SetSimplified(true);

            local n = 0;

            for id, currencyType in pairs(altCurreny) do
                n = n + 1;
                tokens[n] = {currencyType, id};
            end

            tsort(tokens, SortFunc_CurrencyType);
        else
            TokenDisplay.MoneyFrame:SetSimplified(false);
        end

        if not self.otherTabShown then
            if not TokenDisplay:DisplayMerchantPriceOnFrame(tokens, ShopUI, -5, 6, hasAnyContextToken and slotIndexList or nil) then
                self:RequestUpdate(0.2);
            end
        end
    end

    function Controller:_UpdateBuybackInfo()
        self:SetupTokenDisplay();
        TokenDisplay:ShowMoneyFrame(true);
        TokenDisplay.MoneyFrame:SetSimplified(false);
        if not self.otherTabShown then
            TokenDisplay:DisplayCurrencyOnFrame(nil, ShopUI, "BOTTOMRIGHT", -5, 6);
        end
    end
end


do
    local function EnableModule(state)
        Controller:EnableModule(state);
    end

    local moduleData = {
        name = addon.L["ModuleName MerchantPrice"],
        dbKey = "MerchantPrice",
        description = addon.L["ModuleDescription MerchantPrice"],
        toggleFunc = EnableModule,
        categoryID = 1,
        uiOrder = 6,
        moduleAddedTime = 1719566000,
    };

    addon.ControlCenter:AddModule(moduleData);
end




--[[
function Debug_ShowCurrentMerchantItemList()
    SetMerchantFilter(1);   --All

    local numMerchantItems = GetMerchantNumItems();

    local currencyID;
    local itemID;
    local output, lineText;
    local numCost, itemTexture, itemValue, itemLink, currencyName;

    for i = 1, numMerchantItems do
        local info = C_MerchantFrame.GetItemInfo(i);
        itemID = GetMerchantItemID(i);
        numCost = GetMerchantItemCostInfo(i);

        for n = 1, numCost do
            itemTexture, itemValue, itemLink, currencyName = GetMerchantItemCostItem(i, n);
            currencyID = info.currencyID or string.match(itemLink, "currency:(%d+)");
        end

        currencyID = currencyID or "";
        lineText = strjoin(", ", info.name, itemID, info.price, currencyID);

        if i == 1 then
            output = lineText;
        else
            output = output .. "\n" .. lineText;
        end
    end

    API.PrintTextToClipboard(output);
end
--]]