local addonName = ...

-- ============================================================
--  Colours
-- ============================================================
local C = {
    bg         = {0.05, 0.04, 0.07, 0.98},
    sidebar    = {0.03, 0.02, 0.05, 1.00},
    accent     = {0.65, 0.30, 1.00, 1.00},   -- bright purple
    accentDim  = {0.40, 0.15, 0.65, 1.00},
    header     = {0.95, 0.92, 1.00, 1.00},
    sub        = {0.48, 0.42, 0.58, 1.00},
    text       = {0.82, 0.78, 0.90, 1.00},
    inputBg    = {0.08, 0.06, 0.12, 1.00},
    border     = {0.20, 0.14, 0.28, 1.00},
    row1       = {0.10, 0.07, 0.15, 0.70},
}

local FRAME_W   = 1020
local FRAME_H   = 680
local SIDEBAR_W = 240
local PAD       = 20

-- ============================================================
--  Helpers
-- ============================================================
local function Backdrop(f, bg, border)
    f:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
    })
    f:SetBackdropColor(bg[1], bg[2], bg[3], bg[4] or 1)
    if border then
        f:SetBackdropBorderColor(border[1], border[2], border[3], border[4] or 1)
    end
end

local function Tex(parent, r, g, b, a)
    local t = parent:CreateTexture(nil, "ARTWORK")
    t:SetColorTexture(r, g, b, a)
    return t
end

local function Label(parent, fontObj, text, r, g, b, a)
    local fs = parent:CreateFontString(nil, "OVERLAY", fontObj)
    fs:SetText(text or "")
    if r then fs:SetTextColor(r, g, b, a or 1) end
    fs:SetJustifyH("LEFT")
    return fs
end

-- ============================================================
--  Main window
-- ============================================================
local Main = CreateFrame("Frame", "CFLMainFrame", UIParent, "BackdropTemplate")
Main:SetSize(FRAME_W, FRAME_H)
Main:SetPoint("CENTER")
Main:SetFrameStrata("DIALOG")
Main:SetMovable(true)
Main:EnableMouse(true)
Main:RegisterForDrag("LeftButton")
Main:SetScript("OnDragStart", Main.StartMoving)
Main:SetScript("OnDragStop",  Main.StopMovingOrSizing)

-- Register with WoW's Escape key handler
table.insert(UISpecialFrames, "CFLMainFrame")
Main:Hide()
Main:SetBackdrop({
    bgFile   = "Interface\\Buttons\\WHITE8X8",
    edgeFile = "Interface\\Buttons\\WHITE8X8",
    edgeSize = 1,
})
Main:SetBackdropColor(C.bg[1], C.bg[2], C.bg[3], C.bg[4] or 1)
Main:SetBackdropBorderColor(0, 0, 0, 0)

-- border textures drawn directly on Main so they never escape its stacking context
local function AddBorderLine(side)
    local t = Main:CreateTexture(nil, "OVERLAY", nil, 7)
    t:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.9)
    if side == "top" then
        t:SetHeight(2); t:SetPoint("TOPLEFT", Main, "TOPLEFT", 0, 0); t:SetPoint("TOPRIGHT", Main, "TOPRIGHT", 0, 0)
    elseif side == "bottom" then
        t:SetHeight(2); t:SetPoint("BOTTOMLEFT", Main, "BOTTOMLEFT", 0, 0); t:SetPoint("BOTTOMRIGHT", Main, "BOTTOMRIGHT", 0, 0)
    elseif side == "left" then
        t:SetWidth(2); t:SetPoint("TOPLEFT", Main, "TOPLEFT", 0, 0); t:SetPoint("BOTTOMLEFT", Main, "BOTTOMLEFT", 0, 0)
    elseif side == "right" then
        t:SetWidth(2); t:SetPoint("TOPRIGHT", Main, "TOPRIGHT", 0, 0); t:SetPoint("BOTTOMRIGHT", Main, "BOTTOMRIGHT", 0, 0)
    end
end
AddBorderLine("top")
AddBorderLine("bottom")
AddBorderLine("left")
AddBorderLine("right")

-- version label bottom-left


-- clean single-pixel border via backdrop, no overlay frame needed

