local _, ns = ...;

local _G = _G

local settingsProvider = ns.SettingsProvider:new()

local EndsWith = ns.StringExtensions.EndsWith
local StartsWith = ns.StringExtensions.StartsWith
local StringsAreEqual = ns.StringExtensions.StringsAreEqual
local NormalizeStringAndExtractNumerics = ns.StringNormalizer.NormalizeStringAndExtractNumerics
local IsValueInTable = ns.CommonExtensions.IsValueInTable

local SPELL_PASSIVE_TRANSLATION = ns.SPELL_PASSIVE_TRANSLATION
local TALENT_UPGRADE_TRANSLATION = ns.TALENT_UPGRADE_TRANSLATION
local SPELL_RANK_TRANSLATION = ns.SPELL_RANK_TRANSLATION
local SPELL_NEXT_RANK_TRANSLATION = ns.SPELL_NEXT_RANK_TRANSLATION
local TALENT_REPLACES_TRANSLATION = ns.TALENT_REPLACES_TRANSLATION
local TALENT_REPLACED_BY_TRANSLATION = ns.TALENT_REPLACED_BY_TRANSLATION

local GetSpellNameOrDefault = ns.DbContext.Spells.GetSpellNameOrDefault
local GetSpellDescriptionOrDefault = ns.DbContext.Spells.GetSpellDescriptionOrDefault
local GetSpellAttributeOrDefault = ns.DbContext.Spells.GetSpellAttributeOrDefault
local GetAdditionalSpellTipsOrDefault = ns.DbContext.Frames.GetAdditionalSpellTipsOrDefault

local talentRankPattern = "Rank (%d+)/(%d+)"
local talentReplacedByPattern = "^Replaced by%s+(.+)"
local talentReplacesPattern = "^Replaces%s+(.+)"
local maxChargesPattern = "Max %d+ Charges"

local translator = class("SpellTooltipTranslator", ns.Translators.BaseTooltipTranslator)
ns.Translators.SpellTooltipTranslator = translator

