local _, ns = ...
local L = ns.L or {}

local Quest = ns.Quest or {}
ns.Quest = Quest

local function isQuestCompleted(questID)
    if not questID or questID <= 0 then
        return false
    end

    if C_QuestLog and type(C_QuestLog.IsQuestFlaggedCompleted) == "function" then
        local ok, completed = pcall(C_QuestLog.IsQuestFlaggedCompleted, questID)
        return ok and completed and true or false
    end

    if type(IsQuestFlaggedCompleted) == "function" then
        local ok, completed = pcall(IsQuestFlaggedCompleted, questID)
        return ok and completed and true or false
    end

    return false
end

local function isQuestCompletedOnAccount(questID)
    if not questID or questID <= 0 then
        return false
    end

    if C_QuestLog and type(C_QuestLog.IsQuestFlaggedCompletedOnAccount) == "function" then
        local ok, completed = pcall(C_QuestLog.IsQuestFlaggedCompletedOnAccount, questID)
        if ok then
            return completed == true
        end
    end

    return isQuestCompleted(questID)
end

local function isQuestActive(questID)
    if not questID or questID <= 0 then
        return false
    end

    if C_QuestLog and type(C_QuestLog.IsOnQuest) == "function" then
        local ok, active = pcall(C_QuestLog.IsOnQuest, questID)
        if ok and active then
            return true
        end
    end

    if C_QuestLog and type(C_QuestLog.GetLogIndexForQuestID) == "function" then
        local ok, logIndex = pcall(C_QuestLog.GetLogIndexForQuestID, questID)
        if ok and logIndex and logIndex > 0 then
            return true
        end
    end

    return false
end

local function isTaskQuestActive(questID)
    if not questID or questID <= 0 then
        return false
    end

    if C_TaskQuest and type(C_TaskQuest.IsActive) == "function" then
        local ok, active = pcall(C_TaskQuest.IsActive, questID)
        if ok and active then
            return true
        end
    end

    return false
end

