---@class QuestieLink
local QuestieLink = QuestieLoader:CreateModule("QuestieLink")
-------------------------
--Import modules
-------------------------
---@type QuestieDB
local QuestieDB = QuestieLoader:ImportModule("QuestieDB")
---@type QuestieLib
local QuestieLib = QuestieLoader:ImportModule("QuestieLib")
---@type QuestiePlayer
local QuestiePlayer = QuestieLoader:ImportModule("QuestiePlayer")
---@type TrackerUtils
local TrackerUtils = QuestieLoader:ImportModule("TrackerUtils")
---@type QuestieEvent
local QuestieEvent = QuestieLoader:ImportModule("QuestieEvent")
---@type l10n
local l10n = QuestieLoader:ImportModule("l10n")
---@type ZoneDB
local ZoneDB = QuestieLoader:ImportModule("ZoneDB")

QuestieLink.lastItemRefTooltip = ""

-- Forward declaration
local _AddQuestTitle, _AddQuestStatus, _AddQuestDescription, _AddQuestRequirements, _AddDungeonInfo, _GetQuestStarter, _GetQuestFinisher, _AddPlayerQuestProgress
local _AddTooltipLine, _AddColoredTooltipLine


local oldItemSetHyperlink = ItemRefTooltip.SetHyperlink
--- Override of the default SetHyperlink function to filter Questie links
---@param link string
function ItemRefTooltip:SetHyperlink(link, ...)
    local questiePrefix, questId = string.match(link, "(questie):(%d+):")
    local isQuestieLink = questiePrefix == "questie"

    if (not ItemRefTooltip:IsShown()) then
        QuestieLink.lastItemRefTooltip = ""
    else
        QuestieLink.lastItemRefTooltip = QuestieLink.lastItemRefTooltip or link
    end

    if isQuestieLink and questId then
        Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTooltips:ItemRefTooltip] SetHyperlink:", link)
        ShowUIPanel(ItemRefTooltip)
        ItemRefTooltip:SetOwner(UIParent, "ANCHOR_PRESERVE");
        QuestieLink:CreateQuestTooltip(link)
        ItemRefTooltip:Show()

        local tooltipText = ItemRefTooltipTextLeft1:GetText()
        if QuestieLink.lastItemRefTooltip == tooltipText then
            ItemRefTooltip:Hide()
            QuestieLink.lastItemRefTooltip = ""
            return
        end

        QuestieLink.lastItemRefTooltip = tooltipText
        return
    else
        -- Make sure to call the default function so everything that is not Questie can be handled (item links e.g.)
        oldItemSetHyperlink(self, link, ...)
    end
end

---@return string
function QuestieLink:GetQuestLinkStringById(questId)
    local questName = QuestieDB.QueryQuestSingle(questId, "name");
    local questLevel, _ = QuestieLib.GetTbcLevel(questId);
    return QuestieLink:GetQuestLinkString(questLevel, questName, questId)
end

---@return string
function QuestieLink:GetQuestLinkString(questLevel, questName, questId)
    return "[["..tostring(questLevel).."] "..questName.." ("..tostring(questId)..")]"
end

---@return string
function QuestieLink:GetQuestHyperLink(questId, senderGUID)
    local coloredQuestName = QuestieLib:GetColoredQuestName(questId, Questie.db.profile.trackerShowQuestLevel, true)
    local questLevel, _ = QuestieLib.GetTbcLevel(questId)
    local isRepeatable = QuestieDB.IsRepeatable(questId)

    if (not senderGUID) then
        senderGUID = UnitGUID("player")
    end

    return "|Hquestie:"..questId..":"..senderGUID.."|h"..QuestieLib:PrintDifficultyColor(questLevel, "[", isRepeatable, QuestieEvent.IsEventQuest(questId), QuestieDB.IsPvPQuest(questId))..coloredQuestName..QuestieLib:PrintDifficultyColor(questLevel, "]", isRepeatable, QuestieEvent.IsEventQuest(questId), QuestieDB.IsPvPQuest(questId)).."|h"
end

---@param link string
function QuestieLink:CreateQuestTooltip(link)
    local isQuestieLink, _, _ = string.match(link, "questie:(%d+):.*")
    if isQuestieLink then
        ---@type string
        local questIdStr = select(2, strsplit(":", link))
        local questId = tonumber(questIdStr)
        local quest = QuestieDB.GetQuest(questId)

        if quest then
            _AddQuestTitle(quest)
            _AddQuestStatus(quest)

            _AddTooltipLine(" ")

            _AddQuestDescription(quest)
            _AddDungeonInfo(quest)
            _AddQuestRequirements(quest)
            local starterName, starterZoneName = _GetQuestStarter(quest)
            local finisherName, finisherZoneName = _GetQuestFinisher(quest)
            _AddPlayerQuestProgress(quest, starterName, starterZoneName, finisherName, finisherZoneName)
        end
    end
