--------------------------------------------------------------------------------
-- Nanami-UI: KeyBind Manager
-- Profile-based keybinding management (save/load/delete/rename/export/import)
-- Covers ALL keybindings, not just action bars.
--------------------------------------------------------------------------------

SFrames.KeyBindManager = {}

local KBM = SFrames.KeyBindManager

--------------------------------------------------------------------------------
-- Data helpers
--------------------------------------------------------------------------------

local function EnsureDB()
    if not SFramesGlobalDB then SFramesGlobalDB = {} end
    if not SFramesGlobalDB.KeyBindProfiles then SFramesGlobalDB.KeyBindProfiles = {} end
end

local function GetCharName()
    return UnitName("player") or "Unknown"
end

local function GetTimestamp()
    return time and time() or 0
end

--------------------------------------------------------------------------------
-- Collect / Apply bindings
--------------------------------------------------------------------------------

function KBM:CollectAllBindings()
    local result = {}
    local n = GetNumBindings()
    for i = 1, n do
        local command, key1, key2 = GetBinding(i)
        if command and (key1 or key2) then
            table.insert(result, {
                command = command,
                key1 = key1,
                key2 = key2,
            })
        end
    end
    return result
end

function KBM:ClearAllBindings()
    local n = GetNumBindings()
    for i = 1, n do
        local command, key1, key2 = GetBinding(i)
        if key2 then SetBinding(key2, nil) end
        if key1 then SetBinding(key1, nil) end
    end
end

function KBM:ApplyBindings(data)
    if not data or type(data) ~= "table" then return false end

    self:ClearAllBindings()

    local applied = 0
    for _, entry in ipairs(data) do
        if entry.command then
            if entry.key1 then
                SetBinding(entry.key1, entry.command)
                applied = applied + 1
            end
            if entry.key2 then
                SetBinding(entry.key2, entry.command)
                applied = applied + 1
            end
        end
    end

    SaveBindings(2)

    if SFrames.ActionBars and SFrames.ActionBars.RefreshAllHotkeys then
        SFrames.ActionBars:RefreshAllHotkeys()
    end

    return true, applied
end

--------------------------------------------------------------------------------
-- Base64 encode / decode (pure Lua 5.0 compatible)
--------------------------------------------------------------------------------

local B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

local function Base64Encode(src)
    if not src or src == "" then return "" end
    local out = {}
    local len = string.len(src)
    local i = 1
    while i <= len do
        local a = string.byte(src, i)
        local b = (i + 1 <= len) and string.byte(src, i + 1) or 0
        local c = (i + 2 <= len) and string.byte(src, i + 2) or 0
        local remaining = len - i + 1

        local n = a * 65536 + b * 256 + c

        local c1 = math.floor(n / 262144)
        local c2 = math.floor(math.mod(n, 262144) / 4096)
        local c3 = math.floor(math.mod(n, 4096) / 64)
        local c4 = math.mod(n, 64)

        table.insert(out, string.sub(B64, c1 + 1, c1 + 1))
        table.insert(out, string.sub(B64, c2 + 1, c2 + 1))
        if remaining >= 2 then
            table.insert(out, string.sub(B64, c3 + 1, c3 + 1))
        else
            table.insert(out, "=")
        end
        if remaining >= 3 then
            table.insert(out, string.sub(B64, c4 + 1, c4 + 1))
        else
            table.insert(out, "=")
        end

        i = i + 3
    end
    return table.concat(out)
end

local B64_INV = {}
for i = 1, 64 do
    B64_INV[string.sub(B64, i, i)] = i - 1
end

local function Base64Decode(src)
    if not src or src == "" then return "" end
    src = string.gsub(src, "%s+", "")
    local out = {}
    local len = string.len(src)
    local i = 1
    while i <= len do
        local v1 = B64_INV[string.sub(src, i, i)] or 0
        local v2 = B64_INV[string.sub(src, i + 1, i + 1)] or 0
        local v3 = B64_INV[string.sub(src, i + 2, i + 2)]
        local v4 = B64_INV[string.sub(src, i + 3, i + 3)]

        local n = v1 * 262144 + v2 * 4096 + (v3 or 0) * 64 + (v4 or 0)

        table.insert(out, string.char(math.floor(n / 65536)))
        if v3 then
            table.insert(out, string.char(math.floor(math.mod(n, 65536) / 256)))
        end
        if v4 then
            table.insert(out, string.char(math.mod(n, 256)))
        end

        i = i + 4
    end
    return table.concat(out)