local function getTaskQuestIDsOnMap(mapID)
    if not (C_TaskQuest and type(C_TaskQuest.GetQuestsOnMap) == "function") then
        return {}
    end

    local ok, quests = pcall(C_TaskQuest.GetQuestsOnMap, mapID)
    if not ok or type(quests) ~= "table" then
        return {}
    end

    local ids = {}
    for _, entry in ipairs(quests) do
        local questID = tonumber(type(entry) == "table" and (entry.questID or entry.questId) or entry)
        if questID and questID > 0 then
            ids[#ids + 1] = questID
        end
    end

    return ids
end

local function getQuestLogEntryByID(questID)
    if not questID or questID <= 0 then
        return nil
    end

    if not (C_QuestLog and type(C_QuestLog.GetLogIndexForQuestID) == "function" and type(C_QuestLog.GetInfo) == "function") then
        return nil
    end

    local okIndex, logIndex = pcall(C_QuestLog.GetLogIndexForQuestID, questID)
    if not okIndex or not logIndex or logIndex <= 0 then
        return nil
    end

    local okInfo, info = pcall(C_QuestLog.GetInfo, logIndex)
    if okInfo and type(info) == "table" then
        return info
    end

    return nil
end

local function parseObjectiveFraction(text)
    if type(text) ~= "string" or text == "" then
        return nil, nil
    end

    local current, maximum = text:match("(%d+)%s*/%s*(%d+)")
    current = tonumber(current)
    maximum = tonumber(maximum)
    if current and maximum and maximum > 0 then
        return current, maximum
    end

    return nil, nil
end

local function getQuestObjectiveLines(questID)
    local lines = {}

    if C_QuestLog and type(C_QuestLog.GetQuestObjectives) == "function" then
        local ok, objectives = pcall(C_QuestLog.GetQuestObjectives, questID)
        if ok and type(objectives) == "table" then
            for _, objective in ipairs(objectives) do
                local text = type(objective) == "table" and (objective.text or objective.description) or objective
                if type(text) == "string" and text ~= "" then
                    lines[#lines + 1] = text
                end
            end
        end
    end

    if #lines == 0 and C_QuestLog and type(C_QuestLog.GetLogIndexForQuestID) == "function" and type(GetNumQuestLeaderBoards) == "function" and type(GetQuestLogLeaderBoard) == "function" then
        local okIndex, logIndex = pcall(C_QuestLog.GetLogIndexForQuestID, questID)
        if okIndex and logIndex and logIndex > 0 then
            local okCount, objectiveCount = pcall(GetNumQuestLeaderBoards, logIndex)
            if okCount and objectiveCount and objectiveCount > 0 then
                for objectiveIndex = 1, objectiveCount do
                    local okObjective, objectiveText = pcall(GetQuestLogLeaderBoard, objectiveIndex, logIndex)
                    if okObjective and type(objectiveText) == "string" and objectiveText ~= "" then
                        lines[#lines + 1] = objectiveText
                    end
                end
            end
        end
    end

    return lines
end

local function getObjectiveProgressFromInfo(objective)
    if type(objective) ~= "table" then
        return parseObjectiveFraction(objective)
    end

    local current = tonumber(objective.numFulfilled or objective.fulfilledAmount or objective.currentAmount
        or objective.quantity or objective.numCollected or objective.curValue)
    local maximum = tonumber(objective.numRequired or objective.requiredAmount or objective.totalAmount
        or objective.total or objective.targetAmount or objective.maxValue)

    if current and maximum and maximum > 0 then
        return math.max(0, current), math.max(1, maximum)
    end

    return parseObjectiveFraction(objective.text or objective.description)
end

local function getObjectiveFinishedState(objective)
    if type(objective) ~= "table" then
        return nil
    end

    if objective.finished == true or objective.isFinished == true or objective.completed == true then
        return true
    end

    local current, maximum = getObjectiveProgressFromInfo(objective)
    if current and maximum then
        return current >= maximum
    end

    return nil
end

local function getQuestObjectiveProgressText(questID, includeComplete)
    if not questID or questID <= 0 then
        return nil
    end

    local progressEntries = {}
    local finishedCount = 0
    local objectiveCount = 0

    if C_QuestLog and type(C_QuestLog.GetQuestObjectives) == "function" then
        local ok, objectives = pcall(C_QuestLog.GetQuestObjectives, questID)
        if ok and type(objectives) == "table" then
            for _, objective in ipairs(objectives) do
                local current, maximum = getObjectiveProgressFromInfo(objective)
                if current and maximum and maximum > 0 then
                    progressEntries[#progressEntries + 1] = {
                        current = math.min(maximum, math.max(0, current)),
                        maximum = maximum,
                    }
                end

                local finished = getObjectiveFinishedState(objective)
                if finished ~= nil then
                    objectiveCount = objectiveCount + 1
                    if finished then
                        finishedCount = finishedCount + 1
                    end
                end
            end
        end
    end

    if #progressEntries == 0 then
        for _, objectiveText in ipairs(getQuestObjectiveLines(questID)) do
            local current, maximum = parseObjectiveFraction(objectiveText)
            if current and maximum and maximum > 0 then
                progressEntries[#progressEntries + 1] = {
                    current = math.min(maximum, math.max(0, current)),
                    maximum = maximum,
                }
            end
        end
    end

    if #progressEntries > 0 then
        local chosen
        for _, entry in ipairs(progressEntries) do
            if entry.current < entry.maximum then
                chosen = entry
                break
            end
        end
        if not chosen and includeComplete == true then
            chosen = progressEntries[1]
        end
        if not chosen then
            return nil
        end
        local current = math.floor((tonumber(chosen.current) or 0) + 0.5)
        local maximum = math.max(1, math.floor((tonumber(chosen.maximum) or 1) + 0.5))
        return string.format("%d/%d", current, maximum)
    end

    if objectiveCount > 1 and (finishedCount < objectiveCount or includeComplete == true) then
        return string.format("%d/%d", finishedCount, objectiveCount)
    end

    return nil
end

local function getQuestLogEntries()
    local entries = {}

    if C_QuestLog and type(C_QuestLog.GetNumQuestLogEntries) == "function" and type(C_QuestLog.GetInfo) == "function" then
        local okCount, numEntries = pcall(C_QuestLog.GetNumQuestLogEntries)
        if okCount and tonumber(numEntries) and numEntries > 0 then
            for index = 1, numEntries do
                local okInfo, info = pcall(C_QuestLog.GetInfo, index)
                if okInfo and type(info) == "table" and not info.isHeader and not info.isHidden then
                    entries[#entries + 1] = {
                        questID = tonumber(info.questID) or 0,
                        title = info.title or "",
                        text = info.title or "",
                        isComplete = info.isComplete == true,
                    }
                end
            end
        end
    end

    return entries
end

local function getQuestObjectiveCompletionState(questID)
    local sawObjective = false
    local allFinished = true

    if C_QuestLog and type(C_QuestLog.GetQuestObjectives) == "function" then
        local ok, objectives = pcall(C_QuestLog.GetQuestObjectives, questID)
        if ok and type(objectives) == "table" then
            for _, objective in ipairs(objectives) do
                local finished
                if type(objective) == "table" then
                    if objective.finished == true or objective.isFinished == true or objective.completed == true then
                        finished = true
                    else
                        local current = tonumber(objective.numFulfilled or objective.fulfilledAmount or objective.currentAmount or objective.quantity or objective.numCollected or objective.curValue)
                        local maximum = tonumber(objective.numRequired or objective.requiredAmount or objective.totalAmount or objective.total or objective.targetAmount or objective.maxValue)
                        if current and maximum and maximum > 0 then
                            finished = current >= maximum
                        else
                            local lineCurrent, lineMaximum = parseObjectiveFraction(objective.text or objective.description)
                            if lineCurrent and lineMaximum then
                                finished = lineCurrent >= lineMaximum
                            end
                        end
                    end
                end

                if finished ~= nil then
                    sawObjective = true
                    allFinished = allFinished and finished
                end
            end
        end
    end

    if sawObjective then
        return allFinished
    end

    local parsedAny = false
    for _, objectiveText in ipairs(getQuestObjectiveLines(questID)) do
        local current, maximum = parseObjectiveFraction(objectiveText)
        if current and maximum then
            parsedAny = true
            if current < maximum then
                return false
            end
        end
    end

    if parsedAny then
        return true
    end

    return nil
end

local function isQuestTurnInReady(questID)
    if not questID or questID <= 0 then
        return false
    end

    if isQuestCompleted(questID) or not isQuestActive(questID) then
        return false
    end

    if C_QuestLog and type(C_QuestLog.ReadyForTurnIn) == "function" then
        local okReady, readyForTurnIn = pcall(C_QuestLog.ReadyForTurnIn, questID)
        if okReady and readyForTurnIn then
            return true
        end
    end

    local logEntry = getQuestLogEntryByID(questID)
    if logEntry and logEntry.isComplete then
        return true
    end

    if C_QuestLog and type(C_QuestLog.GetLogIndexForQuestID) == "function" and type(GetQuestLogIsComplete) == "function" then
        local okIndex, logIndex = pcall(C_QuestLog.GetLogIndexForQuestID, questID)
        if okIndex and logIndex and logIndex > 0 then
            local okComplete, isComplete = pcall(GetQuestLogIsComplete, logIndex)
            if okComplete and isComplete then
                return true
            end
        end
    end

    return getQuestObjectiveCompletionState(questID) == true
end

local function getQuestObjectiveProgress(questID, targetCount)
    targetCount = math.max(1, tonumber(targetCount) or 1)

    if isQuestCompleted(questID) then
        return {
            seen = true,
            completed = true,
            status = "complete",
            count = targetCount,
            text = L.STATUS_DONE or "Done",
            questID = questID,
            turnInQuestID = nil,
        }
    end

    if not isQuestActive(questID) then
        return {
            seen = false,
            completed = false,
            status = "missing",
            count = 0,
            text = L.STATUS_MISSING or "Accept",
            questID = questID,
            turnInQuestID = nil,
        }
    end

    if isQuestTurnInReady(questID) then
        return {
            seen = true,
            completed = false,
            status = "turnin",
            count = targetCount,
            text = L.STATUS_TURNIN or "Turn in",
            questID = questID,
            turnInQuestID = questID,
        }
    end

    local progress = 0
    for _, objectiveText in ipairs(getQuestObjectiveLines(questID)) do
        local current, maximum = parseObjectiveFraction(objectiveText)
        if current and maximum and maximum > 0 then
            progress = math.max(progress, math.min(targetCount, current))
        end
    end

    local clamped = math.min(targetCount, math.max(0, progress))

    return {
        seen = true,
        completed = false,
        status = (clamped >= targetCount) and "turnin" or "open",
        count = clamped,
        text = ((clamped >= targetCount) and (L.STATUS_TURNIN or "Turn in")) or string.format("%d/%d", clamped, targetCount),
        questID = questID,
        turnInQuestID = (clamped >= targetCount) and questID or nil,
    }
end

local function getQuestTitle(questID, fallbackTitle)
    if C_QuestLog and type(C_QuestLog.GetTitleForQuestID) == "function" then
        local ok, title = pcall(C_QuestLog.GetTitleForQuestID, questID)
        if ok and type(title) == "string" and title ~= "" then
            return title
        end
    end

    local logEntry = getQuestLogEntryByID(questID)
    if logEntry and type(logEntry.title) == "string" and logEntry.title ~= "" then
        return logEntry.title
    end

    if C_TaskQuest and type(C_TaskQuest.GetQuestInfoByQuestID) == "function" then
        local ok, title = pcall(C_TaskQuest.GetQuestInfoByQuestID, questID)
        if ok and type(title) == "string" and title ~= "" then
            return title
        end
    end

    return fallbackTitle
end

local function buildQuestPoolState(pool)
    if type(pool) ~= "table" then
        return nil
    end

    local label = L[pool.labelKey] or pool.fallbackLabel or ""
    local activeQuest
    local completedQuest

    for _, questInfo in ipairs(pool.quests or {}) do
        if isQuestCompleted(questInfo.id) then
            completedQuest = questInfo
            break
        end
        if not activeQuest and isQuestActive(questInfo.id) then
            activeQuest = questInfo
        end
    end

    local chosenQuest = activeQuest or completedQuest or (pool.quests and pool.quests[1]) or nil
    local questTitle = chosenQuest and getQuestTitle(chosenQuest.id, chosenQuest.fallbackTitle) or label

    if completedQuest then
        return {
            key = pool.key,
            label = label,
            text = L.STATUS_DONE or "Done",
            status = "complete",
            seen = true,
            completed = true,
            questID = completedQuest.id,
            tooltipTitle = questTitle,
            tooltipLines = { L.PVP_WEEKLY_DONE_TOOLTIP or "Already completed this week." },
        }
    end

    if activeQuest then
        local lines = getQuestObjectiveLines(activeQuest.id)
        local isTurnInReady = isQuestTurnInReady(activeQuest.id)
        if #lines == 0 then
            lines[#lines + 1] = isTurnInReady
                and (L.PVP_WEEKLY_TURNIN_TOOLTIP or "This quest is done and can now be turned in.")
                or (L.PVP_WEEKLY_ACTIVE_TOOLTIP or "This quest is active. Check the quest log for the current objective.")
        end

        return {
            key = pool.key,
            label = label,
            text = isTurnInReady and (L.STATUS_TURNIN or "Turn in")
                or getQuestObjectiveProgressText(activeQuest.id)
                or (L.STATUS_OPEN or "Accepted"),
            status = isTurnInReady and "turnin" or "open",
            seen = true,
            completed = false,
            questID = activeQuest.id,
            turnInQuestID = isTurnInReady and activeQuest.id or nil,
            tooltipTitle = questTitle,
            tooltipLines = lines,
        }
    end

    return {
        key = pool.key,
        label = label,
        text = L.STATUS_MISSING or "Accept",
        status = "missing",
        seen = false,
        completed = false,
        questID = chosenQuest and chosenQuest.id or nil,
        tooltipTitle = questTitle or label,
        tooltipLines = { L.PVP_WEEKLY_GIVER_TOOLTIP or "This quest can be accepted from Zerella in Silvermoon." },
    }
end

Quest.IsQuestCompleted = isQuestCompleted
Quest.IsQuestCompletedOnAccount = isQuestCompletedOnAccount
Quest.IsQuestActive = isQuestActive
Quest.IsTaskQuestActive = isTaskQuestActive
Quest.GetTaskQuestIDsOnMap = getTaskQuestIDsOnMap
Quest.GetQuestLogEntryByID = getQuestLogEntryByID
Quest.ParseObjectiveFraction = parseObjectiveFraction
Quest.GetQuestObjectiveLines = getQuestObjectiveLines
Quest.GetQuestLogEntries = getQuestLogEntries
Quest.IsQuestTurnInReady = isQuestTurnInReady
Quest.GetQuestObjectiveProgress = getQuestObjectiveProgress
Quest.GetQuestObjectiveProgressText = getQuestObjectiveProgressText
Quest.GetQuestTitle = getQuestTitle
Quest.BuildQuestPoolState = buildQuestPoolState
