--[[
============================================================================
ETB v12.40.0 - CommandRegistry.lua
============================================================================
Registry-table dispatcher for /etb slash commands.

Replaces the old if/elseif chain in Commands.lua. Commands register themselves
by calling ETB.CommandRegistry:Register{name, aliases, handler, help, user}.

Dispatch is O(1) lookup (single hash-map query) instead of the old
O(n_commands) linear scan through 50+ elseif branches.

Public API:
  ETB.CommandRegistry:Register(spec)    — register a command
  ETB.CommandRegistry:Dispatch(cmd,args) — run the handler for a command
  ETB.CommandRegistry:IsUserCommand(cmd) — check if a command is user-facing
                                           (not gated behind ETB.debug)
  ETB.CommandRegistry:GetHelpLines()     — for /etb help
  ETB.CommandRegistry:List()             — all registered command names (debug)

Spec format:
  {
    name = "version",              -- primary command word
    aliases = {"ver"},              -- optional alias list
    handler = function(args) ... end,
    help = "Show version info",    -- optional; shown in /etb help if .user
    user = true,                    -- if true: always available; else debug-gated
  }
============================================================================
]]--

local ADDON_NAME, NS = ...
local ETB = NS.ETB

local CR = {}
ETB.CommandRegistry = CR

-- name/alias -> spec (shared table so lookup works for both)
CR._byName = {}
-- Ordered list of primary specs (for /etb help and deterministic iteration)
CR._ordered = {}

-- ============================================================================
-- Registration
-- ============================================================================

function CR:Register(spec)
    if type(spec) ~= "table" or type(spec.name) ~= "string" or type(spec.handler) ~= "function" then
        error("CommandRegistry:Register requires {name=string, handler=function, ...}", 2)
    end
    if self._byName[spec.name] then
        -- Last-writer-wins would silently break things; fail loudly instead.
        error("CommandRegistry: duplicate command name '" .. spec.name .. "'", 2)
    end

    self._byName[spec.name] = spec
    self._ordered[#self._ordered + 1] = spec

    if spec.aliases then
        for _, alias in ipairs(spec.aliases) do
            if self._byName[alias] then
                error("CommandRegistry: alias '" .. alias .. "' conflicts with existing command", 2)
            end
            self._byName[alias] = spec
        end
    end
end

-- ============================================================================
-- Dispatch
-- ============================================================================

-- Returns true if a handler was found and called, false otherwise.
-- The caller (Commands.lua) uses the false return to print an unknown-
-- command message. As of v12.51.0 all commands are registered here;
-- the legacy if/elseif chain was fully removed.
function CR:Dispatch(cmd, args)
    local spec = self._byName[cmd]
    if not spec then return false end
    spec.handler(args)
    return true
end

-- ============================================================================
-- User-command gating (replaces the old USER_COMMANDS table)
-- ============================================================================

function CR:IsUserCommand(cmd)
    local spec = self._byName[cmd]
    return spec ~= nil and spec.user == true
end

-- ============================================================================
-- Help + introspection
-- ============================================================================

function CR:GetHelpLines()
    local lines = {}
    for _, spec in ipairs(self._ordered) do
        if spec.user and spec.help then
            local cmdStr = "/etb " .. spec.name
            if spec.aliases and #spec.aliases > 0 then
                cmdStr = cmdStr .. " (" .. table.concat(spec.aliases, ", ") .. ")"
            end
            lines[#lines + 1] = "|cffffd100" .. cmdStr .. "|r — " .. spec.help
        end
    end
    return lines
end

-- Developer/audit listing for /etb dev. Mirrors GetHelpLines but for the
-- debug-gated commands (user ~= true). Includes the name even when a command
-- carries no help text, so the dev toolbox is fully discoverable in debug mode.
function CR:GetDevHelpLines()
    local lines = {}
    for _, spec in ipairs(self._ordered) do
        if not spec.user then
            local cmdStr = "/etb " .. spec.name
            if spec.aliases and #spec.aliases > 0 then
                cmdStr = cmdStr .. " (" .. table.concat(spec.aliases, ", ") .. ")"
            end
            local tail = spec.help and (" — " .. spec.help) or ""
            lines[#lines + 1] = "|cff00ccff" .. cmdStr .. "|r" .. tail
        end
    end
    return lines
end

function CR:List()
    local names = {}
    for _, spec in ipairs(self._ordered) do
        names[#names + 1] = spec.name
    end
    return names
end

-- ============================================================================
-- Self-test (debug mode only)
-- ============================================================================

if ETB and ETB.debug then
    local function _selftest()
        -- Build a temporary registry (don't pollute CR)
        local T = setmetatable({_byName={}, _ordered={}}, {__index = CR})

        -- Register a valid command
        T:Register{name="foo", handler=function() end, help="foo help", user=true}
        assert(T._byName.foo, "foo registered")
        assert(T:IsUserCommand("foo"), "foo is user command")

        -- Aliases
        T:Register{name="bar", aliases={"b", "baz"}, handler=function() end, user=false}
        assert(T._byName.bar, "bar registered")
        assert(T._byName.b == T._byName.bar, "alias b points to bar spec")
        assert(T._byName.baz == T._byName.bar, "alias baz points to bar spec")
        assert(not T:IsUserCommand("bar"), "bar is NOT user command (debug-gated)")

        -- Dispatch
        local called
        T:Register{name="run", handler=function(args) called = args end, help="run it", user=true}
        local ok = T:Dispatch("run", {"arg1", "arg2"})
        assert(ok == true, "dispatch returns true on hit")
        assert(called[1] == "arg1", "handler received args")

        -- Missing command
        assert(T:Dispatch("nonexistent", {}) == false, "dispatch returns false on miss")

        -- Help lines — foo and run are user-facing with help text; bar is debug
        local help = T:GetHelpLines()
        assert(#help == 2, "help has 2 lines (foo and run; bar is debug-gated)")

        -- Duplicate registration errors
        local okDup = pcall(function() T:Register{name="foo", handler=function() end} end)
        assert(not okDup, "duplicate name raises error")

        print("|cff00ff00[ETB CommandRegistry] All 8 self-tests passed.|r")
    end

    local ok, err = pcall(_selftest)
    if not ok then
        print("|cffff0000[ETB CommandRegistry] SELF-TEST FAILED: " .. tostring(err) .. "|r")
    end
end