end

--------------------------------------------------------------------------------
-- Serialization (export/import text format, Base64 encoded)
--------------------------------------------------------------------------------

local EXPORT_PREFIX = "!NKB1!"
local SEP = "\t"

local function EncodeRaw(data)
    local lines = {}
    for _, entry in ipairs(data) do
        local line = entry.command
        if entry.key1 then
            line = line .. SEP .. entry.key1
        else
            line = line .. SEP
        end
        if entry.key2 then
            line = line .. SEP .. entry.key2
        end
        table.insert(lines, line)
    end
    return table.concat(lines, "\n")
end

local function DecodeRaw(raw)
    local data = {}
    for line in string.gfind(raw .. "\n", "(.-)\n") do
        line = string.gsub(line, "\r", "")
        if line ~= "" and string.sub(line, 1, 1) ~= "#" then
            local parts = {}
            for part in string.gfind(line .. SEP, "(.-)" .. SEP) do
                table.insert(parts, part)
            end
            local command = parts[1]
            if command and command ~= "" then
                local key1 = parts[2]
                local key2 = parts[3]
                if key1 == "" then key1 = nil end
                if key2 == "" then key2 = nil end
                if key1 or key2 then
                    table.insert(data, {
                        command = command,
                        key1 = key1,
                        key2 = key2,
                    })
                end
            end
        end
    end
    return data
end

function KBM:SerializeBindings(data)
    if not data then data = self:CollectAllBindings() end
    local raw = EncodeRaw(data)
    return EXPORT_PREFIX .. Base64Encode(raw)
end

function KBM:DeserializeBindings(text)
    if not text or text == "" then return nil, "文本为空" end
    text = string.gsub(text, "^%s+", "")
    text = string.gsub(text, "%s+$", "")

    if string.len(text) == 0 then return nil, "文本为空" end

    local raw
    if string.sub(text, 1, string.len(EXPORT_PREFIX)) == EXPORT_PREFIX then
        local encoded = string.sub(text, string.len(EXPORT_PREFIX) + 1)
        raw = Base64Decode(encoded)
        if not raw or raw == "" then return nil, "解码失败" end
    else
        raw = text
    end

    local data = DecodeRaw(raw)
    if table.getn(data) == 0 then return nil, "未找到有效的绑定数据" end
    return data
end

--------------------------------------------------------------------------------
-- Profile CRUD
--------------------------------------------------------------------------------

function KBM:SaveProfile(name)
    if not name or name == "" then return false, "方案名不能为空" end
    EnsureDB()
    local data = self:CollectAllBindings()
    SFramesGlobalDB.KeyBindProfiles[name] = {
        timestamp = GetTimestamp(),
        charName = GetCharName(),
        bindings = data,
    }
    SFrames:Print("按键绑定方案已保存: |cffffd100" .. name .. "|r (" .. table.getn(data) .. " 条)")
    return true
end

function KBM:LoadProfile(name)
    if not name or name == "" then return false, "方案名不能为空" end
    EnsureDB()
    local profile = SFramesGlobalDB.KeyBindProfiles[name]
    if not profile then return false, "方案不存在: " .. name end

    local ok, count = self:ApplyBindings(profile.bindings)
    if ok then
        SFrames:Print("按键绑定方案已加载: |cffffd100" .. name .. "|r (" .. count .. " 个按键)")
    end
    return ok
end

function KBM:DeleteProfile(name)
    if not name or name == "" then return false end
    EnsureDB()
    if not SFramesGlobalDB.KeyBindProfiles[name] then return false end
    SFramesGlobalDB.KeyBindProfiles[name] = nil
    SFrames:Print("按键绑定方案已删除: |cffffd100" .. name .. "|r")
    return true
end

function KBM:RenameProfile(oldName, newName)
    if not oldName or oldName == "" or not newName or newName == "" then
        return false, "名称不能为空"
    end
    EnsureDB()
    local profiles = SFramesGlobalDB.KeyBindProfiles
    if not profiles[oldName] then return false, "方案不存在" end
    if profiles[newName] then return false, "目标名称已存在" end

    profiles[newName] = profiles[oldName]
    profiles[oldName] = nil
    SFrames:Print("按键绑定方案已重命名: |cffffd100" .. oldName .. "|r -> |cffffd100" .. newName .. "|r")
    return true
end

