local _, addonTable = ...
local WipedQoL = addonTable.WipedQoL
local TRANSLATE = addonTable.TRANSLATE or function(msg) return msg end

local MACRO_NAME = "WipedQoLHealth"
local MACRO_ICON_FALLBACK = "INV_Misc_QuestionMark"
local HEALTHSTONE_ITEM_ID = 5512
local DEMONIC_HEALTHSTONE_ITEM_ID = 224464
local HEALTH_POTION_ITEM_ID = 241304
local HEALTH_POTION_ITEM_ID_2 = 241305
local HEALTH_POTION_ITEM_IDS = { HEALTH_POTION_ITEM_ID, HEALTH_POTION_ITEM_ID_2 }
local MAX_MACRO_BODY_LENGTH = 255
local DEFAULT_CLASS_HEALING_SPELL_IDS = {
    HUNTER = 109304, -- Exhilaration
    ROGUE = 185311, -- Crimson Vial
    WARRIOR = 34428, -- Victory Rush
}
local lastMacroText
local BuildHealthMacroText

local function GetLocalizedSpellName(english, german)
    return GetLocale and GetLocale() == "deDE" and german or english
end

local function GetHealthstoneItemID()
    local classFile
    if UnitClass then
        _, classFile = UnitClass("player")
    end

    if classFile == "WARLOCK" then
        return DEMONIC_HEALTHSTONE_ITEM_ID
    end

    return HEALTHSTONE_ITEM_ID
end

local function GetPlayerClassFile()
    return UnitClass and select(2, UnitClass("player")) or nil
end

local function GetSpellNameByID(spellID)
    if C_Spell and C_Spell.GetSpellName then
        return C_Spell.GetSpellName(spellID)
    end
    if C_Spell and C_Spell.GetSpellInfo then
        local info = C_Spell.GetSpellInfo(spellID)
        return info and info.name or nil
    end
    if GetSpellInfo then
        return GetSpellInfo(spellID)
    end
    return nil
end

function WipedQoL:GetHealthMacroSettings()
    local modules = self.db and self.db.profile and self.db.profile.modules
    if type(modules) ~= "table" then
        return {
            includeClassHealingSpell = false,
            classSpells = {},
            classSpellDefaultsInitialized = {},
        }
    end

    modules.healthMacro = modules.healthMacro or {}
    local settings = modules.healthMacro
    settings.includeClassHealingSpell = settings.includeClassHealingSpell == true
    settings.classSpells = type(settings.classSpells) == "table" and settings.classSpells or {}
    settings.classSpellDefaultsInitialized = type(settings.classSpellDefaultsInitialized) == "table"
        and settings.classSpellDefaultsInitialized or {}
    return settings
end

function WipedQoL:GetHealthMacroClassSpells()
    local classFile = GetPlayerClassFile()
    if not classFile then
        return {}
    end

    local settings = self:GetHealthMacroSettings()
    local entries = settings.classSpells[classFile]
    if type(entries) ~= "table" then
        entries = {}
        settings.classSpells[classFile] = entries
    end

    if settings.classSpellDefaultsInitialized[classFile] ~= true then
        local defaultSpellID = DEFAULT_CLASS_HEALING_SPELL_IDS[classFile]
        if defaultSpellID then
            entries[#entries + 1] = {
                id = defaultSpellID,
                enabled = settings.includeClassHealingSpell == true,
            }
        end
        settings.classSpellDefaultsInitialized[classFile] = true
    end

    local normalized = {}
    local seen = {}
    for _, entry in ipairs(entries) do
        local spellID = tonumber(type(entry) == "table" and entry.id or entry)
        if spellID and spellID > 0 and not seen[spellID] then
            normalized[#normalized + 1] = {
                id = spellID,
                enabled = type(entry) == "table" and entry.enabled == true,
            }
            seen[spellID] = true
        end
    end
    settings.classSpells[classFile] = normalized
    return normalized
end

