--[[
    Commander 3.0 - Quests Data Source

    Provides access to quest log.
    Commands: quests, quest

    Supports both Retail and Classic
]]

local ADDON_NAME, C3 = ...

-- Use shared utilities
local Colorize = function(...) return C3.Colors.Colorize(...) end
local fuzzyMatch = function(...) return C3.StringUtils.fuzzyMatch(...) end

-------------------------------------------------------------------------------
-- Quest Type Registration
-------------------------------------------------------------------------------

C3.Types:Register("Quest", {
    description = "Quest from quest log",
    source = "quests",
    properties = {
        { "id", "Quest ID number" },
        { "name", "Quest name" },
        { "level", "Quest level" },
        { "isComplete", "true if ready to turn in" },
        { "isFailed", "true if failed" },
        { "isDaily", "true if daily quest" },
        { "isWeekly", "true if weekly quest" },
        { "isStory", "true if story quest" },
        { "isCampaign", "true if campaign quest" },
        { "objectives", "Table of objective details" },
        { "numObjectives", "Number of objectives" },
        { "zone", "Quest zone" },
        { "frequency", "Quest frequency type" },
    },
})

C3.Formatter:Register("Quest", {
    format = function(obj)
        local name = obj.name or "Unknown Quest"
        local levelStr = ""

        -- Color by completion status
        if obj.isComplete then
            name = Colorize(name, "accent.success") .. " " .. Colorize("[Complete]", "status.gold")
        elseif obj.isFailed then
            name = Colorize(name, "accent.error") .. " " .. Colorize("[Failed]", "accent.error")
        else
            name = Colorize(name, "accent.warning")
        end

        -- Add quest type indicator
        local typeStr = ""
        if obj.isCampaign then
            typeStr = " " .. Colorize("[Campaign]", "accent.warning")
        elseif obj.isStory then
            typeStr = " " .. Colorize("[Story]", "accent.special")
        elseif obj.isDaily then
            typeStr = " |cff00ccff[Daily]|r"
        elseif obj.isWeekly then
            typeStr = " |cff00ccff[Weekly]|r"
        end

        -- Level
        if obj.level and obj.level > 0 then
            levelStr = " " .. Colorize(string.format("(%d)", obj.level), "status.dim")
        end

        return name .. typeStr .. levelStr
    end,
    list = function(obj)
        local status
        if obj.isComplete then
            status = Colorize("+", "accent.success")
        elseif obj.isFailed then
            status = Colorize("X", "accent.error")
        else
            status = Colorize("-", "accent.warning")
        end
        return string.format("%s %s", status, obj.name or "?")
    end,
    compact = function(obj)
        return obj.name or "?"
    end
})

-------------------------------------------------------------------------------
-- API Compatibility
-------------------------------------------------------------------------------

local function isRetailQuests()
    return C_QuestLog and C_QuestLog.GetNumQuestLogEntries
end

-------------------------------------------------------------------------------
-- Quests Intellisense Provider
-------------------------------------------------------------------------------

local MAX_SUGGESTIONS = C3.Constants.MAX_SUGGESTIONS