-- ── CLOSE BUTTON (X) ─────────────────────────────────────────
local CloseBtn = CreateFrame("Button", nil, Main, "BackdropTemplate")
CloseBtn:SetSize(32, 32)
CloseBtn:SetPoint("TOPRIGHT", Main, "TOPRIGHT", -8, -8)
CloseBtn:SetFrameLevel(Main:GetFrameLevel() + 10)
Backdrop(CloseBtn, {0.15, 0.08, 0.04, 1}, C.border)
local closeFS = CloseBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
closeFS:SetText("x")
closeFS:SetTextColor(C.sub[1], C.sub[2], C.sub[3])
closeFS:SetAllPoints(CloseBtn)
closeFS:SetJustifyH("CENTER")
CloseBtn:SetScript("OnEnter", function()
    closeFS:SetTextColor(1, 0.30, 0.20)
end)
CloseBtn:SetScript("OnLeave", function()
    closeFS:SetTextColor(C.sub[1], C.sub[2], C.sub[3])
end)
CloseBtn:SetScript("OnClick", function() Main:Hide() end)

-- ── SIDEBAR ──────────────────────────────────────────────────
local Sidebar = CreateFrame("Frame", nil, Main, "BackdropTemplate")
Sidebar:SetWidth(SIDEBAR_W)
Sidebar:SetPoint("TOPLEFT",    Main, "TOPLEFT",    2, -2)
Sidebar:SetPoint("BOTTOMLEFT", Main, "BOTTOMLEFT", 2,  2)
Backdrop(Sidebar, C.sidebar, C.border)

local sideDiv = Tex(Sidebar, C.accent[1], C.accent[2], C.accent[3], 0.35)
sideDiv:SetWidth(1)
sideDiv:SetPoint("TOPRIGHT",    Sidebar, "TOPRIGHT",    0,  0)
sideDiv:SetPoint("BOTTOMRIGHT", Sidebar, "BOTTOMRIGHT", 0,  0)

local LOGO_H = 80
local logoGlow = Tex(Sidebar, C.accent[1], C.accent[2], C.accent[3], 0.08)
logoGlow:SetPoint("TOPLEFT",  Sidebar, "TOPLEFT",  0, 0)
logoGlow:SetPoint("TOPRIGHT", Sidebar, "TOPRIGHT", 0, 0)
logoGlow:SetHeight(LOGO_H)

local logoTitle = Label(Sidebar, "GameFontNormalHuge", "Custom Font Loader",
    C.accent[1], C.accent[2], C.accent[3])
logoTitle:SetPoint("TOPLEFT", Sidebar, "TOPLEFT", PAD, -14)
logoTitle:SetWidth(SIDEBAR_W - PAD * 2)
logoTitle:SetWordWrap(true)

local logoAuth = Label(Sidebar, "GameFontNormal", "Author: Exxos",
    C.sub[1], C.sub[2], C.sub[3])
logoAuth:SetPoint("TOPLEFT", logoTitle, "BOTTOMLEFT", 0, -4)

local logoDivLine = Tex(Sidebar, C.accent[1], C.accent[2], C.accent[3], 0.25)
logoDivLine:SetHeight(1)
logoDivLine:SetPoint("TOPLEFT",  Sidebar, "TOPLEFT",  0, -LOGO_H)
logoDivLine:SetPoint("TOPRIGHT", Sidebar, "TOPRIGHT", 0, -LOGO_H)

-- version label bottom-left of sidebar
local verLabel = Sidebar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
verLabel:SetPoint("BOTTOMLEFT", Sidebar, "BOTTOMLEFT", 8, 6)
verLabel:SetTextColor(C.sub[1], C.sub[2], C.sub[3])
verLabel:SetJustifyH("LEFT")
verLabel:SetText("v" .. (C_AddOns.GetAddOnMetadata(addonName, "Version") or "?"))



-- ── NAV BUTTONS ───────────────────────────────────────────────
local NAV_Y   = LOGO_H + 40
local NAV_H   = 42
local activePane = nil

local function MakeNavBtn(label, yOffset)
    local btn = CreateFrame("Button", nil, Sidebar, "BackdropTemplate")
    btn:SetHeight(NAV_H)
    btn:SetPoint("TOPLEFT",  Sidebar, "TOPLEFT",  0, -(NAV_Y + yOffset))
    btn:SetPoint("TOPRIGHT", Sidebar, "TOPRIGHT", 0, -(NAV_Y + yOffset))

    local selBG = Tex(btn, C.accent[1], C.accent[2], C.accent[3], 0.00)
    selBG:SetAllPoints(btn)
    btn._selBG = selBG

    local selBar = Tex(btn, C.accent[1], C.accent[2], C.accent[3], 0.00)
    selBar:SetWidth(3)
    selBar:SetPoint("TOPLEFT",    btn, "TOPLEFT",    0, 0)
    selBar:SetPoint("BOTTOMLEFT", btn, "BOTTOMLEFT", 0, 0)
    btn._selBar = selBar

    local hl = Tex(btn, 1, 1, 1, 0.00)
    hl:SetAllPoints(btn)
    btn:SetScript("OnEnter", function(self)
        if activePane ~= self then hl:SetColorTexture(1, 1, 1, 0.05) end
    end)
    btn:SetScript("OnLeave", function()
        hl:SetColorTexture(1, 1, 1, 0.00)
    end)

    local lbl = Label(btn, "GameFontNormalLarge", label,
        C.sub[1], C.sub[2], C.sub[3])
    lbl:SetPoint("LEFT", btn, "LEFT", PAD + 10, 0)
    btn._lbl = lbl

    return btn