end

---@param text string
---@param wrapText boolean?
_AddTooltipLine = function(text, wrapText)
    ItemRefTooltip:AddLine(text, 1, 1, 1, wrapText)
end

---@param text string
---@param color string
---@param wrapText boolean?
_AddColoredTooltipLine = function(text, color, wrapText)
    text = Questie:Colorize(text, color)
    ItemRefTooltip:AddLine(text, 1, 1, 1, wrapText)
end

---@param quest Quest
_AddQuestTitle = function(quest)
    local questLevel = QuestieLib:GetLevelString(quest.Id, quest.level)

    local titleColor = string.sub(QuestieLib:PrintDifficultyColor(quest.level, "", QuestieDB.IsRepeatable(quest.Id), QuestieEvent.IsEventQuest(quest.Id), QuestieDB.IsPvPQuest(quest.Id)),5,10)

    if Questie.db.profile.trackerShowQuestLevel and Questie.db.profile.enableTooltipsQuestID then
        _AddColoredTooltipLine(questLevel .. quest.name .. " (" .. quest.Id .. ")", titleColor)
    elseif Questie.db.profile.trackerShowQuestLevel and (not Questie.db.profile.enableTooltipsQuestID) then
        _AddColoredTooltipLine(questLevel .. quest.name, titleColor)
    elseif Questie.db.profile.enableTooltipsQuestID and (not Questie.db.profile.trackerShowQuestLevel) then
        _AddColoredTooltipLine(quest.name .. " (" .. quest.Id .. ")", titleColor)
    else
        _AddColoredTooltipLine(quest.name, titleColor)
    end
end

---@param quest Quest
_AddQuestStatus = function(quest)
    if QuestiePlayer.currentQuestlog[quest.Id] then
        local onQuestText = l10n("You are on this quest")
        local stateText
        local questIsComplete = QuestieDB.IsComplete(quest.Id)
        if questIsComplete == 1 then
            stateText = Questie:Colorize(l10n("Complete"), "green")
        elseif questIsComplete == -1 then
            stateText = Questie:Colorize(l10n("Failed"), "red")
        end

        if stateText then
            _AddTooltipLine(onQuestText .. " (" .. stateText .. ")")
        else
            _AddColoredTooltipLine(onQuestText, "green")
        end
    elseif Questie.db.char.complete[quest.Id] then
        _AddColoredTooltipLine(l10n("You have completed this quest"), "green")
    elseif (UnitLevel("player") < quest.requiredLevel or (not QuestieDB.IsDoable(quest.Id))) and (not Questie.db.char.hidden[quest.Id]) then
        _AddColoredTooltipLine(l10n("You are ineligible for this quest"), "red")
    elseif quest.specialFlags == 1 then
        _AddColoredTooltipLine(l10n("This quest is repeatable"), "yellow")
    else
        _AddColoredTooltipLine(l10n("You have not done this quest"), "yellow")
    end
end

---@param quest Quest
_AddQuestDescription = function(quest)
    local description = quest.Description
    if description and description[1] then
        _AddColoredTooltipLine(description[1], "white", true)
        if #description > 2 then
            for i = 2, #description do
                --_AddTooltipLine(" ") -- this is just adding extra lines between text definitions in DB files
                _AddColoredTooltipLine(description[i], "white", true)
            end
        end
    else
        _AddColoredTooltipLine(l10n("This quest is an automatic completion quest and does not contain an objective."), "white", true)
    end
end

-- helper function to format a label with a colon, respecting localization rules
---@param label string
---@return string
local function FormatLabelWithColon(label)
    local locale = GetLocale()
    if locale == "frFR" then
        return label .. " :"
    else
        return label .. ":"
    end
end

---@param quest Quest
_AddDungeonInfo = function(quest)
    local zoneOrSort = quest.zoneOrSort
    if zoneOrSort and zoneOrSort > 0 then
        local localizedDungeonName = ZoneDB:GetLocalizedDungeonName(zoneOrSort)
        if localizedDungeonName then
            _AddTooltipLine(" ")
            _AddColoredTooltipLine(FormatLabelWithColon(l10n("Dungeon")) .. " " .. localizedDungeonName, "gray")
        end
    end
end

