--[[
    Commander 3.0 - Custom Commander Keybinds

    Commands: cbind, cunbind, cbinds

    Bind keys to execute Commander commands directly.
    Uses invisible buttons with SetOverrideBindingClick() for interception.
]]

local ADDON_NAME, C3 = ...

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

-------------------------------------------------------------------------------
-- Keybind Manager
-------------------------------------------------------------------------------

local KeybindManager = {}
C3.KeybindManager = KeybindManager

KeybindManager._buttons = {}
KeybindManager._initialized = false

-- Reserved keys that cannot be bound
local RESERVED_KEYS = {
    GRAVE = true,  -- Terminal toggle
}

---Normalize a key string (uppercase, CONTROL -> CTRL, etc.)
---@param key string
---@return string
local function NormalizeKey(key)
    if not key then return "" end
    return key:upper():gsub("CONTROL", "CTRL"):gsub("COMMAND", "META")
end

---Check if a key is reserved
---@param key string
---@return boolean
local function IsReservedKey(key)
    return RESERVED_KEYS[NormalizeKey(key)] == true
end

---Get a unique button name for a key
---@param key string
---@return string
local function GetButtonName(key)
    return "C3Bind_" .. key:gsub("-", "_"):gsub("%+", "PLUS")
end

---Create or get a keybind button for a key
---@param key string
---@return Frame?
function KeybindManager:GetOrCreateButton(key)
    local normalizedKey = NormalizeKey(key)

    -- Guard: Return existing
    if self._buttons[normalizedKey] then
        return self._buttons[normalizedKey]
    end

    -- Create new button
    local button = CreateFrame("Button", GetButtonName(normalizedKey), UIParent, "SecureActionButtonTemplate")
    button:SetSize(1, 1)
    button:SetPoint("TOPLEFT", UIParent, "TOPLEFT", -100, 100)
    button:Hide()
    button.boundKey = normalizedKey

    button:SetScript("PostClick", function(btn)
        local session = C3:GetSession()
        local command = session:GetKeybind(btn.boundKey)

        -- Guard: No command
        if not command or command == "" then return end

        C3:Debug("Keybind: Executing: " .. command)

        -- Open terminal unless command ends with "exit"
        local terminal = C3.UI and C3.UI.Terminal
        if terminal and not command:match("exit%s*$") and not terminal:IsShown() then
            terminal:Show()
        end

        C3:Execute(command)
    end)

    self._buttons[normalizedKey] = button
    return button
end

---Apply a keybind (create button and set override binding)
---@param key string
---@param command string
---@return boolean success
---@return string? error
function KeybindManager:ApplyKeybind(key, command)
    local normalizedKey = NormalizeKey(key)

    -- Guard: Reserved key
    if IsReservedKey(normalizedKey) then
        return false, "Key is reserved: " .. normalizedKey
    end

    -- Guard: Button creation failed
    local button = self:GetOrCreateButton(normalizedKey)
    if not button then
        return false, "Failed to create keybind button"
    end

    -- Clear any existing WoW binding
    local existingAction = GetBindingAction(normalizedKey)
    if existingAction and existingAction ~= "" then
        SetBinding(normalizedKey, nil)
        C3:Debug("Keybind: Cleared WoW binding: " .. existingAction)
    end

    SetOverrideBindingClick(button, true, normalizedKey, button:GetName())
    C3:Debug("Keybind: Applied " .. normalizedKey .. " -> " .. command)
    return true
end

---Remove a keybind
---@param key string
function KeybindManager:RemoveKeybind(key)
    local button = self._buttons[NormalizeKey(key)]

    -- Guard: No button
    if not button then return end

    ClearOverrideBindings(button)
    C3:Debug("Keybind: Removed " .. NormalizeKey(key))
end

---Apply all keybinds from session
function KeybindManager:ApplyAllFromSession()
    -- Don't trigger session creation - just check if one exists
    local session = C3.SessionManager and C3.SessionManager._activeSession
    if not session then return end

    for key, command in pairs(session:GetKeybinds()) do
        self:ApplyKeybind(key, command)
    end
end

---Clear all override bindings
function KeybindManager:ClearAll()
    for _, button in pairs(self._buttons) do
        ClearOverrideBindings(button)
    end
end

---Initialize the keybind manager
function KeybindManager:Initialize()
    -- Guard: Already initialized
    if self._initialized then return end

    self:ApplyAllFromSession()
    self._initialized = true
    C3:Debug("KeybindManager initialized")
end

-------------------------------------------------------------------------------
-- CommanderKeybind Type
-------------------------------------------------------------------------------

C3.Types:Register("CommanderKeybind", {
    description = "Commander keybind mapping",
    source = "cbinds",
    properties = {
        { "key", "Key combination" },
        { "command", "Commander command" },
    },
    format = function(self)
        return string.format("%s %s",
            Colorize(string.format("%-15s", self.key), "accent.success"),
            Colorize(self.command, "accent.warning")
        )
    end,
})

-------------------------------------------------------------------------------
-- cbinds - List Commander keybinds
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "cbinds",
    description = "List all Commander keybinds",
    category = "utility",
    acceptsInput = false,
    execute = function(ctx)
        local keybinds = ctx.session:GetKeybinds()

        -- Guard: No keybinds
        if not next(keybinds) then
            return ctx:Text(Colorize("No Commander keybinds configured", "status.dim") ..
                "\nUse " .. Colorize("cbind <key> <command>", "accent.primary") .. " to create one")
        end

        local results = {}
        for key, command in pairs(keybinds) do
            results[#results + 1] = C3.Types.Create("CommanderKeybind", {
                key = key,
                command = command,
            })
        end

        table.sort(results, function(a, b) return a.key < b.key end)
        return results
    end
})

