--------------------------------------------------------------------------------
-- Nanami-UI: SpellBook UI (SpellBookUI.lua)
-- Replaces the default SpellBookFrame with a modern rounded UI
--------------------------------------------------------------------------------

SFrames = SFrames or {}
SFrames.SpellBookUI = {}
local SB = SFrames.SpellBookUI
SFramesDB = SFramesDB or {}

--------------------------------------------------------------------------------
-- Theme  (aligned with CharacterPanel / SocialUI standard palette)
--------------------------------------------------------------------------------
local T = SFrames.ActiveTheme

--------------------------------------------------------------------------------
-- Layout (single table to stay under upvalue limit)
--------------------------------------------------------------------------------
local L = {
    RIGHT_TAB_W   = 56,
    SIDE_PAD      = 10,
    CONTENT_W     = 316,
    HEADER_H      = 30,
    SPELL_COLS    = 2,
    SPELL_ROWS    = 8,
    SPELL_H       = 38,
    ICON_SIZE     = 30,
    PAGE_H        = 26,
    OPTIONS_H     = 26,
    TAB_H         = 40,
    TAB_GAP       = 2,
    TAB_ICON      = 26,
    BOOK_TAB_W    = 52,
    BOOK_TAB_H    = 22,
}
L.SPELLS_PER_PAGE = L.SPELL_COLS * L.SPELL_ROWS
L.SPELL_W = (L.CONTENT_W - 4) / L.SPELL_COLS
L.MAIN_W  = L.CONTENT_W + L.SIDE_PAD * 2
L.FRAME_W = L.MAIN_W + L.RIGHT_TAB_W + 4
L.FRAME_H = L.HEADER_H + 6 + L.SPELL_ROWS * L.SPELL_H + 6 + L.PAGE_H + 4 + L.OPTIONS_H + 10

--------------------------------------------------------------------------------
-- State (single table to stay under upvalue limit)
--------------------------------------------------------------------------------
local S = {
    frame         = nil,
    spellButtons  = {},
    tabButtons    = {},
    bookTabs      = {},
    currentTab    = 1,
    currentPage   = 1,
    currentBook   = "spell",
    initialized   = false,
    filteredCache = nil,
    scanTip       = nil,
}

local widgetId = 0
local function NextName(p)
    widgetId = widgetId + 1
    return "SFramesSB" .. (p or "") .. tostring(widgetId)
end

--------------------------------------------------------------------------------
-- Helpers
--------------------------------------------------------------------------------
local function GetFont()
    if SFrames and SFrames.GetFont then return SFrames:GetFont() end
    return "Fonts\\ARIALN.TTF"
end

local function SetRoundBackdrop(frame, bgColor, borderColor)
    frame: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 },
    })
    local bg = bgColor or T.panelBg
    local bd = borderColor or T.panelBorder
    frame:SetBackdropColor(bg[1], bg[2], bg[3], bg[4] or 1)
    frame:SetBackdropBorderColor(bd[1], bd[2], bd[3], bd[4] or 1)
end

local function SetPixelBackdrop(frame, bgColor, borderColor)
    frame:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        tile = false, tileSize = 0, edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 },
    })
    if bgColor then
        frame:SetBackdropColor(bgColor[1], bgColor[2], bgColor[3], bgColor[4] or 1)
    end
    if borderColor then
        frame:SetBackdropBorderColor(borderColor[1], borderColor[2], borderColor[3], borderColor[4] or 1)
    end
end

local function CreateShadow(parent)
    local s = CreateFrame("Frame", nil, parent)
    s:SetPoint("TOPLEFT", parent, "TOPLEFT", -4, 4)
    s:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 4, -4)
    s:SetFrameLevel(math.max(parent:GetFrameLevel() - 1, 0))
    s:SetBackdrop({
        bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 16,
        insets = { left = 4, right = 4, top = 4, bottom = 4 },
    })
    s:SetBackdropColor(0, 0, 0, 0.6)
    s:SetBackdropBorderColor(0, 0, 0, 0.45)
    return s
end

local function MakeFS(parent, size, justifyH, color)
    local fs = parent:CreateFontString(nil, "OVERLAY")
    fs:SetFont(GetFont(), size or 11, "OUTLINE")
    fs:SetJustifyH(justifyH or "LEFT")
    local c = color or T.nameText
    fs:SetTextColor(c[1], c[2], c[3])
    return fs
end

