local ns = _G.WoWTranslate_UK_NS

local translator = setmetatable({}, { __index = ns.BaseTranslator })
translator.IsEnabled = translator.IsEnabled or function() return true end
ns.TranslationsManager:Add(translator)

local function pack(...)
    return { n = select("#", ...), ... }
end

local function schedule(delay, fn)
    if C_Timer and C_Timer.After then
        C_Timer.After(delay or 0, fn)
    else
        fn()
    end
end

local function resolvePlaceholders(text)
    if type(text) ~= "string" or text == "" then return text end
    local fn = ns.resolvePlaceholders
    if type(fn) == "function" then
        local ok, out = pcall(fn, text)
        if ok and type(out) == "string" and out ~= "" then return out end
    end
    return text
end

local EXACT_OVERRIDES = {
    ["Available Quests"] = "Доступні квести",
    ["Quests"] = "Квести",
    ["Campaign"] = "Кампанія",
    ["Rewards"] = "Нагороди",
    ["Experience"] = "Досвід",
    ["Completed"] = "Виконано",
    ["Goodbye"] = "Вийти",
    ["Accept"] = "Прийняти",
    ["Decline"] = "Відмовитися",
    ["Continue"] = "Продовжити",
    ["Complete Quest"] = "Готово",
    ["Quest Objectives"] = "Цілі завдання",
    ["Description"] = "Опис",
    ["Objectives"] = "Цілі",
    ["Quest Rewards"] = "Нагороди за завдання",
    ["You will receive:"] = "Ви отримаєте:",
    ["Choose your reward:"] = "Оберіть нагороду:",
    ["You will also receive:"] = "Ви також отримаєте:",
    ["You will learn:"] = "Ви вивчите:",
    ["Spell:"] = "Закляття:",
    ["Back"] = "Назад",
    ["Cancel"] = "Скасувати",
}

local function applyCyrillicFont(fs, sampleText)
    if InCombatLockdown and InCombatLockdown() then return end
    if not fs or not fs.SetFont then return end
    if not sampleText or type(sampleText) ~= "string" or sampleText == "" then return end
    local hasCyr = type(ns.hasCyrillic) == "function" and ns.hasCyrillic(sampleText)
    if not hasCyr then return end
    local needs = type(ns.needsCyrillicFont) == "function"
        and select(1, pcall(ns.needsCyrillicFont, select(1, pcall(fs.GetFont, fs))))
    if not needs then return end
    local ok, fontFile, height, flags = pcall(fs.GetFont, fs)
    if not ok or not fontFile then return end
    local chosen = (type(ns.selectFontForContext) == "function" and ns.selectFontForContext(fontFile)) or ns.PreferredFont
    if not chosen then return end
    pcall(fs.SetFont, fs, chosen, height or 13, flags or "")
end

local function cleanColor(text)
    if type(text) ~= "string" then return text end
    text = text:gsub("|c%x%x%x%x%x%x%x%x", "")
    text = text:gsub("|r", "")
    return text
end

local _prefixLookup = nil