---@param quest Quest
_AddQuestRequirements = function(quest)
    if #quest.ObjectiveData > 0 and not (QuestiePlayer.currentQuestlog[quest.Id] or Questie.db.char.complete[quest.Id]) then
        for i = 1, #quest.ObjectiveData do
            local currentObjective = quest.ObjectiveData[i]
            if currentObjective then
                if currentObjective.Text then
                    if currentObjective == quest.ObjectiveData[1] then
                        _AddTooltipLine(" ")
                        _AddColoredTooltipLine(l10n("Objectives"), "gold")
                    end
                    _AddColoredTooltipLine(currentObjective.Text, "white")
                else
                    local objectiveName
                    if currentObjective.Type == "monster" then
                        objectiveName = QuestieDB.QueryNPCSingle(currentObjective.Id, "name")
                    else
                        objectiveName = QuestieDB.QueryItemSingle(currentObjective.Id, "name")
                    end

                    if objectiveName then
                        if currentObjective == quest.ObjectiveData[1] then
                            _AddTooltipLine(" ")
                            _AddColoredTooltipLine(l10n("Objectives"), "gold")
                        end
                        _AddColoredTooltipLine(objectiveName, "white")
                    end
                end
            end
        end
    end
end

---@param quest Quest
---@return string?, string?
_GetQuestStarter = function(quest)
    if quest.Starts then
        local starterName, starterZoneName
        if quest.Starts.NPC ~= nil then
            local npc = QuestieDB:GetNPC(quest.Starts.NPC[1])
            starterName = npc.name

            if npc.zoneID ~= 0 then
                starterZoneName = TrackerUtils:GetZoneNameByID(npc.zoneID)
            else
                starterZoneName = TrackerUtils:GetZoneNameByID(quest.zoneOrSort)
            end
        elseif quest.Starts.Item ~= nil then
            local item = QuestieDB:GetItem(quest.Starts.Item[1])
            starterName = item.name

            if item.Sources and item.Sources[1] and item.Sources[1].Type then
                local itemSource = item.Sources[1]
                local dropStart

                if itemSource.Type == "monster" then
                    dropStart = QuestieDB:GetNPC(itemSource.Id)
                elseif itemSource.Type == "object" then
                    dropStart = QuestieDB:GetObject(itemSource.Id)
                end

                if item.zoneID ~= 0 then
                    starterZoneName = TrackerUtils:GetZoneNameByID(dropStart.zoneID)
                else
                    starterZoneName = TrackerUtils:GetZoneNameByID(quest.zoneOrSort)
                end
            else
                starterZoneName = TrackerUtils:GetZoneNameByID(quest.zoneOrSort)
            end
        elseif quest and quest.Starts and quest.Starts.GameObject and quest.Starts.GameObject[1] then
            local object = QuestieDB:GetObject(quest.Starts.GameObject[1])
            starterName = object.name
            if object.zoneID ~= 0 then
                starterZoneName = TrackerUtils:GetZoneNameByID(object.zoneID)
            else
                starterZoneName = TrackerUtils:GetZoneNameByID(quest.zoneOrSort)
            end
        else
            starterZoneName = TrackerUtils:GetZoneNameByID(quest.zoneOrSort)
        end

        return starterName, l10n(starterZoneName)
    end

    return nil, nil
end

---@param quest Quest
---@return string?, string?
_GetQuestFinisher = function(quest)
    local finisherName, finisherZoneName
    if quest.Finisher.NPC then
        local npc = QuestieDB:GetNPC(quest.Finisher.NPC[1])
        finisherName = npc.name

        if npc.zoneID ~= 0 then
            finisherZoneName = TrackerUtils:GetZoneNameByID(npc.zoneID)
        else
            finisherZoneName = TrackerUtils:GetZoneNameByID(quest.zoneOrSort)
        end
    elseif quest.Finisher.GameObject then
        local object = QuestieDB:GetObject(quest.Finisher.GameObject[1])
        finisherName = object.name
        if object.zoneID ~= 0 then
            finisherZoneName = TrackerUtils:GetZoneNameByID(object.zoneID)
        else
            finisherZoneName = TrackerUtils:GetZoneNameByID(quest.zoneOrSort)
        end
    else
        finisherZoneName = TrackerUtils:GetZoneNameByID(quest.zoneOrSort)
    end

    return finisherName, l10n(finisherZoneName)
end