local function MakeButton(parent, text, w, h)
    local btn = CreateFrame("Button", NextName("Btn"), parent)
    btn:SetWidth(w or 80)
    btn:SetHeight(h or 22)
    SetRoundBackdrop(btn, T.btnBg, T.btnBorder)
    local fs = MakeFS(btn, 10, "CENTER", T.btnText)
    fs:SetPoint("CENTER", 0, 0)
    fs:SetText(text or "")
    btn.text = fs
    btn:SetScript("OnEnter", function()
        SetRoundBackdrop(this, T.btnHoverBg, T.tabActiveBorder)
        this.text:SetTextColor(T.tabActiveText[1], T.tabActiveText[2], T.tabActiveText[3])
    end)
    btn:SetScript("OnLeave", function()
        SetRoundBackdrop(this, T.btnBg, T.btnBorder)
        this.text:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
    end)
    return btn
end

local function MakeSep(parent, y)
    local sep = parent:CreateTexture(nil, "ARTWORK")
    sep:SetTexture("Interface\\Buttons\\WHITE8X8")
    sep:SetHeight(1)
    sep:SetPoint("TOPLEFT", parent, "TOPLEFT", 4, y)
    sep:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -4, y)
    sep:SetVertexColor(T.sepColor[1], T.sepColor[2], T.sepColor[3], T.sepColor[4])
    return sep
end

--------------------------------------------------------------------------------
-- Hide Blizzard SpellBook
--------------------------------------------------------------------------------
local function HideBlizzardSpellBook()
    if not SpellBookFrame then return end
    SpellBookFrame:SetAlpha(0)
    SpellBookFrame:EnableMouse(false)
    SpellBookFrame:ClearAllPoints()
    SpellBookFrame:SetPoint("TOPLEFT", UIParent, "BOTTOMRIGHT", 2000, 2000)
    if SpellBookFrame.SetScript then
        SpellBookFrame:SetScript("OnShow", function() this:Hide() end)
    end
end

--------------------------------------------------------------------------------
-- Spell Data Helpers
--------------------------------------------------------------------------------
local function GetBookType()
    if S.currentBook == "pet" then return BOOKTYPE_PET or "pet" end
    return BOOKTYPE_SPELL or "spell"
end

local function GetTabInfo()
    local numTabs = GetNumSpellTabs()
    local tabs = {}
    for i = 1, numTabs do
        local name, texture, offset, numSpells = GetSpellTabInfo(i)
        if name then
            table.insert(tabs, {
                name = name, texture = texture,
                offset = offset, numSpells = numSpells, index = i,
            })
        end
    end
    return tabs
end

local function GetCurrentTabSpells()
    local tabs = GetTabInfo()
    local tab = tabs[S.currentTab]
    if not tab then return 0, 0 end
    return tab.offset, tab.numSpells
end

local function GetFilteredSpellList()
    local offset, numSpells = GetCurrentTabSpells()
    local bookType = GetBookType()

    if not SFramesDB.spellBookHighestOnly then
        local list = {}
        for i = 1, numSpells do
            table.insert(list, offset + i)
        end
        S.filteredCache = list
        return list
    end

    local seen = {}
    local order = {}
    for i = 1, numSpells do
        local idx = offset + i
        local name, rank = GetSpellName(idx, bookType)
        if name then
            if seen[name] then
                for k = 1, table.getn(order) do
                    if order[k].name == name then
                        order[k].idx = idx
                        break
                    end
                end
            else
                seen[name] = true
                table.insert(order, { name = name, idx = idx })
            end
        end
    end

    local list = {}
    for _, v in ipairs(order) do
        table.insert(list, v.idx)
    end
    S.filteredCache = list
    return list
end

local function GetMaxPages()
    local list = S.filteredCache or GetFilteredSpellList()
    return math.max(1, math.ceil(table.getn(list) / L.SPELLS_PER_PAGE))
end

--------------------------------------------------------------------------------
-- Auto-Replace Action Bar (lower rank -> highest rank)
--------------------------------------------------------------------------------
local function EnsureScanTooltip()
    if S.scanTip then return end
    S.scanTip = CreateFrame("GameTooltip", "SFramesSBScanTip", UIParent, "GameTooltipTemplate")
    S.scanTip:SetOwner(UIParent, "ANCHOR_NONE")
    S.scanTip:SetPoint("TOPLEFT", UIParent, "BOTTOMRIGHT", 1000, 1000)
end