end

local NavInstructions = MakeNavBtn("Instructions", 0)
local NavFonts        = MakeNavBtn("Font Manager",  NAV_H)

-- ── RIGHT PANE ────────────────────────────────────────────────
local Pane = CreateFrame("Frame", nil, Main)
Pane:SetPoint("TOPLEFT",     Main, "TOPLEFT",     SIDEBAR_W + 1, -2)
Pane:SetPoint("BOTTOMRIGHT", Main, "BOTTOMRIGHT", -2,             2)

local HEADER_H = 66
local PaneHdr = CreateFrame("Frame", nil, Pane, "BackdropTemplate")
PaneHdr:SetHeight(HEADER_H)
PaneHdr:SetPoint("TOPLEFT",  Pane, "TOPLEFT",  0, 0)
PaneHdr:SetPoint("TOPRIGHT", Pane, "TOPRIGHT", 0, 0)
Backdrop(PaneHdr, {0.07, 0.05, 0.03, 1}, C.border)

local paneTitle = Label(PaneHdr, "GameFontNormalHuge", "",
    C.header[1], C.header[2], C.header[3])
paneTitle:SetPoint("BOTTOMLEFT", PaneHdr, "BOTTOMLEFT", PAD, 22)

local paneSub = Label(PaneHdr, "GameFontNormal", "",
    C.sub[1], C.sub[2], C.sub[3])
paneSub:SetPoint("BOTTOMLEFT", PaneHdr, "BOTTOMLEFT", PAD, 7)

local hdrDiv = Tex(Pane, C.border[1], C.border[2], C.border[3], 1)
hdrDiv:SetHeight(1)
hdrDiv:SetPoint("TOPLEFT",  Pane, "TOPLEFT",  0, -HEADER_H)
hdrDiv:SetPoint("TOPRIGHT", Pane, "TOPRIGHT", 0, -HEADER_H)

-- ============================================================
--  CONTENT FRAMES
-- ============================================================
local CONTENT_TOP = HEADER_H + 1

local function ContentFrame()
    local f = CreateFrame("Frame", nil, Pane)
    f:SetPoint("TOPLEFT",     Pane, "TOPLEFT",     0, -CONTENT_TOP)
    f:SetPoint("BOTTOMRIGHT", Pane, "BOTTOMRIGHT", 0,  1)
    f:Hide()
    return f
end

-- ── INSTRUCTIONS ─────────────────────────────────────────────
local InstrFrame = ContentFrame()
local PANE_TXT_W = FRAME_W - SIDEBAR_W - PAD * 2 - 20

local function ILine(text, yOff, font, r, g, b)
    font = font or "GameFontNormal"
    r, g, b = r or C.text[1], g or C.text[2], b or C.text[3]
    local fs = InstrFrame:CreateFontString(nil, "OVERLAY", font)
    fs:SetText(text)
    fs:SetTextColor(r, g, b)
    fs:SetJustifyH("LEFT")
    fs:SetWidth(PANE_TXT_W)
    fs:SetPoint("TOPLEFT", InstrFrame, "TOPLEFT", PAD, -yOff)
    return fs
end

local function ISep(yOff, alpha)
    local t = Tex(InstrFrame, C.border[1], C.border[2], C.border[3], alpha or 0.8)
    t:SetHeight(1)
    t:SetPoint("TOPLEFT",  InstrFrame, "TOPLEFT",  PAD,    -yOff)
    t:SetPoint("TOPRIGHT", InstrFrame, "TOPRIGHT", -PAD,   -yOff)
    return t
end

ILine("How to add a new font", 18, "GameFontNormalLarge",
    C.accent[1], C.accent[2], C.accent[3])
ISep(46, 0.3)