C3.Intellisense:RegisterProvider("Quests", function(partial, ctx)
    local quests = {}

    if C_QuestLog and C_QuestLog.GetNumQuestLogEntries then
        -- Retail API
        local numEntries = C_QuestLog.GetNumQuestLogEntries()

        for i = 1, numEntries do repeat
            local info = C_QuestLog.GetInfo(i)

            -- Guard: Invalid or header entry
            if not info then break end
            if info.isHeader then break end
            if info.isHidden then break end
            if not info.questID then break end

            if not fuzzyMatch(partial, info.title) then break end

            local isComplete = C_QuestLog.IsComplete(info.questID)
            local isFailed = C_QuestLog.IsFailed(info.questID)

            local status = "In Progress"
            if isComplete then
                status = "Complete"
            elseif isFailed then
                status = "Failed"
            end

            quests[#quests + 1] = {
                name = info.title,
                status = status,
                isComplete = isComplete,
                isFailed = isFailed,
                isCampaign = info.isCampaign,
                isDaily = info.frequency == (Enum and Enum.QuestFrequency and Enum.QuestFrequency.Daily or 1),
            }
            if #quests >= MAX_SUGGESTIONS then break end
        until true end
    elseif GetNumQuestLogEntries then
        -- Classic API
        local numEntries = GetNumQuestLogEntries()

        for i = 1, numEntries do repeat
            local title, _, _, isHeader, _, isComplete = GetQuestLogTitle(i)

            -- Guard: Header or no title
            if isHeader then break end
            if not title then break end
            if not fuzzyMatch(partial, title) then break end

            quests[#quests + 1] = {
                name = title,
                status = isComplete and "Complete" or "In Progress",
                isComplete = isComplete,
            }
            if #quests >= MAX_SUGGESTIONS then break end
        until true end
    end

    local results = {}
    for _, quest in ipairs(quests) do
        local desc = quest.status
        if quest.isCampaign then
            desc = desc .. " [Campaign]"
        elseif quest.isDaily then
            desc = desc .. " [Daily]"
        end
        results[#results + 1] = {
            text = quest.name,
            description = desc,
        }
    end
    return results
end)