local function AutoReplaceActionBarSpells()
    if not SFramesDB.spellBookAutoReplace then return end
    if UnitAffectingCombat and UnitAffectingCombat("player") then return end

    EnsureScanTooltip()

    local highestByName = {}
    local highestRankText = {}
    local numTabs = GetNumSpellTabs()
    for tab = 1, numTabs do
        local _, _, offset, numSpells = GetSpellTabInfo(tab)
        for i = 1, numSpells do
            local idx = offset + i
            local name, rank = GetSpellName(idx, "spell")
            if name and not IsSpellPassive(idx, "spell") then
                highestByName[name] = idx
                highestRankText[name] = rank or ""
            end
        end
    end

    local tipLeft = getglobal("SFramesSBScanTipTextLeft1")
    local tipRight = getglobal("SFramesSBScanTipTextRight1")

    for slot = 1, 120 do
        if HasAction(slot) then
            S.scanTip:ClearLines()
            S.scanTip:SetAction(slot)
            local actionName = tipLeft and tipLeft:GetText()
            local actionRank = tipRight and tipRight:GetText()
            if actionName and highestByName[actionName] then
                local bestRank = highestRankText[actionName]
                if bestRank and bestRank ~= "" and actionRank and actionRank ~= bestRank then
                    PickupSpell(highestByName[actionName], "spell")
                    PlaceAction(slot)
                    ClearCursor()
                end
            end
        end
    end
end

--------------------------------------------------------------------------------
-- Slot backdrop helper: backdrop on a SEPARATE child at lower frameLevel
-- so icon / text render cleanly above it (same fix as ActionBars)
--------------------------------------------------------------------------------
local function SetSlotBg(btn, bgColor, borderColor)
    if not btn.sfBg then return end
    SetPixelBackdrop(btn.sfBg, bgColor or T.slotBg, borderColor or T.slotBorder)
end

local function CreateSlotBackdrop(btn)
    if btn:GetBackdrop() then btn:SetBackdrop(nil) end
    local level = btn:GetFrameLevel()
    local bd = CreateFrame("Frame", nil, btn)
    bd:SetFrameLevel(level > 0 and (level - 1) or 0)
    bd:SetAllPoints(btn)
    SetPixelBackdrop(bd, T.slotBg, T.slotBorder)
    btn.sfBg = bd
    return bd
end

--------------------------------------------------------------------------------
-- Update Spell Buttons
--------------------------------------------------------------------------------
local function UpdateSpellButtons()
    if not S.frame or not S.frame:IsShown() then return end

    local list = GetFilteredSpellList()
    local bookType = GetBookType()
    local startIdx = (S.currentPage - 1) * L.SPELLS_PER_PAGE
    local totalSpells = table.getn(list)

    for i = 1, L.SPELLS_PER_PAGE do
        local btn = S.spellButtons[i]
        if not btn then break end

        local listIdx = startIdx + i
        local spellIdx = list[listIdx]

        if spellIdx and listIdx <= totalSpells then
            local spellName, spellRank = GetSpellName(spellIdx, bookType)
            local texture = GetSpellTexture(spellIdx, bookType)

            btn.icon:SetTexture(texture)
            btn.icon:SetAlpha(1)
            btn.nameFS:SetText(spellName or "")
            btn.subFS:SetText(spellRank or "")
            btn.spellId = spellIdx
            btn.bookType = bookType

            local isPassive = IsSpellPassive(spellIdx, bookType)
            if isPassive then
                btn.nameFS:SetTextColor(T.passive[1], T.passive[2], T.passive[3])
                btn.subFS:SetTextColor(T.passive[1], T.passive[2], T.passive[3])
                btn.icon:SetVertexColor(T.passive[1], T.passive[2], T.passive[3])
                btn.passiveBadge:Show()
            else
                btn.nameFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
                btn.subFS:SetTextColor(T.subText[1], T.subText[2], T.subText[3])
                btn.icon:SetVertexColor(1, 1, 1)
                btn.passiveBadge:Hide()
            end

            SetSlotBg(btn, T.slotBg, T.slotBorder)
            btn:Show()
            btn:Enable()
        else
            btn.icon:SetTexture(nil)
            btn.icon:SetAlpha(0)
            btn.nameFS:SetText("")
            btn.subFS:SetText("")
            btn.spellId = nil
            btn.bookType = nil
            btn.passiveBadge:Hide()
            SetSlotBg(btn, T.emptySlotBg, T.emptySlotBd)
            btn:Show()
            btn:Disable()
        end
    end

    local maxPages = GetMaxPages()
    local f = S.frame
    if f.pageText then
        if maxPages <= 1 then
            f.pageText:SetText("")
        else
            f.pageText:SetText(S.currentPage .. " / " .. maxPages)
        end
    end
    if f.prevBtn then
        if S.currentPage > 1 then
            f.prevBtn:Enable(); f.prevBtn:SetAlpha(1)
        else
            f.prevBtn:Disable(); f.prevBtn:SetAlpha(0.4)
        end
    end
    if f.nextBtn then
        if S.currentPage < maxPages then
            f.nextBtn:Enable(); f.nextBtn:SetAlpha(1)
        else
            f.nextBtn:Disable(); f.nextBtn:SetAlpha(0.4)
        end
    end