function translator:initialize(tooltipDataType)
    ns.Translators.BaseTooltipTranslator.initialize(self, tooltipDataType)

    hooksecurefunc(_G["TalentDisplayMixin"], "SetTooltipInternal", function(...)
        if (not self._postCallLineCount) then return end
        for i = self._postCallLineCount + 1, GameTooltip:NumLines() do
            local lineLeft = _G["GameTooltipTextLeft" .. i]
            if (lineLeft) then
                local leftTranslatedTips = GetAdditionalSpellTipsOrDefault(lineLeft:GetText() or '')
                lineLeft:SetText(leftTranslatedTips)
            end

            local lineRight = _G["GameTooltipTextRight" .. i]
            if (lineRight) then
                local rightTranslatedTips = GetAdditionalSpellTipsOrDefault(lineRight:GetText() or '')
                lineRight:SetText(rightTranslatedTips)
            end
        end
        GameTooltip:Show();
    end)

    EventRegistry:RegisterCallback("PvPTalentButton.TooltipHook", function(...)
        local function extractRequirementTalentName(str)
            local prefix = "Requires "
            local suffix = " talent"
            local start = str:find("^" .. prefix)
            local _end = str:find(suffix .. "$")
            if start and _end then
                local extracted = str:sub(#prefix + 1, _end - 1)
                return prefix .. "%s" .. suffix, extracted
            end
            return str
        end

        if (not self._postCallLineCount) then return end
        for i = self._postCallLineCount + 1, GameTooltip:NumLines() do
            local lineLeft = _G["GameTooltipTextLeft" .. i]
            if (lineLeft) then
                local text = lineLeft:GetText() or ''
                local requiresText, talentName = extractRequirementTalentName(text)
                if (talentName ~= nil) then
                    local translatedRequiresText = GetSpellAttributeOrDefault(requiresText)
                    translatedRequiresText = translatedRequiresText:format(GetSpellNameOrDefault(talentName))
                    lineLeft:SetText(translatedRequiresText)
                else
                    lineLeft:SetText(GetAdditionalSpellTipsOrDefault(text))
                end
            end
        end
        GameTooltip:Show();
    end, translator)
end

local function processResourceStrings(str)
    local function isResourceString(value)
        local spellResources = {
            "Arcane Charges",
            "Astral Power",
            "Chi",
            "Combo Points",
            "Energy",
            "Essence",
            "Focus",
            "Fury",
            "Holy Power",
            "Insanity",
            "Maelstrom",
            "Mana",
            "Pain",
            "Rage",
            "Rune",
            "Runes",
            "Health",
            "Runic Power",
            "Soul Shards",
            "Soul Shard"
        }

        for _, resource in ipairs(spellResources) do
            if value:match("^%d+[.,]?%d* to %d+[.,]?%d* " .. resource .. "$")
                or value:match("^%d+[.,]?%d* " .. resource .. "$")
                or value:match("^%d+[.,]?%d* " .. resource .. ", plus %d+[.,]?%d* per sec$")
                or value:match("^%d+[.,]?%d* " .. resource .. " per sec$") then
                return true
            end
        end

        return false
    end

    local function splitResourceString(value)
        local resourceStrings = {}
        for resourceString in value:gmatch("([^\n]+)") do
            table.insert(resourceStrings, resourceString)
        end
        return resourceStrings
    end

    local resultTable = {}
    local resourceStrings = splitResourceString(str)

    for _, resourceString in ipairs(resourceStrings) do
        if isResourceString(resourceString) then
            local isInTable = false
            for _, element in ipairs(resultTable) do
                if element == resourceString then
                    isInTable = true
                    break
                end
            end
            if not isInTable then
                table.insert(resultTable, resourceString)
            end
        end
    end

    return next(resultTable) ~= nil and resultTable or nil
end

local function parseSpellTooltip(tooltipTexts)
    local function isEvokerSpellColor(str)
        return str == "Red" or str == "Green" or str == "Blue" or str == "Black" or str == "Bronze"
    end

    local function isAdditionalSpellTips(str)
        return str == "Left click to select this talent."
            or StartsWith(str, "Unlocked at level ")
            or str == "Click to learn"
            or str == "Talents cannot be changed in combat."
    end

    local spellTooltip = {
        Name = tooltipTexts[1],
        Form = tooltipTexts[2] or "",
    }

    local contentIndex = 3

    if (tooltipTexts[3]) then
        local minRank, maxRank = tooltipTexts[3]:match(talentRankPattern)
        if (minRank and maxRank) then
            local talent = { MinRank = tonumber(minRank), MaxRank = tonumber(maxRank), CurrentRank = {} }

            if (talent.MaxRank > 1 and talent.MinRank < talent.MaxRank) then
                talent.NextRankIndex = -1
                talent.NextRank = {}
            end

            spellTooltip.Talent = talent

            contentIndex = contentIndex + 2
        else
            spellTooltip.Spell = {}
        end
    end

    local spellContainer =
        (spellTooltip.Talent and spellTooltip.Talent.CurrentRank) and spellTooltip.Talent.CurrentRank
        or spellTooltip.Spell
        or nil

    if (spellContainer) then
        for i = contentIndex, #tooltipTexts do
            local text = tooltipTexts[i]
            if (text ~= nil or text ~= "") then
                local resourceTypes = processResourceStrings(text)
                if (resourceTypes) then
                    spellContainer.ResourceType = { i }
                    for x, resourceType in ipairs(resourceTypes) do
                        table.insert(spellContainer.ResourceType, x + 1, resourceType)
                    end
                end

                if not resourceTypes then
                    local replacedBy = text:match(talentReplacedByPattern)
                    local replaces = text:match(talentReplacesPattern)
                    if (replaces or replacedBy) then
                        if (replaces) then
                            spellContainer.Replaces = { i, replaces:trim() }
                        else
                            spellContainer.ReplacedBy = { i, replacedBy:trim() }
                        end
                    elseif (text == "Next Rank:") then
                        spellTooltip.Talent.NextRankIndex = i
                        spellContainer = spellTooltip.Talent.NextRank
                    elseif isAdditionalSpellTips(text) then
                        if (not spellContainer.AdditionalSpellTips) then spellContainer.AdditionalSpellTips = {} end
                        table.insert(spellContainer.AdditionalSpellTips, { i, text })
                    elseif (isEvokerSpellColor(text)) then
                        spellContainer.EvokerSpellColor = { i, text }
                    elseif (string.match(text, maxChargesPattern)) then
                        spellContainer.MaxCharges = { i, text }
                    elseif text == "Melee Range" or text == "Unlimited range" or EndsWith(text, "yd range") then
                        spellContainer.Range = { i, text }
                    elseif text == "Instant" or text == "Channeled" or EndsWith(text, "sec cast") or EndsWith(text, "sec empower") then
                        spellContainer.CastTime = { i, text }
                    elseif StartsWith(text, "Requires") then
                        spellContainer.Requires = { i, text }
                    elseif EndsWith(text, "cooldown") or EndsWith(text, "recharge") or StartsWith(text, "Recharging: ") then
                        spellContainer.Cooldown = { i, text }
                    elseif StartsWith(text, "Cooldown remaining:") then
                        spellContainer.CooldownRemaining = { i, text }
                    elseif text == "Passive" then
                        spellContainer.Passive = i
                    elseif text == "Upgrade" then
                        spellContainer.Upgrade = i
                    elseif i % 2 == 1 then
                        if not spellContainer.Descriptions then spellContainer.Descriptions = {} end
                        table.insert(spellContainer.Descriptions, { index = i, value = text })
                    end
                end
            end
        end

        if (not spellContainer.Descriptions) then
            return -- HOOK: Description for PvP talent is empty. In this case client send another callback. Need to find why.
        end
    end

    return spellTooltip
end

local function translateTooltipSpellInfo(spellContainer)
    if (not spellContainer) then return end

    local translatedTooltipLines = {}

    if (spellContainer.ResourceType) then
        for i = 2, #spellContainer.ResourceType do
            spellContainer.ResourceType[i] = GetSpellAttributeOrDefault(spellContainer.ResourceType[i])
        end
        table.insert(translatedTooltipLines, {
            index = spellContainer.ResourceType[1],
            value = table.concat(spellContainer.ResourceType, "\n", 2)
        })
    end

    if (spellContainer.Range) then
        table.insert(translatedTooltipLines, {
            index = spellContainer.Range[1],
            value = GetSpellAttributeOrDefault(spellContainer.Range[2])
        })
    end

    if (spellContainer.Requires) then
        table.insert(translatedTooltipLines, {
            index = spellContainer.Requires[1],
            value = GetSpellAttributeOrDefault(spellContainer.Requires[2])
        })
    end

    if (spellContainer.ReplacedBy) then
        local spellName = GetSpellNameOrDefault(spellContainer.ReplacedBy[2], false)
        table.insert(translatedTooltipLines, {
            index = spellContainer.ReplacedBy[1],
            value = TALENT_REPLACED_BY_TRANSLATION .. " " .. spellName
        })
    end

    if (spellContainer.Replaces) then
        local spellName = GetSpellNameOrDefault(spellContainer.Replaces[2], false)
        table.insert(translatedTooltipLines, {
            index = spellContainer.Replaces[1],
            value = TALENT_REPLACES_TRANSLATION .. " " .. spellName
        })
    end

    if (spellContainer.CastTime) then
        table.insert(translatedTooltipLines, {
            index = spellContainer.CastTime[1],
            value = GetSpellAttributeOrDefault(spellContainer.CastTime[2])
        })
    end

    if (spellContainer.Cooldown) then
        table.insert(translatedTooltipLines, {
            index = spellContainer.Cooldown[1],
            value = GetSpellAttributeOrDefault(spellContainer.Cooldown[2])
        })
    end

    if (spellContainer.CooldownRemaining) then
        table.insert(translatedTooltipLines, {
            index = spellContainer.CooldownRemaining[1],
            value = GetSpellAttributeOrDefault(spellContainer.CooldownRemaining[2])
        })
    end

    if (spellContainer.MaxCharges) then
        table.insert(translatedTooltipLines, {
            index = spellContainer.MaxCharges[1],
            value = GetSpellAttributeOrDefault(spellContainer.MaxCharges[2])
        })
    end

    if (spellContainer.EvokerSpellColor) then
        table.insert(translatedTooltipLines, {
            index = spellContainer.EvokerSpellColor[1],
            value = GetSpellAttributeOrDefault(spellContainer.EvokerSpellColor[2])
        })
    end

    if (spellContainer.Passive) then
        table.insert(translatedTooltipLines, {
            index = spellContainer.Passive,
            value = SPELL_PASSIVE_TRANSLATION
        })
    end

    if (spellContainer.Upgrade) then
        table.insert(translatedTooltipLines, {
            index = spellContainer.Upgrade,
            value = TALENT_UPGRADE_TRANSLATION
        })
    end

    if (spellContainer.AdditionalSpellTips) then
        for _, spellTip in ipairs(spellContainer.AdditionalSpellTips) do
            table.insert(translatedTooltipLines, {
                index = spellTip[1],
                value = GetAdditionalSpellTipsOrDefault(spellTip[2])
            })
        end
    end

    if (spellContainer.Descriptions) then
        for _, description in ipairs(spellContainer.Descriptions) do
            local value = description.value:trim()
            if (value ~= "") then
                table.insert(translatedTooltipLines, {
                    index = description.index,
                    value = GetSpellDescriptionOrDefault(value, settingsProvider.IsNeedHighlightSpellNameInDescription()),
                    originalValue = value,
                    tag = "Description"
                })
            end
        end
    end

    return translatedTooltipLines
end

local function addUntranslatedSpellToDataStorage(spellId, translatedTooltipLines)
    local function findUntranslatedDescriptions(tooltipLines)
        local results = {}
        for _, obj in ipairs(tooltipLines) do
            if obj.tag == "Description" and StringsAreEqual(obj.value, obj.originalValue, true) then
                table.insert(results, obj.originalValue)
            end
        end
        return results
    end

    local _, build = GetBuildInfo()

    local originalName
    if translatedTooltipLines[1] then
        originalName = translatedTooltipLines[1].originalValue
    end

    if (originalName == nil) then return end

    local untranslatedDescriptions = findUntranslatedDescriptions(translatedTooltipLines)
    local untranslatedName = translatedTooltipLines[1].value == originalName and originalName or ""

    if (#untranslatedDescriptions == 0 and untranslatedName == "") then return end

    local className = UnitClass("player")
    local specializationId, specializationName = GetSpecializationInfo(GetSpecialization())

    if (not _G.WowUkrainizerData) then _G.WowUkrainizerData = {} end
    if (not _G.WowUkrainizerData.UntranslatedData) then _G.WowUkrainizerData.UntranslatedData = {} end
    if (not _G.WowUkrainizerData.UntranslatedData.Spells) then _G.WowUkrainizerData.UntranslatedData.Spells = {} end

    local untranslatedSpells = _G.WowUkrainizerData.UntranslatedData.Spells

    if (not untranslatedSpells[className]) then untranslatedSpells[className] = {} end
    if (not untranslatedSpells[className][specializationId]) then
        untranslatedSpells[className][specializationId] = { Name = specializationName, Values = {} }
    end

    if (not untranslatedSpells[className][specializationId].Values[originalName]) then
        untranslatedSpells[className][specializationId].Values[originalName] = {}
    end

    local untranslatedSpell = untranslatedSpells[className][specializationId].Values[originalName]
    untranslatedSpell.UntranslatedName = untranslatedName ~= ""

    if not untranslatedSpell[spellId] then untranslatedSpell[spellId] = {} end

    local spellInfo = untranslatedSpell[spellId]
    for _, desc in ipairs(untranslatedDescriptions) do
        local formattedDesc, _ = NormalizeStringAndExtractNumerics(desc)
        local isValueInTable = IsValueInTable(spellInfo, formattedDesc, "desc")
        if (not isValueInTable) then
            table.insert(spellInfo, { build = build, desc = formattedDesc })
        end
    end
end

function translator:ParseTooltip(tooltip, tooltipData)
    local linePrefix = "GameTooltip";
    if (tooltip == _G["ElvUI_SpellBookTooltip"]) then linePrefix = 'ElvUI_SpellBookTooltip' end

    self._postCallLineCount = tonumber(tooltip:NumLines())

    local tooltipTexts = {}
    for i = 1, tooltip:NumLines() do
        local lineLeft = _G[linePrefix .. "TextLeft" .. i]
        if (lineLeft) then
            local lli = #tooltipTexts + 1;
            tooltipTexts[lli] = lineLeft:GetText() or ''
            self:_addFontStringToIndexLookup(lli, lineLeft)
        end

        local lineRight = _G[linePrefix .. "TextRight" .. i]
        if (lineRight) then
            local lri = #tooltipTexts + 1;
            tooltipTexts[lri] = lineRight:GetText() or ''
            self:_addFontStringToIndexLookup(lri, lineRight)
        end
    end

    local tooltipInfo = parseSpellTooltip(tooltipTexts)

    if (not tooltipInfo) then return end

    -- HOOK: No Rank 1/2+ info in multirang talent tooltip. In this case client send another callback. Need to find why
    if (tooltipInfo and tooltipInfo.Talent and (tooltipInfo.Talent.MinRank ~= 0 and tooltipInfo.Talent.NextRankIndex == -1)) then return end

    tooltipInfo.SpellId = tonumber(tooltipData.id)

    return tooltipInfo
end

function translator:TranslateTooltipInfo(tooltipInfo)
    local function addRange(t1, t2)
        if (not t2) then return end
        for _, value in ipairs(t2) do
            table.insert(t1, value)
        end
    end

    local translatedTooltipLines = {}

    local spellNameLang = settingsProvider.GetTooltipSpellLangInName()
    if (spellNameLang ~= "en") then
        local spellName = tooltipInfo.Name
        local translatedValue = GetSpellNameOrDefault(tooltipInfo.Name, true)

        if (spellName ~= translatedValue) then
            spellName = spellNameLang == "ua" and translatedValue or
                "|cFF47D5FF" .. spellName .. "|r\n" .. translatedValue
        end

        table.insert(translatedTooltipLines, {
            index = 1,
            value = spellName,
            originalValue = tooltipInfo.Name,
            tag = "Name"
        })
    end

    if (settingsProvider.IsNeedTranslateSpellDescriptionInTooltip()) then
        if (tooltipInfo.Form and tooltipInfo.Form ~= "") then
            table.insert(translatedTooltipLines, {
                index = 2,
                value = GetSpellAttributeOrDefault(tooltipInfo.Form)
            })
        end

        if (tooltipInfo.Spell) then
            addRange(translatedTooltipLines, translateTooltipSpellInfo(tooltipInfo.Spell))
        elseif (tooltipInfo.Talent) then
            table.insert(translatedTooltipLines, {
                index = 3,
                value = SPELL_RANK_TRANSLATION .. " " .. tooltipInfo.Talent.MinRank .. "/" .. tooltipInfo.Talent.MaxRank
            })
            addRange(translatedTooltipLines, translateTooltipSpellInfo(tooltipInfo.Talent.CurrentRank))

            if (tooltipInfo.Talent.NextRankIndex ~= -1) then
                table.insert(translatedTooltipLines, {
                    index = tooltipInfo.Talent.NextRankIndex,
                    value = SPELL_NEXT_RANK_TRANSLATION
                })
                addRange(translatedTooltipLines, translateTooltipSpellInfo(tooltipInfo.Talent.NextRank))
            end
        end
    end

    addUntranslatedSpellToDataStorage(tooltipInfo.SpellId, translatedTooltipLines)

    return translatedTooltipLines
end
