--[[
    Core/Commands.lua

    Slash command surface: /shiny and /shinyhunter.

    Destructive actions never run on the first invocation. "/shiny reset" explains
    what it would erase and requires "/shiny reset confirm" to go through.

    The two SLASH_* variables and the SlashCmdList entry are globals because
    that is the interface Blizzard exposes; they are the only names this addon
    adds to _G besides the saved variables table and the main window frame.
]]

local _, ns = ...

local Commands = ns.NewModule("Commands")
ns.Commands = Commands

local handlers = {}

local function PrintHelp()
    local L = ns.L

    ns.Print(L["CMD_HELP_HEADER"])
    ns.Print(L["CMD_HELP_OPEN"])
    ns.Print(L["CMD_HELP_DEBUG"])
    ns.Print(L["CMD_HELP_DUMP"])
    ns.Print(L["CMD_HELP_VERSION"])
    ns.Print(L["CMD_HELP_VALIDATE"])
    ns.Print(L["CMD_HELP_STABLE"])
    ns.Print(L["CMD_HELP_SIGHTINGS"])
    ns.Print(L["CMD_HELP_SHARE"])
    ns.Print(L["CMD_HELP_PORTAL"])
    ns.Print(L["CMD_HELP_RESET"])
    ns.Print(L["CMD_HELP_HELP"])
end

handlers.help = PrintHelp
handlers["?"] = PrintHelp

handlers.debug = function(argument, extra)
    if argument == "dump" then
        ns.Debug:Dump(tonumber(extra))
        return
    end

    -- Not stored: diagnostics last for this session only. See Config.lua.
    local enabled = not ns.Debug:IsEnabled()
    ns.Debug:SetEnabled(enabled)
    ns.Print(enabled and ns.L["DEBUG_ON"] or ns.L["DEBUG_OFF"])
end

-- Recording a travel link takes two calls with a journey in between, so the
-- command is the same one both times. "cancel" drops a departure noted by
-- mistake.
handlers.portal = function(argument)
    if argument == "cancel" then
        ns.Navigation.TravelGraph:CancelRecording()
        return
    end

    ns.Navigation.TravelGraph:RecordStep(argument ~= "" and argument or nil)
end

handlers.version = function()
    local clientVersion, _, _, interfaceVersion = GetBuildInfo()
    ns.Print(ns.L["VERSION_INFO"]:format(ns.version, clientVersion, tostring(interfaceVersion)))
end

handlers.validate = function()
    ns.Data.Validator:PrintReport()
    ns.Data.Tests:PrintResults()
end

handlers.sightings = function()
    ns.Collection.SightingTracker:PrintReport()
end

handlers.stable = function()
    ns.Collection.StableScanner:PrintReport()
end

-- Field notes, in a form that can leave the machine.
--
-- The catalogue will never be finished: a zone released last week has no wiki
-- page and no published coordinates, so for those creatures the player who
-- walked past one is the only survey there is. The addon has been recording
-- exactly that all along and had no way to hand it back.
--
-- Nothing is sent anywhere. This builds text and shows it selected; where it
-- goes next is the player's decision.
local function BuildFieldReport()
    local lines = {}
    local clientVersion = select(1, GetBuildInfo())

    lines[#lines + 1] = ("# ShinyHunter %s | %s | wow %s"):format(
        tostring(ns.version), GetLocale(), tostring(clientVersion))

    local sightings = ns.Collection.SightingTracker:GetSightings()
    local ordered = {}
    for creatureID in pairs(sightings) do
        ordered[#ordered + 1] = creatureID
    end
    table.sort(ordered)

    if #ordered > 0 then
        lines[#lines + 1] = "# unlisted: npcID|name|family|class|mapID|x|y"
        for _, creatureID in ipairs(ordered) do
            local seen = sightings[creatureID]
            lines[#lines + 1] = ("%d|%s|%s|%s|%s|%s|%s"):format(
                creatureID,
                tostring(seen.name),
                tostring(seen.family),
                tostring(seen.classification),
                tostring(seen.mapID or ""),
                seen.x and ("%.4f"):format(seen.x) or "",
                seen.y and ("%.4f"):format(seen.y) or "")
        end
    end

    -- Display ids the client knew and the shipped data did not. These are the
    -- portraits missing from brand new entries.
    local learned = ns.Collection.ClientFacts:GetLearnedDisplayIDs()
    local withDisplay = {}
    for creatureID in pairs(learned) do
        if not (ns.Data.RareCatalog:Get(creatureID) or {}).displayIDs then
            withDisplay[#withDisplay + 1] = creatureID
        end
    end
    table.sort(withDisplay)

    if #withDisplay > 0 then
        lines[#lines + 1] = "# displays: npcID|displayID"
        for _, creatureID in ipairs(withDisplay) do
            lines[#lines + 1] = ("%d|%d"):format(creatureID, learned[creatureID])
        end
    end

    return table.concat(lines, "\n"), #ordered + #withDisplay
end

handlers.share = function()
    local text, count = BuildFieldReport()
    if count == 0 then
        ns.Print(ns.L["SHARE_EMPTY"])
        return
    end

    ns.UI.ShareWindow:Show(text, ns.L["SHARE_INTRO"]:format(count))
end

handlers.reset = function(argument)
    if argument ~= "confirm" then
        ns.Print(ns.L["RESET_CONFIRM"])
        return
    end

    ns.Database:ResetAll()
    ns.Config:ApplyStoredSettings()
    ns.Print(ns.L["RESET_DONE"])
end

local function Dispatch(input)
    -- Split into at most three words. Anything beyond is ignored rather than
    -- failing to match, so a fat-fingered "/shiny debug dump 20 please" still
    -- does the sensible thing.
    local command, rest = input:lower():match("^%s*(%S*)%s*(.-)%s*$")
    local argument, extra = rest:match("^(%S*)%s*(%S*)")

    if command == "" then
        ns.UI.MainWindow:Toggle()
        return
    end

    local handler = handlers[command]
    if handler then
        handler(argument, extra)
    else
        ns.Print(ns.L["CMD_UNKNOWN"]:format(command))
    end
end

--- Installs the slash commands. Called once during start-up.
function Commands:OnInitialize()
    SLASH_SHINYHUNTER1 = "/shiny"
    SLASH_SHINYHUNTER2 = "/shinyhunter"

    SlashCmdList["SHINYHUNTER"] = function(input)
        Dispatch(input or "")
    end

    ns.Debug:Log("CMD", "slash commands registered")
end