ILine("Step 1 — Place your font file", 58, "GameFontNormalLarge",
    C.header[1], C.header[2], C.header[3])
ILine("Copy your .ttf font file(s) into the addon Fonts folder:", 84)
ILine("   World of Warcraft\\_retail_\\Interface\\AddOns\\CustomFontLoader\\Fonts\\", 106,
    "GameFontNormal", C.accent[1], C.accent[2] * 0.85, C.accent[3] * 0.4)
ILine("Avoid spaces in filenames — use hyphens instead (e.g. My-Font-Bold.ttf).", 132)
ISep(156)

ILine("Step 2 — Reload", 172, "GameFontNormalLarge",
    C.header[1], C.header[2], C.header[3])
ILine("WoW must load the files before they can be registered. Type  /reload  or use the button below:", 198)

-- inline reload button in the instructions panel
local reloadBtn = CreateFrame("Button", nil, InstrFrame, "BackdropTemplate")
reloadBtn:SetSize(110, 26)
reloadBtn:SetPoint("TOPLEFT", InstrFrame, "TOPLEFT", PAD, -222)
Backdrop(reloadBtn, {0.30, 0.10, 0.55, 1.00}, {0.50, 0.20, 0.80, 1.00})
local reloadLbl = reloadBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
reloadLbl:SetText("Reload UI")
reloadLbl:SetTextColor(0.90, 0.80, 1.00)
reloadLbl:SetAllPoints(reloadBtn)
reloadLbl:SetJustifyH("CENTER")
reloadBtn:SetScript("OnEnter", function(self)
    self:SetBackdropColor(C.accent[1], C.accent[2], C.accent[3], 1)
end)
reloadBtn:SetScript("OnLeave", function(self)
    self:SetBackdropColor(0.30, 0.10, 0.55, 1.00)
end)
reloadBtn:SetScript("OnClick", function()
    CFLOpenOnLoad = true
    ReloadUI()
end)

ILine("If you use the button above, this addon will reopen automatically after the reload.", 258)

ISep(278)

ILine("Step 3 — Register in the Font Manager", 294, "GameFontNormalLarge",
    C.header[1], C.header[2], C.header[3])
ILine("Switch to the Font Manager tab, type the filename without the extension, then click Add Font.", 320)
ILine("The font is immediately available in EllesmereUI, Plater, BigWigs, and any other SharedMedia addon.", 346)
ISep(372)

ILine("Notes", 388, "GameFontNormalLarge", C.header[1], C.header[2], C.header[3])
ILine("   -  Fonts are saved automatically and re-registered every time you log in.", 414)
ILine("   -  Only .ttf files are supported — .otf files will not load in WoW.", 438)
ILine("   -  If you delete a font file, the game will not recognize the removal until a full restart.", 462)
ILine("   -  After a full restart, missing fonts are automatically cleaned up on login.", 486)

-- ── FONT MANAGER ─────────────────────────────────────────────
local FontFrame = ContentFrame()

local PANE_INNER = FRAME_W - SIDEBAR_W - 1
local ROW_H      = 30

-- ── PANEL 1: list header (top strip) ─────────────────────────
local ListHeader = CreateFrame("Frame", nil, FontFrame)
ListHeader:SetHeight(44)
ListHeader:SetPoint("TOPLEFT",  FontFrame, "TOPLEFT",  0, 0)
ListHeader:SetPoint("TOPRIGHT", FontFrame, "TOPRIGHT", 0, 0)

local listLabel = Label(ListHeader, "GameFontNormalLarge", "Registered Fonts",
    C.text[1], C.text[2], C.text[3])
listLabel:SetPoint("LEFT", ListHeader, "LEFT", PAD, 0)

local countLabel = Label(ListHeader, "GameFontNormalSmall", "",
    C.sub[1], C.sub[2], C.sub[3])
countLabel:SetPoint("RIGHT", ListHeader, "RIGHT", -PAD, 0)
countLabel:SetJustifyH("RIGHT")

local listDivTop = Tex(FontFrame, C.border[1], C.border[2], C.border[3], 0.8)
listDivTop:SetHeight(1)
listDivTop:SetPoint("TOPLEFT",  FontFrame, "TOPLEFT",  PAD,  -44)
listDivTop:SetPoint("TOPRIGHT", FontFrame, "TOPRIGHT", -PAD, -44)