function KBM:GetProfileList()
    EnsureDB()
    local list = {}
    for name, _ in pairs(SFramesGlobalDB.KeyBindProfiles) do
        table.insert(list, name)
    end
    table.sort(list)
    return list
end

function KBM:GetProfileInfo(name)
    EnsureDB()
    local p = SFramesGlobalDB.KeyBindProfiles[name]
    if not p then return nil end
    return {
        name = name,
        charName = p.charName or "?",
        timestamp = p.timestamp or 0,
        count = p.bindings and table.getn(p.bindings) or 0,
    }
end

--------------------------------------------------------------------------------
-- Export / Import Dialog UI
--------------------------------------------------------------------------------

local exportFrame, importFrame

local function CreateDialogFrame(name, titleText, width, height)
    local f = CreateFrame("Frame", name, UIParent)
    f:SetWidth(width)
    f:SetHeight(height)
    f:SetPoint("CENTER", UIParent, "CENTER", 0, 80)
    f:SetFrameStrata("TOOLTIP")
    f:SetFrameLevel(200)
    f:SetToplevel(true)
    f:EnableMouse(true)
    f:SetMovable(true)
    f:SetClampedToScreen(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", function() this:StartMoving() end)
    f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)

    if SFrames and SFrames.CreateBackdrop then
        SFrames:CreateBackdrop(f)
    else
        f:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Buttons\\WHITE8X8",
            tile = false, edgeSize = 1,
            insets = { left = 1, right = 1, top = 1, bottom = 1 },
        })
        f:SetBackdropColor(0.08, 0.07, 0.1, 0.96)
        f:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.9)
    end

    local font = (SFrames and SFrames.GetFont) and SFrames:GetFont() or "Fonts\\ARIALN.TTF"

    local title = f:CreateFontString(nil, "OVERLAY")
    title:SetFont(font, 13, "OUTLINE")
    title:SetPoint("TOP", f, "TOP", 0, -10)
    title:SetText(titleText)
    local _T = SFrames.ActiveTheme
    if _T and _T.title then
        title:SetTextColor(_T.title[1], _T.title[2], _T.title[3])
    else
        title:SetTextColor(1, 0.82, 0)
    end
    f.title = title

    local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
    close:SetPoint("TOPRIGHT", f, "TOPRIGHT", -2, -2)
    close:SetWidth(20)
    close:SetHeight(20)

    return f, font
end