-------------------------------------------------------------------------------
-- cbind - Create a Commander keybind
-------------------------------------------------------------------------------

-- Key suggestions for intellisense
local KEY_SUGGESTIONS = {
    { text = "F1", description = "Function key 1" },
    { text = "F2", description = "Function key 2" },
    { text = "F3", description = "Function key 3" },
    { text = "F4", description = "Function key 4" },
    { text = "F5", description = "Function key 5" },
    { text = "F6", description = "Function key 6" },
    { text = "F7", description = "Function key 7" },
    { text = "F8", description = "Function key 8" },
    { text = "CTRL-F1", description = "Ctrl + F1" },
    { text = "CTRL-F2", description = "Ctrl + F2" },
    { text = "ALT-F1", description = "Alt + F1" },
    { text = "ALT-F2", description = "Alt + F2" },
    { text = "SHIFT-F1", description = "Shift + F1" },
    { text = "CTRL-M", description = "Ctrl + M" },
    { text = "CTRL-G", description = "Ctrl + G" },
}

C3.Command:Define({
    name = "cbind",
    description = "Bind a key to execute a Commander command",
    category = "utility",
    acceptsInput = false,
    args = {
        {
            name = "key",
            required = true,
            description = "Key to bind (e.g., F1, CTRL-M, ALT-G)",
            provider = function(partial)
                return C3.Intellisense.fuzzyFilter(KEY_SUGGESTIONS, partial, function(item)
                    return item.text
                end)
            end
        },
        {
            name = "command",
            required = false,
            vararg = true,
            description = "Commander command to execute",
            provider = "Commands"
        }
    },
    execute = function(ctx)
        local key = ctx:GetArg(1)

        -- Guard: No key
        if not key or key == "" then
            return ctx:Error("Usage: cbind <key> <command>\nExample: cbind F5 mounts | where isFlying | first")
        end

        local normalizedKey = NormalizeKey(key)
        local args = ctx:GetArgs()
        local session = ctx.session

        -- Query mode: only key provided
        if #args == 1 then
            local existing = session:GetKeybind(normalizedKey)

            -- Guard: Not bound
            if not existing then
                return ctx:Text(string.format("%s is %s",
                    Colorize(normalizedKey, "accent.warning"),
                    Colorize("not bound", "status.dim")
                ))
            end

            return C3.Types.Create("CommanderKeybind", {
                key = normalizedKey,
                command = existing,
            })
        end

        -- Build command from args 2+
        local command = table.concat(args, " ", 2)

        -- Guard: Empty command
        if command == "" then
            return ctx:Error("Usage: cbind <key> <command>")
        end

        -- Guard: Reserved key
        if IsReservedKey(normalizedKey) then
            return ctx:Error("Cannot bind reserved key: " .. normalizedKey)
        end

        local existingCmd = session:GetKeybind(normalizedKey)

        -- Set and apply
        session:SetKeybind(normalizedKey, command)
        local ok, err = KeybindManager:ApplyKeybind(normalizedKey, command)

        -- Guard: Apply failed
        if not ok then
            return ctx:Error("Failed to apply keybind: " .. (err or "unknown error"))
        end

        -- Success message
        local verb = existingCmd and "Rebound" or "Bound"
        local suffix = existingCmd and (" " .. Colorize("(was: " .. existingCmd .. ")", "status.dim")) or ""

        return ctx:Text(string.format("%s %s to %s%s",
            Colorize(verb, "accent.success"),
            Colorize(normalizedKey, "accent.warning"),
            Colorize(command, "accent.primary"),
            suffix
        ))
    end
})

-------------------------------------------------------------------------------
-- cunbind - Remove a Commander keybind
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "cunbind",
    description = "Remove a Commander keybind",
    category = "utility",
    acceptsInput = false,
    args = {
        {
            name = "key",
            required = true,
            description = "Key to unbind",
            provider = function(partial, ctx)
                local suggestions = {}
                for key, command in pairs(ctx.session:GetKeybinds()) do
                    suggestions[#suggestions + 1] = { text = key, description = command }
                end
                return C3.Intellisense.fuzzyFilter(suggestions, partial, function(item)
                    return item.text
                end)
            end
        }
    },
    execute = function(ctx)
        local key = ctx:GetArg(1)

        -- Guard: No key
        if not key or key == "" then
            return ctx:Error("Usage: cunbind <key>")
        end

        local normalizedKey = NormalizeKey(key)
        local command = ctx.session:GetKeybind(normalizedKey)

        -- Guard: Not bound
        if not command then
            return ctx:Error("Key not bound: " .. normalizedKey)
        end

        ctx.session:RemoveKeybind(normalizedKey)
        KeybindManager:RemoveKeybind(normalizedKey)

        return ctx:Text(string.format("%s %s %s",
            Colorize("Unbound", "accent.success"),
            Colorize(normalizedKey, "accent.warning"),
            Colorize("(was: " .. command .. ")", "status.dim")
        ))
    end
})

-------------------------------------------------------------------------------
-- Initialize on PLAYER_LOGIN
-------------------------------------------------------------------------------

local initFrame = CreateFrame("Frame")
initFrame:RegisterEvent("PLAYER_LOGIN")
initFrame:SetScript("OnEvent", function(_, event)
    if event ~= "PLAYER_LOGIN" then return end

    C_Timer.After(0.5, function()
        KeybindManager:Initialize()
    end)
end)

return true