-- ── PANEL 3: add section (bottom fixed-height panel) ─────────
local ADD_PANEL_H = 170
local AddPanel = CreateFrame("Frame", nil, FontFrame, "BackdropTemplate")
AddPanel:SetHeight(ADD_PANEL_H)
AddPanel:SetPoint("BOTTOMLEFT",  FontFrame, "BOTTOMLEFT",  0, 1)
AddPanel:SetPoint("BOTTOMRIGHT", FontFrame, "BOTTOMRIGHT", 0, 1)
AddPanel:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8X8"})
AddPanel:SetBackdropColor(0.04, 0.03, 0.07, 1)

local addPanelDiv = Tex(FontFrame, C.accent[1], C.accent[2], C.accent[3], 0.25)
addPanelDiv:SetHeight(1)
addPanelDiv:SetPoint("BOTTOMLEFT",  FontFrame, "BOTTOMLEFT",  PAD,  ADD_PANEL_H)
addPanelDiv:SetPoint("BOTTOMRIGHT", FontFrame, "BOTTOMRIGHT", -PAD, ADD_PANEL_H)

-- add section title — 16px below the divider line
local addTitle = Label(AddPanel, "GameFontNormalLarge", "Add New Font",
    C.header[1], C.header[2], C.header[3])
addTitle:SetPoint("TOPLEFT", AddPanel, "TOPLEFT", PAD, -14)

-- instruction text
local addInstr = Label(AddPanel, "GameFontNormal",
    "Place the .ttf file in the Fonts folder, enter the filename below, then click Add Font.",
    C.sub[1], C.sub[2], C.sub[3])
addInstr:SetPoint("TOPLEFT", addTitle, "BOTTOMLEFT", 0, -6)
addInstr:SetWidth(PANE_INNER - PAD * 2)

-- field label
local lblFile = Label(AddPanel, "GameFontNormal", "Font Filename",
    C.sub[1], C.sub[2], C.sub[3])
lblFile:SetPoint("TOPLEFT", addInstr, "BOTTOMLEFT", 0, -10)

-- sizes
local BTN_W        = 100
local FILE_INPUT_W = PANE_INNER - PAD * 2 - BTN_W - PAD - 8

-- edit box factory
local function MakeEditBox(parent, width, anchorPt, anchorTo, relPt, ox, oy)
    local eb = CreateFrame("EditBox", nil, parent, "BackdropTemplate")
    eb:SetHeight(32)
    eb:SetWidth(width)
    eb:SetPoint(anchorPt, anchorTo, relPt, ox, oy)
    Backdrop(eb, C.inputBg, C.border)
    eb:SetFontObject("GameFontNormal")
    eb:SetTextColor(C.text[1], C.text[2], C.text[3])
    eb:SetTextInsets(10, 10, 0, 0)
    eb:SetAutoFocus(false)
    eb:SetMaxLetters(256)
    eb:SetScript("OnEditFocusGained", function(self)
        self:SetBackdropBorderColor(C.accent[1], C.accent[2], C.accent[3], 0.9)
    end)
    eb:SetScript("OnEditFocusLost", function(self)
        self:SetBackdropBorderColor(C.border[1], C.border[2], C.border[3], 1)
    end)
    eb:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    eb:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)
    return eb
end

local fileBox = MakeEditBox(AddPanel, FILE_INPUT_W,
    "TOPLEFT", lblFile, "BOTTOMLEFT", 0, -4)

-- Add button
local AddBtn = CreateFrame("Button", nil, AddPanel, "BackdropTemplate")
AddBtn:SetSize(BTN_W, 32)
AddBtn:SetPoint("BOTTOMLEFT", fileBox, "BOTTOMRIGHT", 8, 0)
Backdrop(AddBtn, {0.30, 0.10, 0.55, 1.00}, {0.50, 0.20, 0.80, 1.00})
local addBtnLbl = AddBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
addBtnLbl:SetText("Add Font")
addBtnLbl:SetTextColor(0.90, 0.80, 1.00)
addBtnLbl:SetAllPoints(AddBtn)
addBtnLbl:SetJustifyH("CENTER")
AddBtn:SetScript("OnEnter", function(self)
    self:SetBackdropColor(C.accent[1], C.accent[2], C.accent[3], 1)
end)
AddBtn:SetScript("OnLeave", function(self)
    self:SetBackdropColor(0.30, 0.10, 0.55, 1.00)
end)

-- status line
local statusFS = Label(AddPanel, "GameFontNormal", "",
    C.sub[1], C.sub[2], C.sub[3])
statusFS:SetPoint("TOPLEFT", fileBox, "BOTTOMLEFT", 0, -6)
statusFS:SetWidth(PANE_INNER - PAD * 2)