end

--------------------------------------------------------------------------------
-- Update Skill Line Tabs (right side)
--------------------------------------------------------------------------------
local function UpdateSkillLineTabs()
    local tabs = GetTabInfo()
    for i = 1, 8 do
        local btn = S.tabButtons[i]
        if not btn then break end
        local tab = tabs[i]
        if tab then
            if tab.texture then
                btn.tabIcon:SetTexture(tab.texture)
                btn.tabIcon:Show()
            else
                btn.tabIcon:Hide()
            end
            btn.tabLabel:SetText(tab.name or "")
            btn:Show()

            if i == S.currentTab then
                SetRoundBackdrop(btn, T.tabActiveBg, T.tabActiveBorder)
                btn.tabIcon:SetVertexColor(1, 1, 1)
                btn.tabLabel:SetTextColor(T.tabActiveText[1], T.tabActiveText[2], T.tabActiveText[3])
                btn.indicator:Show()
            else
                SetRoundBackdrop(btn, T.tabBg, T.tabBorder)
                btn.tabIcon:SetVertexColor(T.tabText[1], T.tabText[2], T.tabText[3])
                btn.tabLabel:SetTextColor(T.tabText[1], T.tabText[2], T.tabText[3])
                btn.indicator:Hide()
            end
        else
            btn:Hide()
        end
    end
end

--------------------------------------------------------------------------------
-- Update Book Tabs (Spell / Pet)
--------------------------------------------------------------------------------
local function UpdateBookTabs()
    for i = 1, 2 do
        local bt = S.bookTabs[i]
        if not bt then break end
        local isActive = (i == 1 and S.currentBook == "spell") or (i == 2 and S.currentBook == "pet")
        if isActive then
            SetRoundBackdrop(bt, T.tabActiveBg, T.tabActiveBorder)
            bt.text:SetTextColor(T.tabActiveText[1], T.tabActiveText[2], T.tabActiveText[3])
        else
            SetRoundBackdrop(bt, T.tabBg, T.tabBorder)
            bt.text:SetTextColor(T.tabText[1], T.tabText[2], T.tabText[3])
        end
    end
    local petTab = S.bookTabs[2]
    if petTab then
        local hasPet = HasPetSpells and HasPetSpells()
        if hasPet then petTab:Show() else petTab:Hide() end
    end
end

local function FullRefresh()
    S.filteredCache = nil
    UpdateSkillLineTabs()
    UpdateBookTabs()
    UpdateSpellButtons()
    if S.frame and S.frame.optHighest then
        S.frame.optHighest:UpdateVisual()
    end
    if S.frame and S.frame.optReplace then
        S.frame.optReplace:UpdateVisual()
    end
end