local function CreateThemedButton(parent, text, x, y, width, height, onClick)
    local name = "SFramesKBM_Btn_" .. string.gsub(text, "%s", "") .. "_" .. tostring(math.random(10000, 99999))
    local btn = CreateFrame("Button", name, parent, "UIPanelButtonTemplate")
    btn:SetWidth(width)
    btn:SetHeight(height)
    btn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    btn:SetText(text)
    btn:SetScript("OnClick", onClick)

    local font = (SFrames and SFrames.GetFont) and SFrames:GetFont() or "Fonts\\ARIALN.TTF"
    local _T = SFrames.ActiveTheme
    if not _T then return btn end

    local function HideBtnTex(tex)
        if not tex then return end
        if tex.SetTexture then tex:SetTexture(nil) end
        if tex.SetAlpha then tex:SetAlpha(0) end
        if tex.Hide then tex:Hide() end
    end
    HideBtnTex(btn:GetNormalTexture())
    HideBtnTex(btn:GetPushedTexture())
    HideBtnTex(btn:GetHighlightTexture())
    HideBtnTex(btn:GetDisabledTexture())
    for _, sfx in ipairs({"Left","Right","Middle"}) do
        local t = _G[name .. sfx]
        if t then t:SetAlpha(0); t:Hide() end
    end
    btn:SetBackdrop({
        bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 14,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    btn:SetBackdropColor(_T.btnBg[1], _T.btnBg[2], _T.btnBg[3], _T.btnBg[4])
    btn:SetBackdropBorderColor(_T.btnBorder[1], _T.btnBorder[2], _T.btnBorder[3], _T.btnBorder[4])
    local fs = btn:GetFontString()
    if fs then
        fs:SetFont(font, 11, "OUTLINE")
        fs:SetTextColor(_T.btnText[1], _T.btnText[2], _T.btnText[3])
    end
    btn:SetScript("OnEnter", function()
        this:SetBackdropColor(_T.btnHoverBg[1], _T.btnHoverBg[2], _T.btnHoverBg[3], _T.btnHoverBg[4])
        this:SetBackdropBorderColor(_T.btnHoverBd[1], _T.btnHoverBd[2], _T.btnHoverBd[3], _T.btnHoverBd[4])
        local t = this:GetFontString()
        if t then t:SetTextColor(_T.btnActiveText[1], _T.btnActiveText[2], _T.btnActiveText[3]) end
    end)
    btn:SetScript("OnLeave", function()
        this:SetBackdropColor(_T.btnBg[1], _T.btnBg[2], _T.btnBg[3], _T.btnBg[4])
        this:SetBackdropBorderColor(_T.btnBorder[1], _T.btnBorder[2], _T.btnBorder[3], _T.btnBorder[4])
        local t = this:GetFontString()
        if t then t:SetTextColor(_T.btnText[1], _T.btnText[2], _T.btnText[3]) end
    end)
    btn:SetScript("OnMouseDown", function()
        this:SetBackdropColor(_T.btnDownBg[1], _T.btnDownBg[2], _T.btnDownBg[3], _T.btnDownBg[4])
    end)
    btn:SetScript("OnMouseUp", function()
        this:SetBackdropColor(_T.btnHoverBg[1], _T.btnHoverBg[2], _T.btnHoverBg[3], _T.btnHoverBg[4])
    end)

    return btn
end

function KBM:ShowExportDialog()
    local text = self:SerializeBindings()

    if not exportFrame then
        local f, font = CreateDialogFrame("SFramesKBMExport", "|cffffcc00导出按键绑定|r", 480, 340)

        local desc = f:CreateFontString(nil, "OVERLAY")
        desc:SetFont(font, 10, "OUTLINE")
        desc:SetPoint("TOP", f.title, "BOTTOM", 0, -4)
        desc:SetText("已编码为字符串，Ctrl+A 全选，Ctrl+C 复制")
        desc:SetTextColor(0.7, 0.7, 0.7)

        local sf = CreateFrame("ScrollFrame", "SFramesKBMExportScroll", f, "UIPanelScrollFrameTemplate")
        sf:SetPoint("TOPLEFT", f, "TOPLEFT", 12, -48)
        sf:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -32, 42)

        local edit = CreateFrame("EditBox", "SFramesKBMExportEdit", sf)
        edit:SetWidth(420)
        edit:SetHeight(200)
        edit:SetMultiLine(true)
        edit:SetFont(font, 10, "OUTLINE")
        edit:SetAutoFocus(false)
        edit:SetScript("OnEscapePressed", function() f:Hide() end)
        sf:SetScrollChild(edit)
        f.edit = edit

        CreateThemedButton(f, "关闭", 190, -306, 100, 26, function()
            f:Hide()
        end)

        exportFrame = f
    end

    exportFrame.edit:SetText(text)
    exportFrame:Show()
    exportFrame.edit:HighlightText()
    exportFrame.edit:SetFocus()
end

function KBM:ShowImportDialog()
    if not importFrame then
        local f, font = CreateDialogFrame("SFramesKBMImport", "|cffffcc00导入按键绑定|r", 480, 370)

        local desc = f:CreateFontString(nil, "OVERLAY")
        desc:SetFont(font, 10, "OUTLINE")
        desc:SetPoint("TOP", f.title, "BOTTOM", 0, -4)
        desc:SetText("将编码后的字符串粘贴到下方 (Ctrl+V)，然后点击导入")
        desc:SetTextColor(0.7, 0.7, 0.7)

        local sf = CreateFrame("ScrollFrame", "SFramesKBMImportScroll", f, "UIPanelScrollFrameTemplate")
        sf:SetPoint("TOPLEFT", f, "TOPLEFT", 12, -48)
        sf:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -32, 72)

        local edit = CreateFrame("EditBox", "SFramesKBMImportEdit", sf)
        edit:SetWidth(420)
        edit:SetHeight(200)
        edit:SetMultiLine(true)
        edit:SetFont(font, 10, "OUTLINE")
        edit:SetAutoFocus(false)
        edit:SetScript("OnEscapePressed", function() f:Hide() end)
        sf:SetScrollChild(edit)
        f.edit = edit

        local statusLabel = f:CreateFontString(nil, "OVERLAY")
        statusLabel:SetFont(font, 10, "OUTLINE")
        statusLabel:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 14, 46)
        statusLabel:SetWidth(300)
        statusLabel:SetJustifyH("LEFT")
        statusLabel:SetText("")
        f.statusLabel = statusLabel

        CreateThemedButton(f, "导入并应用", 120, -336, 120, 26, function()
            local inputText = f.edit:GetText()
            if not inputText or inputText == "" then
                f.statusLabel:SetText("|cffff4444请先粘贴绑定数据|r")
                return
            end
            local data, err = KBM:DeserializeBindings(inputText)
            if not data then
                f.statusLabel:SetText("|cffff4444错误: " .. (err or "未知") .. "|r")
                return
            end
            local ok, count = KBM:ApplyBindings(data)
            if ok then
                f.statusLabel:SetText("|cff44ff44成功导入 " .. count .. " 个按键绑定|r")
                SFrames:Print("已从文本导入 " .. count .. " 个按键绑定")
            else
                f.statusLabel:SetText("|cffff4444导入失败|r")
            end
        end)

        CreateThemedButton(f, "取消", 250, -336, 100, 26, function()
            f:Hide()
        end)

        importFrame = f
    end

    importFrame.edit:SetText("")
    importFrame.statusLabel:SetText("")
    importFrame:Show()
    importFrame.edit:SetFocus()
