---@class AddonPrivate
local Private = select(2, ...)

---@class Commands
local commands = {}
Private.Commands = commands

local const = Private.constants

function commands:AddCommand(cmdName, cmdFunc)
    local addon = Private.Addon
    addon:RegisterChatCommand(cmdName, function(...) cmdFunc(self, ...) end)
end

function commands:InitCommands()
    self:AddCommand("drustminimap", self.DrustMinimap)
    self:AddCommand("drustui", self.DrustUI)
    self:AddCommand("drusthud", self.DrustHud)
    self:AddCommand("drustroute", self.DrustRoute)
    self:AddCommand("drustdebugdungeon", self.DrustDebugDungeon)
    self:AddCommand("drustdebugwq", self.DrustDebugWQ)
    self:AddCommand("drustexportnotes", self.DrustExportNotes)
    self:AddCommand("drustexportfixes", self.DrustExportFixes)
    self:AddCommand("drustexportsteps", self.DrustExportSteps)
    self:AddCommand("drustreward", self.DrustReward)
    self:AddCommand("drustrewarddemo", self.DrustRewardDemo)
    self:AddCommand("drustrares", self.DrustRares)
    self:AddCommand("drusttheater", self.DrustTheater)
    self:AddCommand("drusttheaterdebug", self.DrustTheaterDebug)
    self:AddCommand("drustwhatsnew", self.DrustWhatsNew)
    self:AddCommand("drustdebug", self.DrustDebug)
    self:AddCommand("drustrep", self.DrustRep)
    self:AddCommand("drustdelves", self.DrustDelves)
    self:AddCommand("drustdelvepoi", self.DrustDelvePOI)
    self:AddCommand("drustwqdebug", self.DrustWQDebug)
    self:AddCommand("drustwqlist", self.DrustWQList)
    self:AddCommand("drustquests", self.DrustQuests)
    self:AddCommand("drustquestdebug", self.DrustQuestDebug)
    self:AddCommand("mtwho", self.MtWho)
end

-- Hidden peer-detection roster (see Comm.lua). Undocumented on purpose -- normal users
-- never need it. /mtwho = list realm users; "here" = this zone only; "live" = toggle
-- mouseover/auto prints; "off"/"on" = leave/rejoin the channel entirely.
function commands:MtWho(input)
    if not Private.Comm then return end
    local arg = (input or ""):lower():gsub("^%s+", ""):gsub("%s+$", "")
    if arg == "live" then
        Private.Comm:ToggleVerbose()
    elseif arg == "review" then
        Private.Comm:ToggleReview()
    elseif arg == "pull" then
        Private.Comm:Pull()
    elseif arg == "off" or arg == "on" then
        Private.Comm:ToggleEnabled()
    elseif arg == "here" then
        Private.Comm:PrintRoster(true)
    else
        Private.Comm:PrintRoster(false)
    end
end

-- Toggles a debug print on each planner quest click: quest id, on-quest/ready state,
-- resolved questline, and the turn-in waypoint coords the game returns. Lets us see
-- exactly why a "turn in" click does or doesn't get a pin.
function commands:DrustQuestDebug()
    local addon = Private.Addon
    local db = Private.Settings and Private.Settings.db
    if not db then addon:Print("|cffff0000Settings db unavailable.|r"); return end
    db._questDebug = not db._questDebug
    addon:Print("Quest-click debug " .. (db._questDebug and "|cff00ff00ON|r — click a quest row and read the line."
        or "|cffff0000OFF|r"))
end