function WipedQoL:AddHealthMacroClassSpell(spellID)
    spellID = tonumber(spellID)
    if not spellID or spellID <= 0 or not GetSpellNameByID(spellID) then
        return false
    end

    local entries = self:GetHealthMacroClassSpells()
    for _, entry in ipairs(entries) do
        if entry.id == spellID then
            return false
        end
    end

    entries[#entries + 1] = {
        id = spellID,
        enabled = true,
    }
    self:GetHealthMacroSettings().classSpells[GetPlayerClassFile()] = entries
    local reason
    if #BuildHealthMacroText() > MAX_MACRO_BODY_LENGTH then
        for _, storedEntry in ipairs(self:GetHealthMacroClassSpells()) do
            if storedEntry.id == spellID then
                storedEntry.enabled = false
                break
            end
        end
        reason = "macroTooLong"
    end
    self:EnsureHealthMacro()
    if self.RefreshHealthMacroGameplayUI then
        self:RefreshHealthMacroGameplayUI()
    end
    return true, reason
end

function WipedQoL:SetHealthMacroClassSpellEnabled(spellID, enabled)
    spellID = tonumber(spellID)
    for _, entry in ipairs(self:GetHealthMacroClassSpells()) do
        if entry.id == spellID then
            local oldValue = entry.enabled == true
            entry.enabled = enabled == true
            if enabled == true and #BuildHealthMacroText() > MAX_MACRO_BODY_LENGTH then
                entry.enabled = oldValue
                for _, storedEntry in ipairs(self:GetHealthMacroClassSpells()) do
                    if storedEntry.id == spellID then
                        storedEntry.enabled = oldValue
                        break
                    end
                end
                return false, "macroTooLong"
            end
            self:EnsureHealthMacro()
            if self.RefreshHealthMacroGameplayUI then
                self:RefreshHealthMacroGameplayUI()
            end
            return true
        end
    end
    return false
end

function WipedQoL:RemoveHealthMacroClassSpell(spellID)
    spellID = tonumber(spellID)
    local entries = self:GetHealthMacroClassSpells()
    for index, entry in ipairs(entries) do
        if entry.id == spellID then
            table.remove(entries, index)
            self:EnsureHealthMacro()
            if self.RefreshHealthMacroGameplayUI then
                self:RefreshHealthMacroGameplayUI()
            end
            return true
        end
    end
    return false
end