-------------------------------------------------------------------------------
-- quests - List quest log
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "quests",
    description = "List quests in quest log",
    category = "data",
    producesType = "Quest",
    acceptsInput = false,
    flags = {
        complete = { short = "c", description = "Show only completed quests" },
        incomplete = { short = "i", description = "Show only incomplete quests" },
        daily = { short = "d", description = "Show only daily quests" },
        weekly = { short = "w", description = "Show only weekly quests" },
        campaign = { description = "Show only campaign quests" },
    },
    execute = function(ctx)
        local completeOnly = ctx:HasFlag("complete") or ctx:HasFlag("c")
        local incompleteOnly = ctx:HasFlag("incomplete") or ctx:HasFlag("i")
        local dailyOnly = ctx:HasFlag("daily") or ctx:HasFlag("d")
        local weeklyOnly = ctx:HasFlag("weekly") or ctx:HasFlag("w")
        local campaignOnly = ctx:HasFlag("campaign")

        -- Build results array with yielding to prevent freezing
        -- We can't use ctx:Produce() because coroutine.wrap() creates a C-call
        -- boundary that prevents yielding during iteration
        local results = {}
        local processed = 0
        local YIELD_INTERVAL = C3.Constants.YIELD_INTERVAL

        if isRetailQuests() then
            -- Expand all collapsed headers to see all quests
            if ExpandQuestHeader then
                for attempt = 1, 50 do
                    local didExpand = false
                    local numEntries = C_QuestLog.GetNumQuestLogEntries()
                    for i = 1, numEntries do
                        local info = C_QuestLog.GetInfo(i)
                        if info and info.isHeader and info.isCollapsed then
                            ExpandQuestHeader(i)
                            didExpand = true
                            break
                        end
                    end
                    if not didExpand then break end
                end
            end

            local numEntries = C_QuestLog.GetNumQuestLogEntries()

            for i = 1, numEntries do
                -- Yield periodically to prevent freezing
                processed = processed + 1
                if processed % YIELD_INTERVAL == 0 then
                    coroutine.yield()
                end

                local info = C_QuestLog.GetInfo(i)

                if info and not info.isHeader and not info.isHidden and info.questID then
                    local questID = info.questID
                    local isComplete = C_QuestLog.IsComplete(questID)
                    local isFailed = C_QuestLog.IsFailed(questID)

                    -- Get frequency from info struct (more reliable)
                    local frequency = info.frequency or 0
                    -- Enum.QuestFrequency: Daily=1, Weekly=2
                    local DAILY = Enum and Enum.QuestFrequency and Enum.QuestFrequency.Daily or 1
                    local WEEKLY = Enum and Enum.QuestFrequency and Enum.QuestFrequency.Weekly or 2
                    local isDaily = (frequency == DAILY)
                    local isWeekly = (frequency == WEEKLY)

                    -- Check quest type
                    local isCampaign = info.isCampaign or false
                    local isStory = info.isStory or info.isCampaign or false

                    -- Apply filters
                    local include = true

                    if completeOnly and not isComplete then
                        include = false
                    end

                    if incompleteOnly and isComplete then
                        include = false
                    end

                    if dailyOnly and not isDaily then
                        include = false
                    end

                    if weeklyOnly and not isWeekly then
                        include = false
                    end

                    if campaignOnly and not isCampaign then
                        include = false
                    end

                    if include then
                        -- Get objectives
                        local objectives = {}
                        local numObjectives = C_QuestLog.GetNumQuestObjectives(questID)
                        for j = 1, numObjectives do
                            local text, objType, finished = GetQuestObjectiveInfo(questID, j, false)
                            if text then
                                table.insert(objectives, {
                                    text = text,
                                    type = objType,
                                    finished = finished,
                                })
                            end
                        end

                        table.insert(results, C3.Types.Create("Quest", {
                            id = questID,
                            name = info.title,
                            level = info.level or 0,
                            isComplete = isComplete,
                            isFailed = isFailed,
                            isDaily = isDaily,
                            isWeekly = isWeekly,
                            isStory = isStory,
                            isCampaign = isCampaign,
                            objectives = objectives,
                            numObjectives = numObjectives,
                            zone = info.mapID,
                            frequency = frequency,
                            isOnMap = info.isOnMap,
                            hasLocalPOI = info.hasLocalPOI,
                        }))
                    end
                end
            end
        else
            -- Classic API
            local numEntries, numQuests = GetNumQuestLogEntries()

            for i = 1, numEntries do
                -- Yield periodically to prevent freezing
                processed = processed + 1
                if processed % YIELD_INTERVAL == 0 then
                    coroutine.yield()
                end

                local title, level, suggestedGroup, isHeader, isCollapsed,
                      isComplete, frequency, questID = GetQuestLogTitle(i)

                if not isHeader and questID then
                    local isDaily = (frequency == 1)
                    local isWeekly = (frequency == 2)

                    local include = true

                    if completeOnly and not isComplete then
                        include = false
                    end

                    if incompleteOnly and isComplete then
                        include = false
                    end

                    if dailyOnly and not isDaily then
                        include = false
                    end

                    if weeklyOnly and not isWeekly then
                        include = false
                    end

                    if include then
                        table.insert(results, C3.Types.Create("Quest", {
                            id = questID,
                            name = title,
                            level = level or 0,
                            isComplete = isComplete,
                            isFailed = false,
                            isDaily = isDaily,
                            isWeekly = isWeekly,
                            isStory = false,
                            isCampaign = false,
                            frequency = frequency,
                        }))
                    end
                end
            end
        end

        return results
    end
})

-------------------------------------------------------------------------------
-- quest - Get specific quest info
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "quest",
    description = "Get quest info by name or ID",
    category = "data",
    acceptsInput = false,
    args = {
        { name = "query", required = true, description = "Quest name or ID",
          provider = "Quests" }
    },
    execute = function(ctx)
        local query = table.concat(ctx:GetArgs(), " ")
        if not query or query == "" then
            return ctx:Error("Usage: quest <name or id>")
        end

        local questID = tonumber(query)
        local foundQuest = nil

        if isRetailQuests() then
            local numEntries = C_QuestLog.GetNumQuestLogEntries()

            for i = 1, numEntries do
                local info = C_QuestLog.GetInfo(i)

                if info and not info.isHeader and info.questID then
                    local match = false

                    if questID and info.questID == questID then
                        match = true
                    elseif not questID and info.title and
                           info.title:lower() == query:lower() then
                        match = true
                    end

                    if match then
                        local isComplete = C_QuestLog.IsComplete(info.questID)
                        local isFailed = C_QuestLog.IsFailed(info.questID)

                        foundQuest = C3.Types.Create("Quest", {
                            id = info.questID,
                            name = info.title,
                            level = info.level or 0,
                            isComplete = isComplete,
                            isFailed = isFailed,
                            isCampaign = info.isCampaign,
                            isStory = info.isStory,
                        })
                        break
                    end
                end
            end
        end

        if not foundQuest then
            return ctx:Error("Quest not found: " .. query)
        end

        return foundQuest
    end
})