local function buildPrefixLookup()
    if _prefixLookup then return _prefixLookup end
    if not ns.DB or not ns.DB.Lookup then return nil end
    _prefixLookup = {}
    for key, val in pairs(ns.DB.Lookup) do
        if type(key) == "string" and #key >= 30 then
            _prefixLookup[#_prefixLookup + 1] = { key, val }
        end
    end
    table.sort(_prefixLookup, function(a, b) return #a[1] > #b[1] end)
    if #_prefixLookup > 500 then
        local trimmed = {}
        for i = 1, 500 do trimmed[i] = _prefixLookup[i] end
        _prefixLookup = trimmed
    end
    return _prefixLookup
end

local function translateFontString(fs)
    if not fs or fs._wt_updating then return end
    if ns.isProtectedFrame and ns.isProtectedFrame(fs) then return end
    if not fs.GetText or not fs.SetText then return end

    local ok, raw = pcall(fs.GetText, fs)
    if not ok or type(raw) ~= "string" or raw == "" then return end

    if fs._wt_tr == raw then return end
    if fs._wt_orig == raw and fs._wt_tr then
        local parent = fs:GetParent()
        if parent and parent.SetText and not parent._wt_updating then
            parent._wt_updating = true
            pcall(parent.SetText, parent, fs._wt_tr)
            parent._wt_updating = nil
        end
        return
    end

    local text = cleanColor(raw)

    local tr = EXACT_OVERRIDES[text]
    if not tr and type(ns.GetTranslation) == "function" then
        tr = ns.GetTranslation(text)
    end

    if not tr and #text >= 30 then
        local prefixes = buildPrefixLookup()
        if prefixes then
            for i = 1, #prefixes do
                local key, val = prefixes[i][1], prefixes[i][2]
                if text:find(key, 1, true) then
                    tr = text:gsub(key:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1"), val)
                    break
                end
            end
        end
    end

    if not tr or tr == text then return end

    tr = resolvePlaceholders(tr)

    local parent = fs:GetParent()
    if parent and parent.SetText and not parent._wt_updating then
        parent._wt_updating = true
        fs._wt_updating = true
        pcall(parent.SetText, parent, tr)
        fs._wt_updating = nil
        parent._wt_updating = nil
    else
        fs._wt_updating = true
        pcall(fs.SetText, fs, tr)
        fs._wt_updating = nil
    end

    fs._wt_orig = raw
    fs._wt_tr = tr
    applyCyrillicFont(fs, tr)
end

local function safeRegions(frame)
    if not frame or not frame.GetRegions then return nil end
    local ok, result = pcall(function()
        return pack(frame:GetRegions())
    end)
    if ok then return result end
    return nil
end

local function safeChildren(frame)
    if not frame or not frame.GetChildren then return nil end
    local ok, result = pcall(function()
        return pack(frame:GetChildren())
    end)
    if ok then return result end
    return nil
end

local function scanFrame(frame, depth)
    depth = depth or 0
    if not frame or depth > 5 then return end

    local directFields = {
        "GreetingText", "Title", "Text", "Name", "SubTitle",
        "DescriptionText", "ObjectivesText", "RewardText",
        "Header", "ReceiveText", "ValueText", "QuestText",
    }

    for _, key in ipairs(directFields) do
        local fs = frame[key]
        if fs and fs.GetText and fs.SetText then
            translateFontString(fs)
        end
    end

    local regions = safeRegions(frame)
    if regions then
        for i = 1, regions.n do
            local r = regions[i]
            if r and r.GetObjectType and r:GetObjectType() == "FontString" then
                translateFontString(r)
            end
        end
    end

    local children = safeChildren(frame)
    if children then
        for i = 1, children.n do
            scanFrame(children[i], depth + 1)
        end
    end
end

local function currentQuestID()
    if C_QuestLog and C_QuestLog.GetSelectedQuest then
        local qid = C_QuestLog.GetSelectedQuest()
        if qid and qid > 0 then return qid end
    end
    if GetQuestID then
        local qid = GetQuestID()
        if qid and qid > 0 then return qid end
    end
    return nil
end

local function setText(obj, text)
    if not obj or not obj.SetText then return end
    if type(text) ~= "string" or text == "" then return end
    if InCombatLockdown and InCombatLockdown() then return end
    text = resolvePlaceholders(text)
    text = text:gsub("{number}", "#")
    text = text:gsub("{pcf}", "")
    obj._wt_updating = true
    pcall(obj.SetText, obj, text)
    obj._wt_updating = nil
end

local function applyQuestByID()
    local questID = currentQuestID()
    if not questID or not ns.DB or not ns.DB.QuestsByID then return end
    local data = ns.DB.QuestsByID[questID]
    if not data then return end

    setText(QuestInfoTitleHeader, data.title)
    if data.quest_description and data.quest_description ~= "" then
        setText(QuestInfoDescriptionText, data.quest_description)
    elseif data.log_description and data.log_description ~= "" then
        setText(QuestInfoDescriptionText, data.log_description)
    end
    if data.objective_texts and data.objective_texts ~= "" then
        setText(QuestInfoObjectivesText, data.objective_texts)
    end
    if data.reward_text and data.reward_text ~= "" then
        setText(QuestInfoRewardText, data.reward_text)
    end
    if data.progress_text and data.progress_text ~= "" then
        setText(QuestProgressText, data.progress_text)
    end

    setText(QuestLogPopupDetailFrameTitle, data.title)
    if data.quest_description and data.quest_description ~= "" then
        setText(QuestLogPopupDetailFrameDesc, data.quest_description)
    elseif data.log_description and data.log_description ~= "" then
        setText(QuestLogPopupDetailFrameDesc, data.log_description)
    end

    if QuestMapDetailsScrollChildFrame then
        setText(QuestMapDetailsScrollChildFrame.TitleHeader, data.title)
        if data.quest_description and data.quest_description ~= "" then
            setText(QuestMapDetailsScrollChildFrame.DescriptionText, data.quest_description)
        end
        if data.objective_texts and data.objective_texts ~= "" then
            setText(QuestMapDetailsScrollChildFrame.ObjectivesText, data.objective_texts)
        end
    end
end

local function translateQuestFrameButtons()
    pcall(function()
        local map = {
            { _G.QuestFrameAcceptButton, "Accept", "Прийняти" },
            { _G.QuestFrameDeclineButton, "Decline", "Відмовитися" },
            { _G.QuestFrameCompleteButton, "Continue", "Продовжити" },
            { _G.QuestFrameCompleteQuestButton, "Complete Quest", "Готово" },
            { _G.QuestFrameGoodbyeButton, "Goodbye", "Вийти" },
            { _G.QuestFrameGreetingGoodbyeButton, "Goodbye", "Вийти" },
        }

        for _, item in ipairs(map) do
            local btn, eng, uk = item[1], item[2], item[3]
            if btn and btn.GetFontString then
                local ok, fs = pcall(btn.GetFontString, btn)
                if ok and fs then
                    local ok2, cur = pcall(fs.GetText, fs)
                    if ok2 and cur and cur:find(eng) then
                        fs._wt_updating = true
                        pcall(fs.SetText, fs, uk)
                        fs._wt_updating = nil
                        applyCyrillicFont(fs, uk)
                    end
                end
            end
        end

        if GossipFrame and GossipFrame.GreetingPanel then
            local gb = GossipFrame.GreetingPanel.GoodbyeButton
            if gb and gb.GetFontString then
                local ok, fs = pcall(gb.GetFontString, gb)
                if ok and fs then
                    fs._wt_updating = true
                    pcall(fs.SetText, fs, "Вийти")
                    fs._wt_updating = nil
                    applyCyrillicFont(fs, "Вийти")
                end
            end
        end
    end)
end

local function translateQuestRewards()
    local f = QuestInfoRewardsFrame
    if not f then return end
    scanFrame(f, 0)
    if f.RewardButtons then
        for _, btn in ipairs(f.RewardButtons) do
            if btn then
                scanFrame(btn, 0)
                if btn.Name then translateFontString(btn.Name) end
            end
        end
    end
end

local function translateGossipPanel()
    local panel = GossipFrame and GossipFrame.GreetingPanel
    if not panel then return end

    scanFrame(panel, 0)
    if panel.GreetingText then translateFontString(panel.GreetingText) end
    if panel.Text then translateFontString(panel.Text) end

    local children = safeChildren(panel)
    if children then
        for i = 1, children.n do
            local child = children[i]
            if child then
                local ok_fs, fs = pcall(child.GetFontString, child)
                if ok_fs and fs then translateFontString(fs) end
                for _, key in ipairs({"Text","Name","Title","Label"}) do
                    if child[key] and child[key].GetText and child[key].SetText then
                        translateFontString(child[key])
                    end
                end
            end
        end
    end

    if panel.ScrollBox and type(panel.ScrollBox.GetFrames) == "function" then
        local ok, frames = pcall(panel.ScrollBox.GetFrames, panel.ScrollBox)
        if ok and frames then
            for _, f in ipairs(frames) do
                scanFrame(f, 0)
            end
        end
    end
end

local function translateQuestGreetingPanel()
    local panel = QuestFrameGreetingPanel
    if not panel then return end

    pcall(function()
        scanFrame(panel, 0)
        if panel.GreetingText then translateFontString(panel.GreetingText) end
        if panel.Title then translateFontString(panel.Title) end
        if panel.Text then translateFontString(panel.Text) end

        local children = safeChildren(panel)
        if children then
            for i = 1, children.n do
                local child = children[i]
                if child then
                    local ok_fs, fs = pcall(child.GetFontString, child)
                    if ok_fs and fs then translateFontString(fs) end
                    for _, key in ipairs({"Text","Name","Title","GreetingText","Label"}) do
                        if child[key] and child[key].GetText and child[key].SetText then
                            translateFontString(child[key])
                        end
                    end
                end
            end
        end

        local gb = _G.QuestFrameGreetingGoodbyeButton
        if gb then
            local ok_fs, fs = pcall(gb.GetFontString, gb)
            if ok_fs and fs then
                fs._wt_updating = true
                pcall(fs.SetText, fs, "Вийти")
                fs._wt_updating = nil
                applyCyrillicFont(fs, "Вийти")
            end
        end

        local scroll = _G.QuestGreetingScrollFrame
        if scroll then scanFrame(scroll, 0) end
    end)
end

local function translateQuestInfo()
    local roots = {
        QuestInfoTitleHeader, QuestInfoDescriptionText, QuestInfoObjectivesText,
        QuestInfoRewardText, QuestProgressText, QuestInfoDescriptionHeader,
        QuestInfoObjectivesHeader, QuestInfoRewardHeader, QuestFrameRewardText,
        QuestFrameCompleteText, QuestInfoPlayerTitleText, QuestInfoItemChooseText,
        QuestInfoItemReceiveText, QuestInfoSpellLabel, QuestInfoSpellLearnLabel,
        QuestInfoRewardsHeader,
    }
    for _, fs in ipairs(roots) do
        translateFontString(fs)
    end
    if QuestInfoXPFrame then
        translateFontString(QuestInfoXPFrame.ReceiveText)
        translateFontString(QuestInfoXPFrame.ValueText)
    end
    if QuestInfoRewardsFrame then translateQuestRewards() end
    if QuestInfoReputationFrame then
        translateFontString(QuestInfoReputationFrame.RewardText)
    end
end

local function translateQuestMap()
    if QuestMapFrame then
        scanFrame(QuestMapFrame, 0)
        if QuestMapFrame.DetailsFrame then scanFrame(QuestMapFrame.DetailsFrame, 0) end
        if QuestMapFrame.CampaignOverview then scanFrame(QuestMapFrame.CampaignOverview, 0) end
    end
end

local _questUIPending = false

local function queueQuestUI()
    if _questUIPending then return end
    _questUIPending = true
    schedule(0, function()
        _questUIPending = false
        applyQuestByID()
        translateQuestInfo()
        translateQuestGreetingPanel()
        translateGossipPanel()
        translateQuestFrameButtons()
    end)
end

function translator:Init()
    pcall(hooksecurefunc, "QuestInfo_SetText", function(fontString, text)
        if not fontString or type(text) ~= "string" or text == "" then return end
        if fontString._wt_updating then return end
        if InCombatLockdown and InCombatLockdown() then return end
        if ns.isProtectedFrame and ns.isProtectedFrame(fontString) then return end

        local override = EXACT_OVERRIDES[text]
        local tr = override or (type(ns.GetTranslation) == "function" and ns.GetTranslation(text) or nil)
        if not tr or tr == text then
            if fontString._wt_orig == text and fontString._wt_tr then
                fontString._wt_updating = true
                pcall(fontString.SetText, fontString, fontString._wt_tr)
                fontString._wt_updating = nil
            end
            return
        end

        tr = resolvePlaceholders(tr)
        fontString._wt_updating = true
        pcall(fontString.SetText, fontString, tr)
        fontString._wt_updating = nil
        fontString._wt_orig = text
        fontString._wt_tr = tr
        applyCyrillicFont(fontString, tr)
    end)

    pcall(hooksecurefunc, "QuestInfo_ShowTitle", function()
        queueQuestUI()
    end)

    pcall(hooksecurefunc, "QuestInfo_ShowDescriptionText", function()
        queueQuestUI()
    end)

    pcall(hooksecurefunc, "QuestInfo_ShowRewardText", function()
        queueQuestUI()
    end)

    pcall(hooksecurefunc, "QuestInfo_Display", function()
        queueQuestUI()
    end)

    pcall(hooksecurefunc, "QuestInfo_ShowRewards", function()
        queueQuestUI()
    end)

    pcall(function()
        if QuestFrame then
            QuestFrame:HookScript("OnShow", function()
                queueQuestUI()
            end)
        end
    end)

    pcall(function()
        if GossipFrame then
            GossipFrame:HookScript("OnShow", function()
                queueQuestUI()
            end)
        end
    end)

    pcall(function()
        if GossipFrame and GossipFrame.GreetingPanel and GossipFrame.GreetingPanel.ScrollBox then
            ScrollUtil.AddInitializedFrameCallback(
                GossipFrame.GreetingPanel.ScrollBox,
                function(_, frame)
                    scanFrame(frame, 0)
                end
            )
        end
    end)

    pcall(function()
        if QuestFrameGreetingPanel and QuestFrameGreetingPanel.ScrollBox then
            ScrollUtil.AddInitializedFrameCallback(
                QuestFrameGreetingPanel.ScrollBox,
                function(_, frame)
                    scanFrame(frame, 0)
                end
            )
        end
    end)

    pcall(function()
        if EventRegistry then
            EventRegistry:RegisterCallback("GossipFrame.Update", function()
                queueQuestUI()
            end, "WoWTranslateUK_Gossip")
        end
    end)

    local eventFrame = CreateFrame("Frame")
    eventFrame:RegisterEvent("GOSSIP_SHOW")
    eventFrame:RegisterEvent("QUEST_GREETING")
    eventFrame:RegisterEvent("QUEST_DETAIL")
    eventFrame:RegisterEvent("QUEST_PROGRESS")
    eventFrame:RegisterEvent("QUEST_COMPLETE")
    eventFrame:RegisterEvent("QUEST_LOG_UPDATE")
    eventFrame:RegisterEvent("CAMPAIGN_UPDATE")
    eventFrame:SetScript("OnEvent", function(_, event)
        queueQuestUI()
    end)

    local qmFrame = CreateFrame("Frame")
    qmFrame:RegisterEvent("QUEST_LOG_UPDATE")
    qmFrame:RegisterEvent("CAMPAIGN_UPDATE")
    qmFrame:SetScript("OnEvent", function()
        schedule(0.1, translateQuestMap)
    end)
end