--------------------------------------------------------------------------------
-- Build: Header
--------------------------------------------------------------------------------
local function BuildHeader(f)
    local header = CreateFrame("Frame", nil, f)
    header:SetHeight(L.HEADER_H)
    header:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
    header:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, 0)

    local function MakeBookTab(parent, label, idx, xOff)
        local bt = CreateFrame("Button", NextName("BookTab"), parent)
        bt:SetWidth(L.BOOK_TAB_W)
        bt:SetHeight(L.BOOK_TAB_H)
        bt:SetPoint("LEFT", parent, "LEFT", xOff, 0)
        SetRoundBackdrop(bt, T.tabBg, T.tabBorder)

        local txt = MakeFS(bt, 10, "CENTER", T.tabText)
        txt:SetPoint("CENTER", 0, 0)
        txt:SetText(label)
        bt.text = txt
        bt.bookIdx = idx

        bt:SetScript("OnClick", function()
            if this.bookIdx == 1 then S.currentBook = "spell" else S.currentBook = "pet" end
            S.currentTab = 1
            S.currentPage = 1
            FullRefresh()
        end)
        bt:SetScript("OnEnter", function()
            this.text:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
        end)
        bt:SetScript("OnLeave", function()
            local active = (this.bookIdx == 1 and S.currentBook == "spell") or (this.bookIdx == 2 and S.currentBook == "pet")
            if active then
                this.text:SetTextColor(T.tabActiveText[1], T.tabActiveText[2], T.tabActiveText[3])
            else
                this.text:SetTextColor(T.tabText[1], T.tabText[2], T.tabText[3])
            end
        end)
        return bt
    end

    S.bookTabs[1] = MakeBookTab(header, "法术", 1, 8)
    S.bookTabs[2] = MakeBookTab(header, "宠物", 2, 8 + L.BOOK_TAB_W + 4)

    local titleIco = SFrames:CreateIcon(header, "spellbook", 14)
    titleIco:SetDrawLayer("OVERLAY")
    titleIco:SetPoint("CENTER", header, "CENTER", -28, 0)
    titleIco:SetVertexColor(T.gold[1], T.gold[2], T.gold[3])

    local title = MakeFS(header, 13, "CENTER", T.gold)
    title:SetPoint("LEFT", titleIco, "RIGHT", 4, 0)
    title:SetText("法术书")
    f.titleFS = title

    local closeBtn = CreateFrame("Button", NextName("Close"), header)
    closeBtn:SetWidth(20)
    closeBtn:SetHeight(20)
    closeBtn:SetPoint("RIGHT", header, "RIGHT", -8, 0)
    SetRoundBackdrop(closeBtn, T.buttonDownBg, T.btnBorder)
    closeBtn:SetScript("OnClick", function() this:GetParent():GetParent():Hide() end)
    local closeIco = SFrames:CreateIcon(closeBtn, "close", 12)
    closeIco:SetDrawLayer("OVERLAY")
    closeIco:SetPoint("CENTER", closeBtn, "CENTER", 0, 0)
    closeIco:SetVertexColor(1, 0.7, 0.7)
    closeBtn.nanamiIcon = closeIco
    closeBtn:SetScript("OnEnter", function()
        SetRoundBackdrop(this, T.btnHoverBg, T.btnHoverBd)
        if this.nanamiIcon then this.nanamiIcon:SetVertexColor(1, 1, 1) end
    end)
    closeBtn:SetScript("OnLeave", function()
        SetRoundBackdrop(this, T.buttonDownBg, T.btnBorder)
        if this.nanamiIcon then this.nanamiIcon:SetVertexColor(1, 0.7, 0.7) end
    end)

    MakeSep(f, -L.HEADER_H)
end

--------------------------------------------------------------------------------
-- Build: Right Skill Line Tabs
--------------------------------------------------------------------------------
local function BuildSkillTabs(f)
    for idx = 1, 8 do
        local btn = CreateFrame("Button", NextName("Tab"), f)
        btn:SetWidth(L.RIGHT_TAB_W)
        btn:SetHeight(L.TAB_H)
        btn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -3,
            -(L.HEADER_H + 4 + (idx - 1) * (L.TAB_H + L.TAB_GAP)))
        SetRoundBackdrop(btn, T.tabBg, T.tabBorder)
        btn.tabIndex = idx

        local indicator = btn:CreateTexture(nil, "OVERLAY")
        indicator:SetTexture("Interface\\Buttons\\WHITE8X8")
        indicator:SetWidth(3)
        indicator:SetPoint("TOPLEFT", btn, "TOPLEFT", 1, -4)
        indicator:SetPoint("BOTTOMLEFT", btn, "BOTTOMLEFT", 1, 4)
        indicator:SetVertexColor(T.accent[1], T.accent[2], T.accent[3], T.accent[4])
        indicator:Hide()
        btn.indicator = indicator

        local icon = btn:CreateTexture(nil, "ARTWORK")
        icon:SetWidth(L.TAB_ICON)
        icon:SetHeight(L.TAB_ICON)
        icon:SetPoint("TOP", btn, "TOP", 0, -4)
        icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
        btn.tabIcon = icon

        local label = MakeFS(btn, 7, "CENTER", T.tabText)
        label:SetPoint("BOTTOM", btn, "BOTTOM", 0, 3)
        label:SetWidth(L.RIGHT_TAB_W - 6)
        btn.tabLabel = label

        btn:SetScript("OnClick", function()
            S.currentTab = this.tabIndex
            S.currentPage = 1
            FullRefresh()
        end)

        btn:SetScript("OnEnter", function()
            if this.tabIndex ~= S.currentTab then
                SetRoundBackdrop(this, T.slotHover, T.tabActiveBorder)
                this.tabIcon:SetVertexColor(1, 1, 1)
                this.tabLabel:SetTextColor(T.tabActiveText[1], T.tabActiveText[2], T.tabActiveText[3])
            end
            local tabs = GetTabInfo()
            local tab = tabs[this.tabIndex]
            if tab then
                GameTooltip:SetOwner(this, "ANCHOR_LEFT")
                GameTooltip:AddLine(tab.name, T.gold[1], T.gold[2], T.gold[3])
                GameTooltip:AddLine(tab.numSpells .. " 个法术", T.subText[1], T.subText[2], T.subText[3])
                GameTooltip:Show()
            end
        end)
        btn:SetScript("OnLeave", function()
            GameTooltip:Hide()
            if this.tabIndex ~= S.currentTab then
                SetRoundBackdrop(this, T.tabBg, T.tabBorder)
                this.tabIcon:SetVertexColor(T.tabText[1], T.tabText[2], T.tabText[3])
                this.tabLabel:SetTextColor(T.tabText[1], T.tabText[2], T.tabText[3])
            end
        end)

        btn:Hide()
        S.tabButtons[idx] = btn
    end

    local tabSep = f:CreateTexture(nil, "ARTWORK")
    tabSep:SetTexture("Interface\\Buttons\\WHITE8X8")
    tabSep:SetWidth(1)
    tabSep:SetPoint("TOPLEFT", f, "TOPRIGHT", -(L.RIGHT_TAB_W + 5), -(L.HEADER_H + 2))
    tabSep:SetPoint("BOTTOMLEFT", f, "BOTTOMRIGHT", -(L.RIGHT_TAB_W + 5), 4)
    tabSep:SetVertexColor(T.sepColor[1], T.sepColor[2], T.sepColor[3], T.sepColor[4])
