-- Handles mouse wheel and key bindings

PTUtil.SetEnvironment(Puppeteer)
local _G = getfenv(0)
local util = PTUtil

_G.BINDING_HEADER_PUPPETEER = "Puppeteer (Don't Touch These)"
local bindingsNames = {}
local MAX_BINDINGS = 24
for i = 1, MAX_BINDINGS do
    bindingsNames[i] = "PUPPETEERBINDING"..i
    _G["BINDING_NAME_"..bindingsNames[i]] = "Dynamic Binding "..i
end
local bindingsNamesSet = util.ToSet(bindingsNames)

local BindingPrefixes = {
    "",
    "SHIFT-",
    "CTRL-",
    "ALT-",
    "ALT-CTRL-SHIFT-",
    "ALT-CTRL-",
    "ALT-SHIFT-",
    "CTRL-SHIFT-"
}

StoredBindings = {} -- Key: Baked keybind key(like "ALT-SHIFT-R") | Value: The stored value of the binding
IndexButtonMap = {} -- Key: Index corresponding to the binding | Value: Button name
KeybindIndexMap = {} -- Key: Baked keybind key | Value: Index corresponding to the binding

function InitOverrideBindingsMapping()
    util.ClearTable(IndexButtonMap)
    util.ClearTable(KeybindIndexMap)
    local index = 1
    for _, button in ipairs(PTOptions.Buttons) do
        if not util.GetAllButtonsSet()[button] then
            for _, prefix in ipairs(BindingPrefixes) do
                KeybindIndexMap[prefix..button] = index
            end
            IndexButtonMap[index] = button
            index = index + 1
        end
    end

    for _, bindingName in ipairs(bindingsNames) do
        local k1, k2, k3, k4, k5, k6, k7, k8 = GetBindingKey(bindingName)
        for _, key in ipairs({k1, k2, k3, k4, k5, k6, k7, k8}) do
            if key then
                SetBinding(key, nil)
            end
        end
    end
end


-- Stuff to stop expensive UPDATE_BINDINGS events as much as possible

local noOp = function() end
local updateBindingsFunctions = {}
local updateBindingsFrames = {}
local holdingFunctionsHostage = false
function AddUpdateBindingsFunction(funcName)
    table.insert(updateBindingsFunctions, {
        funcName = funcName
    })
end
function AddUpdateBindingsFrame(frameName)
    if _G[frameName] then
        table.insert(updateBindingsFrames, {
            frame = _G[frameName]
        })
    end
end
-- Alternative method
function HookUpdateBindingsFrame(frameName)
    local frame = _G[frameName]
    local realScript = frame:GetScript("OnEvent")
    frame:SetScript("OnEvent", function()
        if holdingFunctionsHostage then
            return
        end
        realScript()
    end)
end
AddUpdateBindingsFunction("ActionButton_OnEvent")
AddUpdateBindingsFunction("BonusActionButton_OnEvent")
AddUpdateBindingsFunction("PetActionButton_OnEvent")
AddUpdateBindingsFunction("CharacterMicroButton_OnEvent")
AddUpdateBindingsFunction("TalentMicroButton_OnEvent")
AddUpdateBindingsFrame("MainMenuMicroButton")
AddUpdateBindingsFrame("QuestLogMicroButton")
AddUpdateBindingsFrame("SocialsMicroButton")
AddUpdateBindingsFrame("WorldMapMicroButton")
util.RunLater(function()
    AddUpdateBindingsFrame("pfActionBar") -- pfUI
    AddUpdateBindingsFrame("DFRL_HotkeyBinding") -- Dragonflight Reloaded
end)

local function StopUpdateBindingsUpdates()
    if holdingFunctionsHostage then
        return
    end
    holdingFunctionsHostage = true
    for _, entry in ipairs(updateBindingsFunctions) do
        entry.func = _G[entry.funcName]
        _G[entry.funcName] = noOp
    end
    for _, entry in ipairs(updateBindingsFrames) do
        entry.func = entry.frame:GetScript("OnEvent")
        entry.frame:SetScript("OnEvent", nil)
    end
end

local function StartUpdateBindingsUpdates()
    if not holdingFunctionsHostage then
        return
    end
    for _, entry in ipairs(updateBindingsFunctions) do
        _G[entry.funcName] = entry.func
    end
    for _, entry in ipairs(updateBindingsFrames) do
        entry.frame:SetScript("OnEvent", entry.func)
    end
    holdingFunctionsHostage = false
end

-- End of UPDATE_BINDINGS mitigation

function ApplyOverrideBindings()
    RemoveOverrideBindings()
    StopUpdateBindingsUpdates()
    for fullButton, index in pairs(KeybindIndexMap) do
        local binding = GetBindingAction(fullButton)
        
        if binding == "" then -- Lazy set the binding to our binding and don't touch until setting up again
            SetBinding(fullButton, bindingsNames[index])
        elseif not bindingsNamesSet[binding] then -- If it's not our binding, it must be stored and replaced
            StoredBindings[fullButton] = binding
            SetBinding(fullButton, bindingsNames[index])
        end
    end
    StartUpdateBindingsUpdates()
end

function RemoveOverrideBindings()
    if util.IsTableEmpty(StoredBindings) then
        return
    end
    StopUpdateBindingsUpdates()
    for button, binding in pairs(StoredBindings) do
        SetBinding(button, binding)
    end
    StartUpdateBindingsUpdates()
    util.ClearTable(StoredBindings)
end

function HandleKeyPress(index)
    if not Mouseover then
        return
    end
    local button = IndexButtonMap[index]
    if keystate == "down" then
        CurrentlyHeldButton = button
        MouseoverFrame.pressed = true
        MouseoverFrame:AdjustHealthPosition()
        if button == "MOUSEWHEELUP" or button == "MOUSEWHEELDOWN" then
            FakeMouseWheelHold(button)
        end
        ReapplySpellsTooltip()
        if PTOptions.CastWhenKey == "Key Down" then
            UnitFrame_OnClick(button, Mouseover, MouseoverFrame)
        end
    else
        if button ~= "MOUSEWHEELUP" and button ~= "MOUSEWHEELDOWN" then
            CurrentlyHeldButton = nil
            MouseoverFrame.pressed = false
            MouseoverFrame:AdjustHealthPosition()
            ReapplySpellsTooltip()
        end
        if PTOptions.CastWhenKey == "Key Up" then
            UnitFrame_OnClick(button, Mouseover, MouseoverFrame)
        end
    end
end

MouseWheelHeldFaker = CreateFrame("Frame", "PTMouseWheelHeldFaker")

local unholdAt
local unholdButton
local MouseWheelHeldFaker_OnUpdate = function()
    if GetTime() >= unholdAt then
        MouseWheelHeldFaker:SetScript("OnUpdate", nil)
        if CurrentlyHeldButton == unholdButton then
            CurrentlyHeldButton = nil
            if MouseoverFrame then
                MouseoverFrame.pressed = false
                MouseoverFrame:AdjustHealthPosition()
            end
            ReapplySpellsTooltip()
        end
    end
end
function FakeMouseWheelHold(button)
    unholdAt = GetTime() + 0.08
    unholdButton = button
    MouseWheelHeldFaker:SetScript("OnUpdate", MouseWheelHeldFaker_OnUpdate)
end