local function GetEnabledClassHealingSpellNames()
    local names = {}
    for _, entry in ipairs(WipedQoL:GetHealthMacroClassSpells()) do
        if entry.enabled then
            local spellName = GetSpellNameByID(entry.id)
            if spellName then
                names[#names + 1] = spellName
            end
        end
    end
    return names
end

BuildHealthMacroText = function()
    local recuperate = GetLocalizedSpellName("Recuperate", "Gesundung")
    local healthstoneItemID = GetHealthstoneItemID()
    local classHealingSpells = GetEnabledClassHealingSpellNames()

    local lines = {
        "#showtooltip [combat] item:" .. HEALTH_POTION_ITEM_ID .. "; [nocombat] " .. recuperate,
        "/stopcasting",
        "/cast [nocombat] " .. recuperate,
    }
    for _, spellName in ipairs(classHealingSpells) do
        lines[#lines + 1] = "/cast [combat] " .. spellName
    end
    lines[#lines + 1] = "/use [combat] item:" .. healthstoneItemID
    lines[#lines + 1] = "/use [combat] item:" .. HEALTH_POTION_ITEM_ID
    lines[#lines + 1] = "/use [combat] item:" .. HEALTH_POTION_ITEM_ID_2

    return table.concat(lines, "\n")
end

function WipedQoL:GetHealthMacroMaxLength()
    return MAX_MACRO_BODY_LENGTH
end

local function FindHealthMacro()
    if not GetNumMacros or not GetMacroInfo then
        return nil, nil
    end

    local globalCount, characterCount = GetNumMacros()
    local maxGlobalMacros = MAX_ACCOUNT_MACROS or 120

    for index = 1, tonumber(globalCount) or 0 do
        local name = GetMacroInfo(index)
        if name == MACRO_NAME then
            return index, false
        end
    end

    for index = maxGlobalMacros + 1, maxGlobalMacros + (tonumber(characterCount) or 0) do
        local name = GetMacroInfo(index)
        if name == MACRO_NAME then
            return index, true
        end
    end

    return nil, nil
end

local function HasFreeGlobalMacroSlot()
    local globalCount = GetNumMacros and GetNumMacros() or 0
    local maxGlobalMacros = MAX_ACCOUNT_MACROS or 120

    return (tonumber(globalCount) or 0) < maxGlobalMacros
end

function WipedQoL:GetHealthMacroIcon()
    if C_Item and C_Item.GetItemIconByID then
        return C_Item.GetItemIconByID(HEALTH_POTION_ITEM_ID) or MACRO_ICON_FALLBACK
    end
    if GetItemIcon then
        return GetItemIcon(HEALTH_POTION_ITEM_ID) or MACRO_ICON_FALLBACK
    end

    return MACRO_ICON_FALLBACK
end

function WipedQoL:GetHealthPotionCount()
    local count = 0

    for _, itemID in ipairs(HEALTH_POTION_ITEM_IDS) do
        if C_Item and C_Item.GetItemCount then
            count = count + (tonumber(C_Item.GetItemCount(itemID, false, false, false)) or 0)
        elseif GetItemCount then
            count = count + (tonumber(GetItemCount(itemID, false, false)) or 0)
        end
    end

    return count
end

function WipedQoL:EnsureHealthMacro()
    if not GetNumMacros or not GetMacroInfo or not CreateMacro then
        return nil
    end

    local macroIndex, isCharacterMacro = FindHealthMacro()
    local macroText = BuildHealthMacroText()
    local icon = MACRO_ICON_FALLBACK

    if self:IsInCombat() then
        if macroIndex and macroIndex > 0 then
            return macroIndex
        end

        self:Print(TRANSLATE("Macro creation can be blocked by WoW while in combat."))
        return nil
    end

    if #macroText > MAX_MACRO_BODY_LENGTH then
        self:Print(string.format(
            TRANSLATE("Health macro is too long (%d/%d characters)."),
            #macroText,
            MAX_MACRO_BODY_LENGTH
        ))
        return macroIndex
    end

    if macroIndex and isCharacterMacro and HasFreeGlobalMacroSlot() and DeleteMacro then
        DeleteMacro(macroIndex)
        macroIndex = nil
        lastMacroText = nil
        self:Print(TRANSLATE("Personal health macro removed; creating it globally now."))
    end

    if macroIndex and macroIndex > 0 then
        if EditMacro then
            EditMacro(macroIndex, MACRO_NAME, icon, macroText)
            lastMacroText = macroText
        end

        return macroIndex
    end

    local createdIndex = CreateMacro(MACRO_NAME, icon, macroText)
    if not createdIndex or createdIndex <= 0 then
        createdIndex = CreateMacro(MACRO_NAME, icon, macroText, true)
    end

    if createdIndex and createdIndex > 0 then
        lastMacroText = macroText
        self:Print(string.format(TRANSLATE("Macro '%s' created/updated."), MACRO_NAME))
        return createdIndex
    end

    self:Print(TRANSLATE("Macro could not be created. Global and personal macro slots are probably full."))
    return nil
end

function WipedQoL:ResolveHealthMacroIndex()
    local macroIndex = FindHealthMacro()
    return macroIndex
end

function WipedQoL:OpenHealthMacro()
    if self:IsInCombat() then
        return
    end

    local macroIndex = self:EnsureHealthMacro()
    macroIndex = self:ResolveHealthMacroIndex() or macroIndex

    if macroIndex and self.OpenBlizzardMacro then
        self:OpenBlizzardMacro(macroIndex)
    end
end

function WipedQoL:GetHealthMacroBody()
    return BuildHealthMacroText()
end

local function ScheduleHealthMacroEnsure()
    if C_Timer and C_Timer.After then
        C_Timer.After(1, function()
            if WipedQoL.EnsureHealthMacro then
                WipedQoL:EnsureHealthMacro()
            end
        end)
        return
    end

    WipedQoL:EnsureHealthMacro()
end

function WipedQoL:SetupHealthMacro()
    if self.healthMacroLoginRegistered then
        return
    end

    if IsLoggedIn and IsLoggedIn() then
        ScheduleHealthMacroEnsure()
        return
    end

    self:RegisterEvent("PLAYER_LOGIN", "HandleHealthMacroLogin")
    self.healthMacroLoginRegistered = true
end

function WipedQoL:HandleHealthMacroLogin()
    self:UnregisterEvent("PLAYER_LOGIN")
    self.healthMacroLoginRegistered = nil

    ScheduleHealthMacroEnsure()
end