end

--------------------------------------------------------------------------------
-- Build: Spell Buttons Grid
-- Backdrop on a SEPARATE child frame at lower frameLevel (ActionBars fix)
--------------------------------------------------------------------------------
local function BuildSpellGrid(f)
    local contentTop = -(L.HEADER_H + 6)
    local contentFrame = CreateFrame("Frame", nil, f)
    contentFrame:SetPoint("TOPLEFT", f, "TOPLEFT", L.SIDE_PAD, contentTop)
    contentFrame:SetWidth(L.CONTENT_W)
    contentFrame:SetHeight(L.SPELL_ROWS * L.SPELL_H + 4)

    for row = 1, L.SPELL_ROWS do
        for col = 1, L.SPELL_COLS do
            local idx = (row - 1) * L.SPELL_COLS + col
            local btn = CreateFrame("Button", NextName("Spell"), contentFrame)
            btn:SetWidth(L.SPELL_W - 2)
            btn:SetHeight(L.SPELL_H - 2)
            local x = (col - 1) * L.SPELL_W + 1
            local y = -((row - 1) * L.SPELL_H)
            btn:SetPoint("TOPLEFT", contentFrame, "TOPLEFT", x, y)

            CreateSlotBackdrop(btn)

            local icon = btn:CreateTexture(nil, "ARTWORK")
            icon:SetWidth(L.ICON_SIZE)
            icon:SetHeight(L.ICON_SIZE)
            icon:SetPoint("LEFT", btn, "LEFT", 5, 0)
            icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
            btn.icon = icon

            local nameFS = MakeFS(btn, 11, "LEFT", T.nameText)
            nameFS:SetPoint("TOPLEFT", icon, "TOPRIGHT", 6, -2)
            nameFS:SetPoint("RIGHT", btn, "RIGHT", -4, 0)
            btn.nameFS = nameFS

            local subFS = MakeFS(btn, 9, "LEFT", T.subText)
            subFS:SetPoint("BOTTOMLEFT", icon, "BOTTOMRIGHT", 6, 2)
            subFS:SetPoint("RIGHT", btn, "RIGHT", -4, 0)
            btn.subFS = subFS

            local passiveBadge = MakeFS(btn, 7, "RIGHT", T.passive)
            passiveBadge:SetPoint("TOPRIGHT", btn, "TOPRIGHT", -4, -3)
            passiveBadge:SetText("被动")
            passiveBadge:Hide()
            btn.passiveBadge = passiveBadge

            btn:RegisterForClicks("LeftButtonUp", "RightButtonUp")
            btn:RegisterForDrag("LeftButton")

            btn:SetScript("OnClick", function()
                if not this.spellId then return end
                if arg1 == "LeftButton" then
                    CastSpell(this.spellId, this.bookType)
                elseif arg1 == "RightButton" then
                    PickupSpell(this.spellId, this.bookType)
                end
            end)

            btn:SetScript("OnDragStart", function()
                if this.spellId then
                    PickupSpell(this.spellId, this.bookType)
                end
            end)

            btn:SetScript("OnEnter", function()
                if this.spellId then
                    SetSlotBg(this, T.slotHover, T.slotSelected)
                    GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
                    GameTooltip:SetSpell(this.spellId, this.bookType)
                    GameTooltip:Show()
                end
            end)

            btn:SetScript("OnLeave", function()
                if this.spellId then
                    SetSlotBg(this, T.slotBg, T.slotBorder)
                else
                    SetSlotBg(this, T.emptySlotBg, T.emptySlotBd)
                end
                GameTooltip:Hide()
            end)

            S.spellButtons[idx] = btn
        end
    end

    contentFrame:EnableMouseWheel(true)
    contentFrame:SetScript("OnMouseWheel", function()
        if arg1 > 0 then
            if S.currentPage > 1 then
                S.currentPage = S.currentPage - 1
                UpdateSpellButtons()
            end
        else
            if S.currentPage < GetMaxPages() then
                S.currentPage = S.currentPage + 1
                UpdateSpellButtons()
            end
        end
    end)

    return contentTop