end

--------------------------------------------------------------------------------
-- Input name dialog (for save / rename)
--------------------------------------------------------------------------------

local inputDialog

local function ShowInputDialog(titleText, defaultText, callback)
    if not inputDialog then
        local f, font = CreateDialogFrame("SFramesKBMInput", "", 360, 120)

        local edit = CreateFrame("EditBox", "SFramesKBMInputEdit", f)
        edit:SetWidth(320)
        edit:SetHeight(24)
        edit:SetPoint("TOP", f, "TOP", 0, -40)
        edit:SetFont(font, 12, "OUTLINE")
        edit:SetAutoFocus(true)
        edit:SetMaxLetters(50)
        edit:SetBackdrop({
            bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            tile = true, tileSize = 16, edgeSize = 10,
            insets = { left = 3, right = 3, top = 3, bottom = 3 },
        })
        edit:SetBackdropColor(0.1, 0.1, 0.1, 0.9)
        edit:SetBackdropBorderColor(0.5, 0.5, 0.5, 0.8)
        edit:SetTextInsets(6, 6, 0, 0)
        f.edit = edit

        CreateThemedButton(f, "确定", 90, -78, 80, 24, function()
            local val = f.edit:GetText()
            if val and val ~= "" and f.callback then
                f.callback(val)
            end
            f:Hide()
        end)

        CreateThemedButton(f, "取消", 185, -78, 80, 24, function()
            f:Hide()
        end)

        edit:SetScript("OnEscapePressed", function() f:Hide() end)
        edit:SetScript("OnEnterPressed", function()
            local val = f.edit:GetText()
            if val and val ~= "" and f.callback then
                f.callback(val)
            end
            f:Hide()
        end)

        inputDialog = f
    end

    inputDialog.title:SetText(titleText)
    inputDialog.edit:SetText(defaultText or "")
    inputDialog.callback = callback
    inputDialog:Show()
    inputDialog.edit:SetFocus()
    inputDialog.edit:HighlightText()
end

--------------------------------------------------------------------------------
-- Confirm dialog
--------------------------------------------------------------------------------

local confirmDialog

local function ShowConfirmDialog(message, callback)
    if not confirmDialog then
        local f, font = CreateDialogFrame("SFramesKBMConfirm", "", 360, 110)

        local msg = f:CreateFontString(nil, "OVERLAY")
        msg:SetFont(font, 11, "OUTLINE")
        msg:SetPoint("TOP", f, "TOP", 0, -34)
        msg:SetWidth(320)
        msg:SetJustifyH("CENTER")
        msg:SetTextColor(0.9, 0.9, 0.9)
        f.msg = msg

        CreateThemedButton(f, "确定", 90, -70, 80, 24, function()
            if f.callback then f.callback() end
            f:Hide()
        end)

        CreateThemedButton(f, "取消", 185, -70, 80, 24, function()
            f:Hide()
        end)

        confirmDialog = f
    end

    confirmDialog.title:SetText("|cffffcc00确认|r")
    confirmDialog.msg:SetText(message)
    confirmDialog.callback = callback
    confirmDialog:Show()
end

KBM.ShowInputDialog = ShowInputDialog
KBM.ShowConfirmDialog = ShowConfirmDialog
