local _, addon = ...

addon.Actions = addon.Actions or {}
local Actions = addon.Actions

Actions.COMMANDS = {
    DISMOUNT = {
        id = "DISMOUNT",
        name = "Dismount",
        icon = 132261,
    },
    EJECT = {
        id = "EJECT",
        name = "Eject Passengers",
        icon = 132226,
    },
    FOLLOW = {
        id = "FOLLOW",
        name = "Follow Target",
        icon = 132328,
    },
    RANDOM_FAVORITE_MOUNT = {
        id = "RANDOM_FAVORITE_MOUNT",
        name = "Random Favorite Mount",
        icon = 132261,
    },
}

local function shallowCopy(tbl)
    if type(tbl) ~= "table" then
        return nil
    end
    local out = {}
    for k, v in pairs(tbl) do
        if type(v) == "table" then
            local t = {}
            for tk, tv in pairs(v) do
                t[tk] = tv
            end
            out[k] = t
        else
            out[k] = v
        end
    end
    return out
end

local function getSpellNameSafe(spellID)
    if type(spellID) ~= "number" or spellID <= 0 then
        return nil
    end
    if C_Spell and C_Spell.GetSpellName then
        return C_Spell.GetSpellName(spellID)
    end
    if type(GetSpellInfo) == "function" then
        return GetSpellInfo(spellID)
    end
    return nil
end

function Actions:Initialize()
    self.travelSpellIDs = {
        783, -- Travel Form
        2645, -- Ghost Wolf
        369536, -- Soar
        383359, -- Soar variant
        375841, -- Soar variant
        441313, -- Soar variant
        430747, -- Soar variant
    }
end

function Actions:NormalizeActionRef(actionRef)
    if type(actionRef) ~= "table" or not actionRef.type then
        return nil
    end
    local normalized = shallowCopy(actionRef)
    normalized.type = tostring(normalized.type)
    if normalized.id == nil then
        return nil
    end
    if not normalized.name or normalized.name == "" then
        normalized.name = tostring(normalized.id)
    end
    return normalized
end

function Actions:GetModifierSlotKey()
    local shift = IsShiftKeyDown()
    local ctrl = IsControlKeyDown()
    local alt = IsAltKeyDown()
    if shift and ctrl and alt then
        return "SHIFT_CTRL_ALT"
    elseif shift and ctrl then
        return "SHIFT_CTRL"
    elseif shift and alt then
        return "SHIFT_ALT"
    elseif ctrl and alt then
        return "CTRL_ALT"
    elseif shift then
        return "SHIFT"
    elseif ctrl then
        return "CTRL"
    elseif alt then
        return "ALT"
    end
    return "NONE"
end

function Actions:IsCommandAction(actionRef, commandID)
    return actionRef and actionRef.type == "COMMAND" and actionRef.id == commandID
end

function Actions:GetCuratedCommands()
    local out = {}
    for _, data in pairs(self.COMMANDS) do
        out[#out + 1] = {
            type = "COMMAND",
            id = data.id,
            name = data.name,
            icon = data.icon,
        }
    end
    table.sort(out, function(a, b) return a.name < b.name end)
    return out
end

function Actions:GetKnownTravelSpells(showAllKnown)
    local out = {}
    local seen = {}

    local function addSpell(spellID)
        if seen[spellID] then
            return
        end
        seen[spellID] = true
        if IsPlayerSpell(spellID) or IsSpellKnown(spellID) then
            local name = getSpellNameSafe(spellID)
            if name then
                out[#out + 1] = {
                    type = "SPELL",
                    id = spellID,
                    name = name,
                    icon = C_Spell and C_Spell.GetSpellTexture and C_Spell.GetSpellTexture(spellID) or nil,
                }
            end
        end
    end

    for _, spellID in ipairs(self.travelSpellIDs or {}) do
        addSpell(spellID)
    end
    if showAllKnown then
        -- Keep this lightweight in Settings; travel-focused list is default behavior.
    end

    table.sort(out, function(a, b) return a.name < b.name end)
    return out
end

function Actions:GetMountActions(showUncollected)
    local out = {}
    local ids = C_MountJournal.GetMountIDs and C_MountJournal.GetMountIDs() or {}
    for _, mountID in ipairs(ids) do
        local name, spellID, icon, isActive, isUsable, _, isFavorite, _, _, shouldHideOnChar, isCollected = C_MountJournal.GetMountInfoByID(mountID)
        local usable = (isUsable ~= false) and (shouldHideOnChar ~= true)
        if name and (showUncollected or isCollected) and usable then
            out[#out + 1] = {
                type = "MOUNT",
                id = mountID,
                name = name,
                icon = icon,
                payload = {
                    spellID = spellID,
                    collected = isCollected,
                    favorite = isFavorite,
                    active = isActive,
                    usable = usable,
                },
            }
        end
    end
    table.sort(out, function(a, b) return a.name < b.name end)
    return out