end

--------------------------------------------------------------------------------
-- Build: Pagination
--------------------------------------------------------------------------------
local function BuildPagination(f, contentTop)
    local pageY = contentTop - L.SPELL_ROWS * L.SPELL_H - 6

    local prevBtn = MakeButton(f, "< 上一页", 66, L.PAGE_H)
    prevBtn:SetPoint("TOPLEFT", f, "TOPLEFT", L.SIDE_PAD, pageY)
    prevBtn:SetScript("OnClick", function()
        if S.currentPage > 1 then
            S.currentPage = S.currentPage - 1
            UpdateSpellButtons()
        end
    end)
    f.prevBtn = prevBtn

    local nextBtn = MakeButton(f, "下一页 >", 66, L.PAGE_H)
    nextBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -(L.RIGHT_TAB_W + L.SIDE_PAD + 4), pageY)
    nextBtn:SetScript("OnClick", function()
        if S.currentPage < GetMaxPages() then
            S.currentPage = S.currentPage + 1
            UpdateSpellButtons()
        end
    end)
    f.nextBtn = nextBtn

    local pageText = MakeFS(f, 11, "CENTER", T.pageText)
    pageText:SetPoint("LEFT", prevBtn, "RIGHT", 4, 0)
    pageText:SetPoint("RIGHT", nextBtn, "LEFT", -4, 0)
    f.pageText = pageText

    return pageY
end

--------------------------------------------------------------------------------
-- Build: Options bar
--------------------------------------------------------------------------------
local function BuildOptions(f, pageY)
    local optY = pageY - L.PAGE_H - 4
    MakeSep(f, optY + 2)

    local function MakeCheckOption(parent, label, xOff, yOff, getFunc, setFunc)
        local btn = CreateFrame("Button", NextName("Opt"), parent)
        btn:SetHeight(L.OPTIONS_H - 4)
        btn:SetPoint("TOPLEFT", parent, "TOPLEFT", xOff, yOff)

        local box = CreateFrame("Frame", nil, btn)
        box:SetWidth(12)
        box:SetHeight(12)
        box:SetPoint("LEFT", btn, "LEFT", 0, 0)
        SetPixelBackdrop(box, T.checkOff, T.tabBorder)
        btn.box = box

        local checkMark = MakeFS(box, 10, "CENTER", T.checkOn)
        checkMark:SetPoint("CENTER", 0, 1)
        checkMark:SetText("")
        btn.checkMark = checkMark

        local txt = MakeFS(btn, 9, "LEFT", T.optionText)
        txt:SetPoint("LEFT", box, "RIGHT", 4, 0)
        txt:SetText(label)
        btn.label = txt

        btn:SetWidth(txt:GetStringWidth() + 20)
        btn.getFunc = getFunc
        btn.setFunc = setFunc

        function btn:UpdateVisual()
            if self.getFunc() then
                self.checkMark:SetText("√")
                SetPixelBackdrop(self.box, { T.checkOn[1]*0.3, T.checkOn[2]*0.3, T.checkOn[3]*0.3, 0.8 }, T.checkOn)
                self.label:SetTextColor(T.tabActiveText[1], T.tabActiveText[2], T.tabActiveText[3])
            else
                self.checkMark:SetText("")
                SetPixelBackdrop(self.box, T.checkOff, T.tabBorder)
                self.label:SetTextColor(T.optionText[1], T.optionText[2], T.optionText[3])
            end
        end

        btn:SetScript("OnClick", function()
            this.setFunc(not this.getFunc())
            this:UpdateVisual()
            FullRefresh()
        end)
        btn:SetScript("OnEnter", function()
            this.label:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
        end)
        btn:SetScript("OnLeave", function()
            this:UpdateVisual()
        end)

        btn:UpdateVisual()
        return btn
    end

    f.optHighest = MakeCheckOption(f, "只显示最高等级", L.SIDE_PAD, optY,
        function() return SFramesDB.spellBookHighestOnly == true end,
        function(v) SFramesDB.spellBookHighestOnly = v; S.currentPage = 1 end
    )

    f.optReplace = MakeCheckOption(f, "学习新等级自动替换动作条", L.SIDE_PAD + 120, optY,
        function() return SFramesDB.spellBookAutoReplace == true end,
        function(v) SFramesDB.spellBookAutoReplace = v end
    )