-------------------------------------------------------------------------------
-- abandon - Abandon a quest
-------------------------------------------------------------------------------

-- Helper function to perform the actual abandon
local function doAbandon(quests)
    local abandoned = {}
    for _, quest in ipairs(quests) do
        if isRetailQuests() then
            C_QuestLog.SetSelectedQuest(quest.id)
            C_QuestLog.SetAbandonQuest()
            C_QuestLog.AbandonQuest()
            table.insert(abandoned, quest.name or ("Quest #" .. quest.id))
        end
    end

    if #abandoned == 1 then
        return C3.Types.Create("Text", {
            content = "Abandoned: " .. abandoned[1]
        })
    else
        return C3.Types.Create("Text", {
            content = "Abandoned " .. #abandoned .. " quests: " .. table.concat(abandoned, ", ")
        })
    end
end

-- Helper to build confirmation message
local function buildConfirmMessage(quests)
    if #quests == 1 then
        return string.format("Abandon '%s'?", quests[1].name or ("Quest #" .. quests[1].id))
    elseif #quests <= 5 then
        local names = {}
        for _, q in ipairs(quests) do
            table.insert(names, q.name or ("Quest #" .. q.id))
        end
        return string.format("Abandon %d quests: %s?", #quests, table.concat(names, ", "))
    else
        return string.format("Abandon %d quests?", #quests)
    end
end

C3.Command:Define({
    name = "abandon",
    description = "Abandon a quest by name or from pipeline",
    category = "action",
    args = {
        { name = "quest", required = false, description = "Quest name to abandon",
          provider = "Quests" }
    },
    flags = {
        { name = "force", short = "f", description = "Skip confirmation prompt" }
    },
    execute = function(ctx)
        local force = ctx:HasFlag("force") or ctx:HasFlag("f")
        local toAbandon = {}

        -- Handle piped input (e.g., quests | where isWeekly | abandon)
        if ctx:HasInput() then
            ctx:Consume(function(obj)
                if obj and obj._type == "Quest" and obj.id then
                    table.insert(toAbandon, { id = obj.id, name = obj.name })
                end
            end)

            if #toAbandon == 0 then
                return ctx:Error("No quests to abandon")
            end
        else
            -- Handle argument-based lookup
            local query = table.concat(ctx:GetArgs(), " ")
            if not query or query == "" then
                return ctx:Error("Usage: abandon <quest name>")
            end

            if isRetailQuests() then
                local numEntries = C_QuestLog.GetNumQuestLogEntries()
                local queryLower = query:lower()

                for i = 1, numEntries do
                    local info = C_QuestLog.GetInfo(i)

                    if info and not info.isHeader and info.title then
                        if info.title:lower() == queryLower then
                            table.insert(toAbandon, { id = info.questID, name = info.title })
                            break  -- Exact match found
                        end
                    end
                end
            end

            if #toAbandon == 0 then
                return ctx:Error("Quest not found: " .. query)
            end
        end

        -- Skip confirmation with --force
        if force then
            return doAbandon(toAbandon)
        end

        -- Request confirmation
        return C3.Prompt.Request({
            message = buildConfirmMessage(toAbandon),
            onConfirm = function()
                return doAbandon(toAbandon)
            end,
            onCancel = function()
                return C3.Types.Create("Text", {
                    content = Colorize("Cancelled", "status.dim")
                })
            end,
        })
    end
})

return true