local function SetStatus(msg, ok)
    statusFS:SetText(msg)
    if ok then
        statusFS:SetTextColor(C.accent[1], C.accent[2], C.accent[3])
    else
        statusFS:SetTextColor(1, 0.35, 0.20)
    end
end

-- ── PANEL 2: scroll list (fills between header and add panel) ─
local scrollFrame = CreateFrame("ScrollFrame", nil, FontFrame, "UIPanelScrollFrameTemplate")
scrollFrame:SetPoint("TOPLEFT",     FontFrame, "TOPLEFT",     PAD,       -46)
scrollFrame:SetPoint("BOTTOMRIGHT", FontFrame, "BOTTOMRIGHT", -(PAD + 22), ADD_PANEL_H + 2)

local scrollChild = CreateFrame("Frame", nil, scrollFrame)
scrollChild:SetWidth(1)
scrollChild:SetHeight(1)
scrollFrame:SetScrollChild(scrollChild)

-- ── FONT VALIDATION & SYNC ───────────────────────────────────
local probeString = nil

local function GetProbe()
    if not probeString then
        probeString = UIParent:CreateFontString(nil, "ARTWORK")
        probeString:Hide()
    end
    return probeString
end

local function FontFileExists(path)
    local probe = GetProbe()
    local ok = pcall(function() probe:SetFont(path, 12) end)
    return ok
end

local function SyncFonts()
    local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
    if not LSM then return 0, 0 end
    if not CFLPendingFonts then CFLPendingFonts = {} end

    local removed = 0
    for i = #CFLPendingFonts, 1, -1 do
        local entry = CFLPendingFonts[i]
        if FontFileExists(entry.path) then
            LSM:Register("font", entry.name, entry.path)
        else
            local hash = LSM:HashTable("font")
            if hash[entry.name] == entry.path then
                hash[entry.name] = nil
            end
            table.remove(CFLPendingFonts, i)
            removed = removed + 1
        end
    end

    local total = 0
    for _ in pairs(LSM:HashTable("font")) do total = total + 1 end
    return total, removed
end