end

--------------------------------------------------------------------------------
-- Build Main Frame
--------------------------------------------------------------------------------
local function BuildMainFrame()
    if S.frame then return end

    local f = CreateFrame("Frame", "SFramesSpellBookUI", UIParent)
    f:SetWidth(L.FRAME_W)
    f:SetHeight(L.FRAME_H)
    f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
    f:SetFrameStrata("HIGH")
    f:SetFrameLevel(10)
    SetRoundBackdrop(f)
    CreateShadow(f)

    f:EnableMouse(true)
    f:SetMovable(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", function() this:StartMoving() end)
    f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
    f:SetScript("OnShow", function()
        if SpellBookFrame and SpellBookFrame:IsShown() then
            SpellBookFrame:Hide()
        end
        FullRefresh()
    end)

    BuildHeader(f)
    BuildSkillTabs(f)
    local contentTop = BuildSpellGrid(f)
    local pageY = BuildPagination(f, contentTop)
    BuildOptions(f, pageY)

    table.insert(UISpecialFrames, "SFramesSpellBookUI")

    local scale = SFramesDB.spellBookScale or 1
    if scale ~= 1 then f:SetScale(scale) end

    S.frame = f
    f:Hide()
end

--------------------------------------------------------------------------------
-- Public API
--------------------------------------------------------------------------------
function SB:Toggle(bookType)
    if not S.initialized then return end
    if not S.frame then BuildMainFrame() end

    if bookType then
        if bookType == (BOOKTYPE_PET or "pet") then
            S.currentBook = "pet"
        else
            S.currentBook = "spell"
        end
    end

    if S.frame:IsShown() then
        S.frame:Hide()
    else
        S.currentTab = 1
        S.currentPage = 1
        S.frame:Show()
    end
end

function SB:Show(bookType)
    if not S.initialized then return end
    if not S.frame then BuildMainFrame() end
    if bookType then
        if bookType == (BOOKTYPE_PET or "pet") then
            S.currentBook = "pet"
        else
            S.currentBook = "spell"
        end
    end
    S.currentTab = 1
    S.currentPage = 1
    S.frame:Show()
end

function SB:Hide()
    if S.frame and S.frame:IsShown() then
        S.frame:Hide()
    end
end

function SB:IsShown()
    return S.frame and S.frame:IsShown()
end

--------------------------------------------------------------------------------
-- Initialize
--------------------------------------------------------------------------------
function SB:Initialize()
    if S.initialized then return end
    S.initialized = true

    if SFramesDB.spellBookHighestOnly == nil then SFramesDB.spellBookHighestOnly = false end
    if SFramesDB.spellBookAutoReplace == nil then SFramesDB.spellBookAutoReplace = false end

    HideBlizzardSpellBook()
    BuildMainFrame()

    local ef = CreateFrame("Frame", nil, UIParent)
    ef:RegisterEvent("SPELLS_CHANGED")
    ef:RegisterEvent("LEARNED_SPELL_IN_TAB")
    ef:RegisterEvent("SPELL_UPDATE_COOLDOWN")
    ef:SetScript("OnEvent", function()
        if event == "LEARNED_SPELL_IN_TAB" then
            AutoReplaceActionBarSpells()
        end
        if S.frame and S.frame:IsShown() then
            FullRefresh()
        end
    end)
end

--------------------------------------------------------------------------------
-- Hook ToggleSpellBook
--------------------------------------------------------------------------------
local origToggleSpellBook = ToggleSpellBook
ToggleSpellBook = function(bookType)
    if SFramesDB and SFramesDB.enableSpellBook == false then
        if origToggleSpellBook then
            origToggleSpellBook(bookType)
        end
        return
    end
    SB:Toggle(bookType)
end

--------------------------------------------------------------------------------
-- Bootstrap
--------------------------------------------------------------------------------
local bootstrap = CreateFrame("Frame", nil, UIParent)
bootstrap:RegisterEvent("PLAYER_LOGIN")
bootstrap:SetScript("OnEvent", function()
    if event == "PLAYER_LOGIN" then
        if SFramesDB.enableSpellBook == nil then
            SFramesDB.enableSpellBook = true
        end
        if SFramesDB.enableSpellBook ~= false then
            SB:Initialize()
        end
    end
end)