-- Lists the meta-required storylines/quests the planner currently sees, and whether
-- the questline API is available. Useful to confirm the Storylines & Quests section.
function commands:DrustQuests()
    local addon, O = Private.Addon, Private.Objectives
    if not (O and O.GetNeededQuests) then
        addon:Print("|cffff0000GetNeededQuests unavailable — old copy of the addon loaded.|r")
        return
    end
    O:RequestQuestLines()
    O:InvalidateQuests()
    local mapID = C_Map.GetBestMapForUnit and C_Map.GetBestMapForUnit("player")
    local quests = O:GetNeededQuests(mapID)
    addon:Print(string.format("|cffffb84dNeeded storylines/quests: %d|r  |cff888888(C_QuestLine=%s)|r",
        #quests, (C_QuestLine and C_QuestLine.GetAvailableQuestLines) and "yes" or "NO"))
    for _, q in ipairs(quests) do
        local story = (q.storyName and q.storyName ~= "" and q.storyName ~= q.questName) and (" (" .. q.storyName .. ")") or ""
        addon:Print(string.format("  - %s: %s%s |cff888888[%s%s]|r -> %s", q.onQuest and "Cont" or "Start",
            q.questName or q.title or "?", story, q.kind,
            q.isCurrent and ", here" or ((q.mapName and (", " .. q.mapName)) or ""),
            (q.ach and q.ach.name) or "?"))
    end
    if #quests == 0 then
        addon:Print("  |cff888888(none needed here right now — questline data loads a moment after you enter a zone)|r")
    end
end

-- Prints every World Quest name currently indexed for meta relevance, so you can
-- confirm a specific one (e.g. "getting out of hand") is being tracked.
function commands:DrustWQList(input)
    local addon, O = Private.Addon, Private.Objectives
    if not (O and O.GetTrackedWQTitles) then
        addon:Print("|cffff0000GetTrackedWQTitles unavailable — old copy of the addon loaded.|r")
        return
    end
    local filter = (input or ""):lower():gsub("^%s+", ""):gsub("%s+$", "")
    local t, broad, broadList = O:GetTrackedWQTitles()
    addon:Print(string.format("|cff33bbffCurated WQ names: %d|r  |cff888888(+ %d-name broad net: any criterion name is matched vs live WQs)|r%s",
        #t, broad or 0, filter ~= "" and ("  |cff888888filter: " .. filter .. "|r") or ""))
    local shown = 0
    for _, e in ipairs(t) do
        if filter == "" or e.title:find(filter, 1, true) then
            addon:Print(string.format("  - %s |cff888888(%s)|r", e.title, e.ach))
            shown = shown + 1
        end
    end
    -- With a filter, also search the broad net so you can confirm any criterion name
    -- (e.g. Worm Theory's WQs) is covered even if it's not in the curated list.
    if filter ~= "" and broadList then
        for _, e in ipairs(broadList) do
            if e.title:find(filter, 1, true) then
                addon:Print(string.format("  - %s |cff888888(%s)|r |cff66aaff[broad]|r", e.title, e.ach))
                shown = shown + 1
            end
        end
    end
    if shown == 0 then addon:Print("  |cffff8800(none match)|r") end
end

-- Toggles a debug row in the planner's World Quests section that forces one active
-- current-zone WQ to display regardless of meta-relevance, proving the live WQ
-- pipeline works. Also prints a quick count.
function commands:DrustWQDebug()
    local addon, O = Private.Addon, Private.Objectives
    if not (Private.RouteHud and O and O.GetWorldQuests) then
        addon:Print("|cffff0000RouteHud / GetWorldQuests unavailable — old copy of the addon loaded.|r")
        return
    end
    Private.RouteHud.wqDebug = not Private.RouteHud.wqDebug
    local mapID = C_Map.GetBestMapForUnit and C_Map.GetBestMapForUnit("player")
    local wqs = O:GetWorldQuests(mapID)
    local rel, cur = 0, 0
    for _, q in ipairs(wqs) do
        if q.relevant then rel = rel + 1 end
        if q.isCurrent then cur = cur + 1 end
    end
    local function mname(m) local i = m and m > 0 and C_Map.GetMapInfo(m); return i and i.name or tostring(m) end
    addon:Print(string.format("WQ debug %s. Active WQs: |cff00ff00%d|r total, %d in this zone, %d meta. GetQuestZoneID=%s, GetQuestUiMapID=%s",
        Private.RouteHud.wqDebug and "|cff00ff00ON|r" or "|cffff0000OFF|r", #wqs, cur, rel,
        (C_TaskQuest and C_TaskQuest.GetQuestZoneID) and "yes" or "NO",
        GetQuestUiMapID and "yes" or "NO"))
    -- Show how each WQ resolved + the raw zone signals, so we can see which is right.
    for i = 1, math.min(#wqs, 8) do
        local q = wqs[i]
        local zid = C_TaskQuest and C_TaskQuest.GetQuestZoneID and C_TaskQuest.GetQuestZoneID(q.questID)
        local uid = GetQuestUiMapID and GetQuestUiMapID(q.questID)
        addon:Print(string.format("  %s |cff888888-> %s%s|r  |cff666666[ZoneID=%s, UiMapID=%s]|r%s", q.title,
            q.mapName or "?", q.isCurrent and " |cff00ff00(current)|r" or "",
            zid and mname(zid) or "-", uid and mname(uid) or "-",
            q.relevant and "  |cffffd700[meta]|r" or ""))
    end
    if Private.RouteHud.wqDebug and cur == 0 then
        addon:Print("  |cffff8800No active World Quests resolve to your current zone right now — move to a zone with WQs up to see the debug row.|r")
    end
    Private.RouteHud:Refresh()
end

-- Dumps every area POI on the current map plus any tooltip-widget text, so we can
-- confirm which POI is a delve and where its live "active story" line lives.
function commands:DrustDelvePOI()
    local addon = Private.Addon
    local mapID = C_Map.GetBestMapForUnit and C_Map.GetBestMapForUnit("player")
    if not (mapID and C_AreaPoiInfo and C_AreaPoiInfo.GetAreaPOIForMap) then
        addon:Print("|cffff0000No map / C_AreaPoiInfo available here.|r")
        return
    end
    local delvePois = (C_AreaPoiInfo.GetDelvesForMap and C_AreaPoiInfo.GetDelvesForMap(mapID)) or {}
    local areaPois = C_AreaPoiInfo.GetAreaPOIForMap(mapID) or {}
    addon:Print(string.format("|cffcc88ffDelve POI dump|r  map=%s  delves=%d  areaPOIs=%d  (GetDelvesForMap %s)",
        tostring(mapID), #delvePois, #areaPois, C_AreaPoiInfo.GetDelvesForMap and "exists" or "MISSING"))
    local pois = {}
    for _, id in ipairs(delvePois) do pois[#pois + 1] = id end
    for _, id in ipairs(areaPois) do pois[#pois + 1] = id end
    for _, poiID in ipairs(pois) do
        local info = C_AreaPoiInfo.GetAreaPOIInfo(mapID, poiID)
        if info then
            addon:Print(string.format("  |cffffd700%s|r  |cff888888(poi %s, widgetSet %s)|r",
                tostring(info.name), tostring(poiID), tostring(info.tooltipWidgetSet)))
            if info.description and info.description ~= "" then
                addon:Print("     desc: " .. info.description)
            end
            local setID = info.tooltipWidgetSet
            if setID and C_UIWidgetManager and C_UIWidgetManager.GetAllWidgetsBySetID then
                local widgets = C_UIWidgetManager.GetAllWidgetsBySetID(setID) or {}
                for _, w in ipairs(widgets) do
                    local ok, wi = pcall(C_UIWidgetManager.GetTextWithStateWidgetVisualizationInfo, w.widgetID)
                    if ok and wi and wi.text and wi.text ~= "" then
                        addon:Print("     widget: " .. wi.text)
                    end
                end
            end
        end
    end
end

function commands:DrustDelves()
    local addon, O = Private.Addon, Private.Objectives
    if not (O and O.GetDelveObjectives) then
        addon:Print("|cffff0000Objectives:GetDelveObjectives is MISSING — the game is running an OLD copy of the addon. Re-copy the files.|r")
        return
    end
    local mapID = C_Map.GetBestMapForUnit and C_Map.GetBestMapForUnit("player")
    local cont = mapID and O:GetContinentID(mapID)
    local delves = O:GetDelveObjectives(mapID)
    addon:Print(string.format("|cffcc88ffDelves debug|r  map=%s continent=%s (2274 = Khaz Algar)  delves with meta objectives: |cff00ff00%d|r",
        tostring(mapID), tostring(cont), #delves))
    for _, d in ipairs(delves) do
        addon:Print(string.format("  - %s |cff888888(%s @ %.0f,%.0f)|r  %d/%d done%s",
            d.name, d.zone, d.x, d.y, d.doneCount, d.totalCount,
            d.activeNeeded and "  |cffffd700[active story needed]|r" or ""))
        if d.activeStory and d.activeStory ~= "" then
            addon:Print("      active: " .. d.activeStory)
        end
    end
    if #delves == 0 and cont ~= 2274 then
        addon:Print("  (Delves only list on the War Within continent — be in a Khaz Algar zone.)")
    end
end

function commands:DrustRep()
    local addon, O = Private.Addon, Private.Objectives
    local mapID = C_Map.GetBestMapForUnit and C_Map.GetBestMapForUnit("player")
    local cont = mapID and O:GetContinentID(mapID)
    local ci = cont and C_Map.GetMapInfo(cont)
    addon:Print(string.format("|cffffd700Rep debug|r  map=%s  continent=%s (%s, type=%s)",
        tostring(mapID), tostring(cont), tostring(ci and ci.name), tostring(ci and ci.mapType)))
    addon:Print("  C_Reputation.GetFactionDataByIndex exists: " ..
        tostring(C_Reputation and C_Reputation.GetFactionDataByIndex ~= nil))
    addon:Print("  'Back from the Beyond' data loaded: " ..
        tostring(Private.ExpansionUtil:GetExpansionByName("Back from the Beyond") ~= nil))
    addon:Print("  raw GetNumFactions (collapsed view): " ..
        tostring(C_Reputation and C_Reputation.GetNumFactions and C_Reputation.GetNumFactions()))
    addon:Print("  C_CovenantSanctumUI.GetRenownLevel exists: " ..
        tostring(C_CovenantSanctumUI and C_CovenantSanctumUI.GetRenownLevel ~= nil) ..
        "  covenant criterion found: " .. tostring(O:GetReputationAchievement("Covenant") ~= nil))
    local reps = O:GetContinentReputations(mapID)
    addon:Print("  reputations returned: " .. #reps)
    for _, r in ipairs(reps) do
        addon:Print("    - " .. tostring(r.name) .. "  standing=" .. tostring(r.standingLabel or r.renownLevel) .. "  met=" .. tostring(r.met))
    end
end

function commands:DrustWhatsNew()
    if Private.WhatsNew then Private.WhatsNew:Show() end
    if Private.RouteHud then Private.RouteHud:ShowPreview() end
end

function commands:DrustTheater()
    if Private.TheaterTroupe then Private.TheaterTroupe:Print() end
end

function commands:DrustTheaterDebug()
    if Private.TheaterTroupe then Private.TheaterTroupe:Debug() end
end

function commands:DrustRares(input)
    local addon = Private.Addon
    if not Private.RareAlerts then return end
    input = (input or ""):lower():gsub("%s+", "")
    local s = Private.Settings:Rares()
    if input == "relevant" then
        s.onlyRelevant = not s.onlyRelevant
        Private.RareAlerts:UpdateFilterLabel()
        Private.RareAlerts:Refresh()
        addon:Print(s.onlyRelevant and "Rare alerts: showing |cff00ff00only meta-relevant|r rares."
            or "Rare alerts: showing |cff00ff00all|r nearby rares.")
        return
    end
    Private.RareAlerts:Toggle()
    if Private.RareAlerts:IsEnabled() then
        addon:Print("Rare alerts |cff00ff00enabled|r. Popup shows nearby rares; click Track to set a waypoint. (/drustrares relevant to filter to meta rares.)")
    else
        addon:Print("Rare alerts |cffff0000disabled|r.")
    end
end

function commands:DrustRewardDemo()
    if Private.RewardPreview then Private.RewardPreview:ShowDemo() end
end

function commands:DrustReward()
    local addon = Private.Addon
    if not Private.RewardPreview then return end
    Private.RewardPreview:Toggle()
    if Private.RewardPreview:IsEnabled() then
        addon:Print("Reward model preview |cff00ff00enabled|r. Click an achievement (with reward data) to preview it.")
    else
        addon:Print("Reward model preview |cffff0000disabled|r.")
    end
end

function commands:DrustExportNotes()
    local addon = Private.Addon
    if not (Private.RouteHud and Private.RouteHud.ExportNotes) then
        addon:Print("|cffff0000Notes export unavailable — the game is running an OLD copy of the addon. Re-copy the files.|r")
        return
    end
    if not Private.RouteHud.frame then Private.RouteHud:Initialize() end
    local count = Private.RouteHud:ExportNotes()
    addon:Print(string.format("Exported |cff00ff00%d|r waypoint note(s). Ctrl+A, Ctrl+C to copy the block.", count))
end

function commands:DrustExportFixes()
    local addon = Private.Addon
    if not (Private.RouteHud and Private.RouteHud.ExportFixes) then
        addon:Print("|cffff0000Fix export unavailable — the game is running an OLD copy of the addon. Re-copy the files.|r")
        return
    end
    if not Private.RouteHud.frame then Private.RouteHud:Initialize() end
    local count = Private.RouteHud:ExportFixes()
    addon:Print(string.format("Exported |cff00ff00%d|r waypoint fix(es). Ctrl+A, Ctrl+C to copy, then paste to Claude.", count))
end

function commands:DrustExportSteps()
    local addon = Private.Addon
    if not (Private.RouteHud and Private.RouteHud.ExportSteps) then
        addon:Print("|cffff0000Route-step export unavailable — the game is running an OLD copy of the addon. Re-copy the files.|r")
        return
    end
    if not Private.RouteHud.frame then Private.RouteHud:Initialize() end
    local count = Private.RouteHud:ExportSteps()
    addon:Print(string.format("Exported |cff00ff00%d|r route step(s). Ctrl+A, Ctrl+C to copy the block (keep it separate from Notes).", count))
end

function commands:DrustDebugWQ()
    local addon = Private.Addon
    if not (Private.Objectives and Private.Objectives.GetWorldQuests) then
        addon:Print("|cffff0000Objectives:GetWorldQuests is MISSING — the game is running an OLD copy of the addon (not the edited files). Check your AddOns folder / re-copy.|r")
        return
    end
    local mapID = C_Map.GetBestMapForUnit("player")
    local hasAPI = Private.Objectives:HasWorldQuestAPI()
    local wqs = Private.Objectives:GetWorldQuests(mapID)
    addon:Print(string.format("WQ debug: new code LOADED. mapID=%s, C_TaskQuest API=%s, found |cff00ff00%d|r world quests nearby.",
        tostring(mapID), tostring(hasAPI), #wqs))
    for i = 1, math.min(#wqs, 5) do
        local q = wqs[i]
        addon:Print(string.format("  - %s |cff888888(%s)|r %s", q.title, q.mapName or "?",
            Private.Objectives:FormatTimeLeft(Private.Objectives:GetQuestTimeLeft(q.questID))))
    end
    addon:Print("RouteHud loaded: " .. tostring(Private.RouteHud ~= nil) .. ", crazy-arrow WaypointsUtil:TrackPoint = " .. tostring(Private.WaypointsUtil and Private.WaypointsUtil.TrackPoint ~= nil))
end

function commands:DrustDebugDungeon()
    local on = Private.Objectives:ToggleDebugDungeon()
    Private.Objectives:InvalidateSummaries()
    if Private.NearbyHud then Private.NearbyHud:Refresh() end
    if Private.RouteHud then Private.RouteHud:Refresh() end
    if on then
        Private.Addon:Print("Debug dungeon injected at your position (Algeth'ar Academy). Open the windows to test the Find Group button. Run /drustdebugdungeon again to remove.")
    else
        Private.Addon:Print("Debug dungeon removed.")
    end
end

function commands:DrustRoute()
    Private.RouteHud:Toggle()
    local addon = Private.Addon
    if Private.RouteHud:IsEnabled() then
        addon:Print("Do-it-now Route shown. Click a step to route to it; /drustroute to hide.")
    else
        addon:Print("Do-it-now Route hidden.")
    end
end

function commands:DrustHud()
    Private.NearbyHud:Toggle()
    local addon = Private.Addon
    if Private.NearbyHud:IsEnabled() then
        addon:Print("Nearby Now HUD shown. Drag to move; /drusthud to hide.")
    else
        addon:Print("Nearby Now HUD hidden.")
    end
end

function commands:DrustMinimap()
    local addon = Private.Addon
    addon.DB.minimap.hide = not addon.DB.minimap.hide
    if addon.DB.minimap.hide then
        Private.MinimapButton:GetIcon():Hide(const.ADDON_NAME)
        addon:Print("Minimap icon hidden.")
    else
        Private.MinimapButton:GetIcon():Show(const.ADDON_NAME)
        addon:Print("Minimap icon shown.")
    end
end

function commands:DrustUI()
    Private.UI:ToggleUI()
end

function commands:DrustDebug(input)
    local achievementID = tonumber(input)
    if not achievementID then
        print("Error: Please provide a valid achievement ID. Example: /drustdebug 41205")
        return
    end

    -- Function to search for the achievement ID in a given table
    local function FindAchievementByID(partsTable, id)
        if not partsTable or type(partsTable) ~= "table" then
            return nil
        end

        for _, achievement in ipairs(partsTable) do
            if achievement.achievementID == id then
                return achievement
            end
        end
        return nil
    end

    -- Search in DrustWaypointsPart1
    local foundInPart1 = FindAchievementByID(DrustWaypointsPart1, achievementID)
    if foundInPart1 then
        print(string.format("Achievement found in DrustWaypointsPart1: %s (ID: %d)", foundInPart1.name, foundInPart1.achievementID))
    else
        print("Achievement not found in DrustWaypointsPart1.")
    end

    -- Search in DrustWaypointsPart2
    local foundInPart2 = FindAchievementByID(DrustWaypointsPart2, achievementID)
    if foundInPart2 then
        print(string.format("Achievement found in DrustWaypointsPart2: %s (ID: %d)", foundInPart2.name, foundInPart2.achievementID))
    else
        print("Achievement not found in DrustWaypointsPart2.")
    end

    -- Combine results
    if not foundInPart1 and not foundInPart2 then
        print(string.format("Achievement ID %d not found in any Waypoints data.", achievementID))
    end
end