local function PopulateFontList()
    for _, c in ipairs({scrollChild:GetChildren()}) do
        c:Hide(); c:SetParent(nil)
    end
    for _, r in ipairs({scrollChild:GetRegions()}) do r:Hide() end

    local LSM   = LibStub and LibStub("LibSharedMedia-3.0", true)
    local fonts = LSM and LSM:HashTable("font") or {}

    local sorted = {}
    for name in pairs(fonts) do sorted[#sorted + 1] = name end
    table.sort(sorted)

    -- update count label
    countLabel:SetText(#sorted .. " font(s) registered")

    local vw = scrollFrame:GetWidth()
    scrollChild:SetWidth(vw)
    scrollChild:SetHeight(math.max(#sorted * ROW_H, 1))

    for i, name in ipairs(sorted) do
        local path    = fonts[name]
        local missing = path and not FontFileExists(path)

        local row = CreateFrame("Frame", nil, scrollChild, "BackdropTemplate")
        row:SetHeight(ROW_H)
        row:SetPoint("TOPLEFT",  scrollChild, "TOPLEFT",  0, -(i - 1) * ROW_H)
        row:SetPoint("TOPRIGHT", scrollChild, "TOPRIGHT", 0, -(i - 1) * ROW_H)
        if i % 2 == 1 then
            Backdrop(row, C.row1, {0, 0, 0, 0})
        end

        local dot = row:CreateTexture(nil, "ARTWORK")
        dot:SetSize(5, 5)
        if missing then
            dot:SetColorTexture(0.8, 0.2, 0.2, 0.8)
        else
            dot:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.8)
        end
        dot:SetPoint("LEFT", row, "LEFT", 10, 0)

        local nameLbl = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        nameLbl:SetText(name)
        if missing then
            nameLbl:SetTextColor(0.6, 0.2, 0.2)
        else
            nameLbl:SetTextColor(C.text[1], C.text[2], C.text[3])
        end
        nameLbl:SetPoint("LEFT", dot, "RIGHT", 6, 0)
        nameLbl:SetJustifyH("LEFT")

        if missing then
            local missingLbl = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            missingLbl:SetText("file not found")
            missingLbl:SetTextColor(0.6, 0.2, 0.2)
            missingLbl:SetPoint("RIGHT", row, "RIGHT", -8, 0)
            missingLbl:SetJustifyH("RIGHT")
        elseif path then
            local prev = row:CreateFontString(nil, "OVERLAY")
            local ok = pcall(function() prev:SetFont(path, 12) end)
            if ok then
                prev:SetText("Aa Bb Cc 123")
                prev:SetTextColor(C.sub[1], C.sub[2], C.sub[3])
                prev:SetPoint("RIGHT", row, "RIGHT", -8, 0)
                prev:SetJustifyH("RIGHT")
            else
                prev:Hide()
            end
        end
    end

    if #sorted == 0 then
        local empty = scrollChild:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        empty:SetText("No fonts registered yet.  Add one below.")
        empty:SetTextColor(C.sub[1], C.sub[2], C.sub[3])
        empty:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 8, -10)
    end
end



AddBtn:SetScript("OnClick", function()
    local LSM  = LibStub and LibStub("LibSharedMedia-3.0", true)
    local file = fileBox:GetText():match("^%s*(.-)%s*$")

    if not LSM then SetStatus("LibSharedMedia-3.0 not found.", false); return end
    if file == "" then SetStatus("Enter a font filename.", false); return end

    -- check for a wrong extension
    local ext = file:match("%.([^%.]+)$")
    if ext and ext:lower() ~= "ttf" then
        SetStatus('".' .. ext .. '" is not a valid format for WoW. Only .ttf files are supported.', false)
        return
    end

    -- strip .ttf if they included it, then re-append cleanly
    file = file:gsub("%.ttf$", ""):gsub("%.TTF$", "")
    local fileName   = file .. ".ttf"
    local displayName = file  -- use filename (without extension) as display name
    local fullPath   = "Interface\\AddOns\\CustomFontLoader\\Fonts\\" .. fileName

    -- validate the file actually loads
    if not FontFileExists(fullPath) then
        SetStatus('"' .. fileName .. '" not found in the Fonts folder. Check the filename and try again.', false)
        return
    end

    if not CFLPendingFonts then CFLPendingFonts = {} end

    local exists = false
    for _, e in ipairs(CFLPendingFonts) do
        if e.name == displayName then e.path = fullPath; exists = true; break end
    end
    if not exists then
        table.insert(CFLPendingFonts, {name = displayName, path = fullPath})
    end

    LSM:Register("font", displayName, fullPath)

    local total, removed = SyncFonts()

    fileBox:SetText("")
    PopulateFontList()

    local msg = '"' .. displayName .. '" added.'
    if removed > 0 then
        msg = msg .. "  Removed " .. removed .. " missing font(s)."
    end
    SetStatus(msg, true)
    print("|cFFAA44FFCustomFontLoader:|r Registered font: " .. displayName)
end)

-- ============================================================
--  NAV switching
-- ============================================================
local function SelectTab(navBtn, contentFrame, title, sub)
    InstrFrame:Hide()
    FontFrame:Hide()

    for _, btn in ipairs({NavInstructions, NavFonts}) do
        btn._selBG:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.00)
        btn._selBar:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.00)
        btn._lbl:SetTextColor(C.sub[1], C.sub[2], C.sub[3])
    end

    navBtn._selBG:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.12)
    navBtn._selBar:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 1.00)
    navBtn._lbl:SetTextColor(C.accent[1], C.accent[2], C.accent[3])
    activePane = navBtn

    paneTitle:SetText(title)
    paneSub:SetText(sub)
    contentFrame:Show()
end

NavInstructions:SetScript("OnClick", function()
    SelectTab(NavInstructions, InstrFrame,
        "Instructions", "How to add fonts to CustomFontLoader")
end)

NavFonts:SetScript("OnClick", function()
    SelectTab(NavFonts, FontFrame,
        "Font Manager", "Fonts registered with LibSharedMedia-3.0")
    PopulateFontList()
end)

Main:SetScript("OnShow", function()
    SelectTab(NavInstructions, InstrFrame,
        "Instructions", "How to add fonts to CustomFontLoader")
end)

-- ============================================================
--  On load: sync (validates files, removes dead entries, re-registers live ones)
-- ============================================================
local loader = CreateFrame("Frame")
loader:RegisterEvent("ADDON_LOADED")
loader:SetScript("OnEvent", function(self, event, addon)
    if addon ~= addonName then return end
    local total, removed = SyncFonts()
    if removed > 0 then
        print("|cFFAA44FFCustomFontLoader:|r Removed " .. removed
            .. " font(s) with missing files.  " .. total .. " font(s) active.")
    end
    -- if the reload button was used, open the UI once then clear the flag
    if CFLOpenOnLoad then
        CFLOpenOnLoad = nil
        -- small delay to ensure UI is fully ready before showing
        C_Timer.After(0.5, function()
            Main:Show()
        end)
    end
end)