end

function Actions:GetMacroActions()
    local out = {}
    local globalCount, charCount = GetNumMacros()
    local total = (globalCount or 0) + (charCount or 0)
    for i = 1, total do
        local name, icon = GetMacroInfo(i)
        if name then
            out[#out + 1] = {
                type = "MACRO",
                id = i,
                name = name,
                icon = icon,
            }
        end
    end
    table.sort(out, function(a, b) return a.name < b.name end)
    return out
end

local function dispatchCommand(actionRef)
    if actionRef.id == "DISMOUNT" then
        Dismount()
        return true
    elseif actionRef.id == "EJECT" then
        if VehicleExit then
            VehicleExit()
            return true
        end
    elseif actionRef.id == "FOLLOW" then
        FollowUnit("target")
        return true
    elseif actionRef.id == "RANDOM_FAVORITE_MOUNT" then
        C_MountJournal.SummonByID(0)
        return true
    end
    return false
end

function Actions:ExecuteCommand(actionRef)
    actionRef = self:NormalizeActionRef(actionRef)
    if not actionRef or actionRef.type ~= "COMMAND" then
        return false
    end
    return dispatchCommand(actionRef)
end

function Actions:GetMountSpellID(mountID)
    local _, spellID = C_MountJournal.GetMountInfoByID(mountID)
    return spellID
end

local function getActiveMountID()
    local ids = C_MountJournal.GetMountIDs and C_MountJournal.GetMountIDs() or {}
    for _, id in ipairs(ids) do
        local _, _, _, isActive = C_MountJournal.GetMountInfoByID(id)
        if isActive then
            return id
        end
    end
    return nil
end

function Actions:ExecuteMount(actionRef)
    actionRef = self:NormalizeActionRef(actionRef)
    if not actionRef or actionRef.type ~= "MOUNT" then
        return false
    end

    local targetID = tonumber(actionRef.id)
    if not targetID then
        return false
    end

    if IsMounted and IsMounted() then
        local activeID = getActiveMountID()
        Dismount()
        if activeID and activeID == targetID then
            return true
        end
        C_Timer.After(0.12, function()
            C_MountJournal.SummonByID(targetID)
        end)
        return true
    end

    C_MountJournal.SummonByID(targetID)
    return true
end

function Actions:ApplyToSecureButton(button, actionRef)
    if not button then
        return false
    end

    actionRef = self:NormalizeActionRef(actionRef)
    button.commandRef = nil
    button.mountRef = nil

    button:SetAttribute("type", nil)
    button:SetAttribute("spell", nil)
    button:SetAttribute("macro", nil)
    button:SetAttribute("macrotext", nil)

    if not actionRef then
        return false
    end

    if actionRef.type == "MOUNT" then
        -- Mount execution is handled in PostClick to support remount/switch logic.
        button.mountRef = actionRef
        return true
    elseif actionRef.type == "SPELL" then
        button:SetAttribute("type", "spell")
        button:SetAttribute("spell", actionRef.id)
        return true
    elseif actionRef.type == "MACRO" then
        button:SetAttribute("type", "macro")
        if type(actionRef.id) == "number" then
            button:SetAttribute("macro", actionRef.id)
        else
            button:SetAttribute("macro", tostring(actionRef.id))
        end
        return true
    elseif actionRef.type == "COMMAND" then
        -- Commands are handled in PostClick (insecure), keep secure attrs clear.
        button.commandRef = actionRef
        return true
    end

    return false
end

function Actions:Execute(actionRef)
    actionRef = self:NormalizeActionRef(actionRef)
    if not actionRef then
        return false
    end

    if actionRef.type == "MOUNT" then
        return self:ExecuteMount(actionRef)
    elseif actionRef.type == "SPELL" then
        if IsPlayerSpell(actionRef.id) or IsSpellKnown(actionRef.id) then
            if C_Spell and C_Spell.CastSpell then
                C_Spell.CastSpell(actionRef.id)
            else
                CastSpellByID(actionRef.id)
            end
            return true
        end
        return false
    elseif actionRef.type == "MACRO" then
        if type(actionRef.id) == "number" then
            RunMacro(actionRef.id)
        else
            RunMacro(tostring(actionRef.id))
        end
        return true
    elseif actionRef.type == "COMMAND" then
        return self:ExecuteCommand(actionRef)
    end

    return false
end