---@param quest Quest
---@param starterName string?
---@param starterZoneName string?
---@param finisherName string?
---@param finisherZoneName string?
_AddPlayerQuestProgress = function(quest, starterName, starterZoneName, finisherName, finisherZoneName)
    if QuestiePlayer.currentQuestlog[quest.Id] then
        -- On Quest: display quest progress
        if (QuestieDB.IsComplete(quest.Id) == 0) then
            _AddTooltipLine(" ")
            _AddTooltipLine(l10n("Your progress")..":")
            for _, objective in pairs(quest.Objectives) do
                local objDesc = objective.Description:gsub("%.", "")

                if objective.Needed > 0 then
                    local lineEnding = tostring(objective.Collected) .. "/" .. tostring(objective.Needed)
                    _AddTooltipLine(" - " .. QuestieLib:GetRGBForObjective(objective) .. objDesc .. ": " .. lineEnding.."|r")
                end
            end
        -- Completed Quest (not turned in): display quest ended by npc and zone
        else
            if finisherName then
                _AddTooltipLine(" ")
                _AddTooltipLine((l10n("Ended by")..": " .. Questie:Colorize(finisherName, "gray")))
            end
            if finisherZoneName then
                _AddTooltipLine(" ")
                _AddTooltipLine((l10n("Found in")..": " .. Questie:Colorize(finisherZoneName, "gray")))
            end
        end
    else
        -- Completed Quest (turned in)
        if Questie.db.char.complete[quest.Id] == true then
            if Questie.db.char.journey then
                local timestamp
                for i = 1, #Questie.db.char.journey do
                    if Questie.db.char.journey[i].Quest ~= nil and Questie.db.char.journey[i].Quest == quest.Id then
                        local year = tonumber(date("%Y", Questie.db.char.journey[i].Timestamp))
                        local day = CALENDAR_WEEKDAY_NAMES[ tonumber(date("%w", Questie.db.char.journey[i].Timestamp)) + 1 ]
                        local month = CALENDAR_FULLDATE_MONTH_NAMES[ tonumber(date("%m", Questie.db.char.journey[i].Timestamp)) ]
                        timestamp = Questie:Colorize(date( "[ "..day ..", ".. month .." %d, "..year.." @ %H:%M ]  " , Questie.db.char.journey[i].Timestamp), "lightBlue")
                    end
                end
                if timestamp then
                    _AddTooltipLine(" ")
                    _AddTooltipLine(l10n("Completed on:"))
                    _AddTooltipLine(timestamp)
                end
            end
        -- Not on Quest: display quest started by npc and zone
        else
            if starterName then
                _AddTooltipLine(" ")
                _AddTooltipLine((l10n("Started by")..": " .. Questie:Colorize(starterName, "gray")))
            end
            if starterZoneName then
                _AddTooltipLine((l10n("Found in")..": " .. Questie:Colorize(starterZoneName, "gray")))
            end
        end
    end
end

-- Compatibility: 2.5.5+ uses ChatFrameMixin:OnHyperlinkClick instead of ChatFrame_OnHyperlinkShow
local function HandleHyperlinkClick(link, button)
    if (IsShiftKeyDown() and ChatEdit_GetActiveWindow() and button == "LeftButton") then
        local linkType, questId, _ = string.split(":", link)
        if linkType and linkType == "questie" and questId then
            Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTooltips:OnHyperlinkClick] Relinking Quest Link to chat:", link)
            questId = tonumber(questId)

            local quest = QuestieDB.GetQuest(questId)
            if quest then
                local activeWindow = ChatEdit_GetActiveWindow()
                if activeWindow then
                    local msg = activeWindow:GetText()
                    if msg then
                        activeWindow:SetText("")
                        ChatEdit_InsertLink(string.gsub(msg, "%|Hquestie:" .. questId .. ":.*%|h", "%[%[" .. quest.level .. "%] " .. quest.name .. " %(" .. questId .. "%)%]"))
                    end
                end
            end
        end
    end
end

-- Try new API first (2.5.5+)
if ChatFrameMixin and ChatFrameMixin.OnHyperlinkClick then
    local function HookChatFrameHyperlink(chatFrame)
        chatFrame:HookScript("OnHyperlinkClick", function(_, link, _, button)
            HandleHyperlinkClick(link, button)
        end)
    end

    for i = 1, (NUM_CHAT_WINDOWS or 10) do
        local chatFrame = _G["ChatFrame" .. i]
        if chatFrame then
            HookChatFrameHyperlink(chatFrame)
        end
    end
else
    -- Fallback to old API (pre-2.5.5)
    hooksecurefunc("ChatFrame_OnHyperlinkShow", function(_, link, _, button)
        HandleHyperlinkClick(link, button)
    end)
end