-- ============================================================
--  Slash command
-- ============================================================
SLASH_CFL1 = "/cfl"
SlashCmdList["CFL"] = function()
    if Main:IsShown() then Main:Hide() else Main:Show() end
end

local Options = CreateFrame("Frame")
Options.name  = addonName
if InterfaceOptions_AddCategory then
    InterfaceOptions_AddCategory(Options)
end

-- ============================================================
--  Minimap Button
-- ============================================================
local MinimapBtn = CreateFrame("Button", "CFLMinimapButton", Minimap)
MinimapBtn:SetSize(32, 32)
MinimapBtn:SetFrameStrata("MEDIUM")
MinimapBtn:SetFrameLevel(8)
MinimapBtn:SetClampedToScreen(true)
MinimapBtn:SetMovable(true)
MinimapBtn:RegisterForClicks("anyUp")
MinimapBtn:RegisterForDrag("LeftButton")

-- black background circle behind the icon
local bg = MinimapBtn:CreateTexture(nil, "BACKGROUND")
bg:SetSize(25, 25)
bg:SetPoint("CENTER", 0, 0)
bg:SetTexture("Interface\\Minimap\\UI-Minimap-Background")
bg:SetVertexColor(0, 0, 0, 1)

-- icon — sized to sit inside the border ring
local icon = MinimapBtn:CreateTexture(nil, "ARTWORK")
icon:SetSize(18, 18)
icon:SetPoint("CENTER", 0, 0)
icon:SetTexture("Interface\\AddOns\\CustomFontLoader\\cfl.png")

-- gold border overlay (standard minimap button look)
local overlay = MinimapBtn:CreateTexture(nil, "OVERLAY")
overlay:SetSize(53, 53)
overlay:SetPoint("TOPLEFT", MinimapBtn, "TOPLEFT", 0, 0)
overlay:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")

-- circular highlight on hover
MinimapBtn:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")

-- tooltip
local isDragging = false
MinimapBtn:SetScript("OnEnter", function(self)
    if isDragging then return end
    GameTooltip:SetOwner(self, "ANCHOR_NONE")
    GameTooltip:SetPoint("TOPRIGHT", self, "TOPLEFT", -2, 0)
    GameTooltip:SetText("Custom Font Loader", 0.65, 0.30, 1.00)
    GameTooltip:AddLine("Left-click: Toggle UI", 0.82, 0.78, 0.90)
    GameTooltip:AddLine("Drag: Reposition", 0.82, 0.78, 0.90)
    GameTooltip:Show()
end)
MinimapBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)

-- click to toggle
MinimapBtn:SetScript("OnClick", function()
    if Main:IsShown() then Main:Hide() else Main:Show() end
end)

-- position on minimap edge
local function UpdateMinimapPos(angle)
    local mw, mh   = Minimap:GetWidth(), Minimap:GetHeight()
    local radius   = (math.max(mw, mh) / 2) + 5
    local rad      = math.rad(angle)
    MinimapBtn:ClearAllPoints()
    MinimapBtn:SetPoint("CENTER", Minimap, "CENTER",
        math.cos(rad) * radius, math.sin(rad) * radius)
end

-- drag to reposition
local function DragOnUpdate()
    local mx, my   = Minimap:GetCenter()
    local cx, cy   = GetCursorPosition()
    local scale    = Minimap:GetEffectiveScale()
    cx, cy         = cx / scale, cy / scale
    CFLMinimapAngle = math.deg(math.atan2(cy - my, cx - mx))
    UpdateMinimapPos(CFLMinimapAngle)
end

MinimapBtn:SetScript("OnDragStart", function(self)
    isDragging = true
    self:LockHighlight()
    self:SetScript("OnUpdate", DragOnUpdate)
    GameTooltip:Hide()
end)

MinimapBtn:SetScript("OnDragStop", function(self)
    self:SetScript("OnUpdate", nil)
    self:UnlockHighlight()
    isDragging = false
    UpdateMinimapPos(CFLMinimapAngle)
end)

-- initialise position from saved angle (default 220 like EllesmereUI)
local initLoader = CreateFrame("Frame")
initLoader:RegisterEvent("ADDON_LOADED")
initLoader:SetScript("OnEvent", function(self, event, addon)
    if addon ~= addonName then return end
    if not CFLMinimapAngle then CFLMinimapAngle = 220 end
    UpdateMinimapPos(CFLMinimapAngle)
end)