local _, ns = ...

------------------------------------------------------------
-- AppFrame: unified navigation shell for YippYapp Helper
------------------------------------------------------------
local FRAME_W, FRAME_H = ns:GetAppFrameSize()
local PAD       = 14
local HEADER_H  = 34
local CARD_GAP  = 14
local CW        = FRAME_W - PAD * 2

------------------------------------------------------------
-- Main frame
------------------------------------------------------------
local app = CreateFrame("Frame", "YippYappApp", UIParent, "BackdropTemplate")
app:SetSize(FRAME_W, FRAME_H)
app:SetPoint("CENTER")
app:SetMovable(true)
app:EnableMouse(true)
app:RegisterForDrag("LeftButton")
app:SetScript("OnDragStart", app.StartMoving)
app:SetScript("OnDragStop", app.StopMovingOrSizing)
app:SetClampedToScreen(true)
app:SetFrameStrata("HIGH")
app:SetBackdrop({
    bgFile   = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    edgeSize = 16,
    insets   = { left = 4, right = 4, top = 4, bottom = 4 },
})
app:SetBackdropColor(0.05, 0.05, 0.05, 0.97)
app:SetBackdropBorderColor(0.35, 0.35, 0.35, 1)
ns.SmoothFrame(app)
app:Hide()
ns.AppFrame = app

-- ESC to close
app:SetScript("OnShow", function()
    tinsert(UISpecialFrames, "YippYappApp")
end)
app:SetScript("OnHide", function()
    for i = #UISpecialFrames, 1, -1 do
        if UISpecialFrames[i] == "YippYappApp" then
            table.remove(UISpecialFrames, i)
            break
        end
    end
    -- Close any open popups
    if YippYappProfileDropdown then YippYappProfileDropdown:Hide() end
    if YippYappPIPicker then YippYappPIPicker:Hide() end
    -- Restore any embedded sub-panel to standalone mode
    if ns.ShowAppPage then
        ns:ShowAppPage("home")
    end
end)

------------------------------------------------------------
-- Header overlay (always renders above sub-panels)
------------------------------------------------------------
local headerOverlay = CreateFrame("Frame", nil, app)
headerOverlay:SetAllPoints(app)
headerOverlay:SetFrameLevel(app:GetFrameLevel() + 100)

-- Close button (parented to headerOverlay for rendering, but closes the app frame)
local closeBtn = CreateFrame("Button", nil, headerOverlay, "UIPanelCloseButton")
closeBtn:SetPoint("TOPRIGHT", app, "TOPRIGHT", -2, -2)
closeBtn:SetScript("OnClick", function() app:Hide() end)

-- Home mode elements
local homeIcon = headerOverlay:CreateTexture(nil, "ARTWORK")
homeIcon:SetSize(22, 22)
homeIcon:SetPoint("TOPLEFT", app, "TOPLEFT", PAD, -7)
homeIcon:SetTexture("Interface\\AddOns\\YippYappHelper\\icon")
homeIcon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

local homeTitle = headerOverlay:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
homeTitle:SetPoint("LEFT", homeIcon, "RIGHT", 6, 0)
homeTitle:SetText("|cff00ff00YippYapp Helper|r")
ns.ApplyTextShadow(homeTitle)

local homeSub = headerOverlay:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
homeSub:SetPoint("LEFT", homeTitle, "RIGHT", 8, 0)
homeSub:SetTextColor(unpack(ns.COLORS.TEXT_TERTIARY))
homeSub:SetText("Midnight Season 1")
ns.ApplyTextShadow(homeSub)

-- Sub-page mode: back button + page title
local backBtn = CreateFrame("Button", nil, headerOverlay, "BackdropTemplate")
backBtn:SetSize(60, 22)
backBtn:SetPoint("TOPLEFT", app, "TOPLEFT", PAD, -6)
backBtn:SetBackdrop({
    bgFile   = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    edgeSize = 8,
    insets   = { left = 2, right = 2, top = 2, bottom = 2 },
})
backBtn:SetBackdropColor(0.12, 0.12, 0.12, 1)
backBtn:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.6)

local backFs = backBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
backFs:SetPoint("CENTER")
backFs:SetText("|cffaaaaaa< Back|r")

backBtn:SetScript("OnEnter", function(self)
    self:SetBackdropColor(0.18, 0.18, 0.18, 1)
    self:SetBackdropBorderColor(0.5, 0.5, 0.5, 0.8)
end)
backBtn:SetScript("OnLeave", function(self)
    self:SetBackdropColor(0.12, 0.12, 0.12, 1)
    self:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.6)
end)
backBtn:Hide()

local pageTitle = headerOverlay:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
pageTitle:SetPoint("LEFT", backBtn, "RIGHT", 8, 0)
pageTitle:Hide()

------------------------------------------------------------
-- Page containers (content area below header)
------------------------------------------------------------
local pages = {}
local currentPage = "home"

local function MakePage(id)
    local c = CreateFrame("Frame", nil, app)
    c:SetPoint("TOPLEFT", 0, -HEADER_H)
    c:SetPoint("BOTTOMRIGHT", 0, 0)
    c:Hide()
    pages[id] = c
    return c
end

-- Home page
local homePage = CreateFrame("Frame", nil, app)
homePage:SetPoint("TOPLEFT", 0, -HEADER_H)
homePage:SetPoint("BOTTOMRIGHT", 0, 0)
pages.home = homePage

MakePage("gear")
MakePage("trinkets")
MakePage("raid")
MakePage("progression")
MakePage("loot")
MakePage("consumables")

------------------------------------------------------------
-- Page navigation
------------------------------------------------------------
local PAGE_INFO = {
    gear        = { title = "|cff00ff00Gear Upgrades|r" },
    trinkets    = { title = "|cffff8000Trinket Rankings|r" },
    raid        = { title = "|cff00aaffRaid Tools|r" },
    progression = { title = "|cffffff00Progression|r" },
    loot        = { title = "|cff00ccffLoot Browser|r" },
    consumables = { title = "|cffff00ffConsumables|r" },
}

local CONTENT_W = FRAME_W
local CONTENT_H = FRAME_H - HEADER_H

-- Restore a sub-panel's strata to standalone mode
local function RestoreFrameLevel(f)
    -- Gear panel uses MEDIUM so it stays behind the app; others use HIGH
    if f == ns.MainFrame then
        f:SetFrameStrata("MEDIUM")
    else
        f:SetFrameStrata("HIGH")
    end
end

local function LeaveCurrentPage()
    if currentPage == "gear" and ns.MainFrame then
        ns.MainFrame.inAppMode = nil
        ns.MainFrame:Hide()
        ns.MainFrame:SetParent(UIParent)
        RestoreFrameLevel(ns.MainFrame)
        if ns.SetGearAppMode then ns:SetGearAppMode(false) end
    elseif currentPage == "trinkets" and ns.TrinketPanel then
        ns.TrinketPanel.inAppMode = nil
        ns.TrinketPanel:Hide()
        ns.TrinketPanel:SetParent(UIParent)
        RestoreFrameLevel(ns.TrinketPanel)
        if ns.SetTrinketAppMode then ns:SetTrinketAppMode(false) end
    elseif currentPage == "raid" and ns.RaidFrame then
        ns.RaidFrame.inAppMode = nil
        ns.RaidFrame:Hide()
        ns.RaidFrame:SetParent(UIParent)
        RestoreFrameLevel(ns.RaidFrame)
        if ns.SetRaidAppMode then ns:SetRaidAppMode(false) end
    elseif currentPage == "progression" and ns.ProgressionFrame then
        ns.ProgressionFrame.inAppMode = nil
        ns.ProgressionFrame:Hide()
        ns.ProgressionFrame:SetParent(UIParent)
        RestoreFrameLevel(ns.ProgressionFrame)
        if ns.SetProgressionAppMode then ns:SetProgressionAppMode(false) end
    elseif currentPage == "loot" and ns.LootBrowserFrame then
        ns.LootBrowserFrame.inAppMode = nil
        ns.LootBrowserFrame:Hide()
        ns.LootBrowserFrame:SetParent(UIParent)
        RestoreFrameLevel(ns.LootBrowserFrame)
        if ns.SetLootBrowserAppMode then ns:SetLootBrowserAppMode(false) end
    elseif currentPage == "consumables" and ns.ConsumablesFrame then
        ns.ConsumablesFrame.inAppMode = nil
        ns.ConsumablesFrame:Hide()
        ns.ConsumablesFrame:SetParent(UIParent)
        RestoreFrameLevel(ns.ConsumablesFrame)
        if ns.SetConsumablesAppMode then ns:SetConsumablesAppMode(false) end
    end
end

-- Normalize a sub-panel's strata/level to sit inside the app frame
-- but below the header overlay (which is at +100)
local function NormalizeFrameLevel(f)
    f:SetFrameStrata(app:GetFrameStrata())
    f:SetFrameLevel(app:GetFrameLevel() + 5)
end

local function EnterPage(id)
    if id == "gear" and ns.MainFrame then
        local f = ns.MainFrame
        f.inAppMode = true
        if ns.SetGearAppMode then ns:SetGearAppMode(true, CONTENT_W) end
        f:SetParent(pages.gear)
        f:ClearAllPoints()
        f:SetPoint("TOPLEFT", pages.gear, "TOPLEFT", 0, 0)
        NormalizeFrameLevel(f)
        f:Show()
        if ns.RefreshAllSlots then ns:RefreshAllSlots() end
        if ns.RefreshCrests then ns:RefreshCrests() end
        if ns.RefreshSuggestions then ns:RefreshSuggestions() end

    elseif id == "trinkets" and ns.TrinketPanel then
        local f = ns.TrinketPanel
        f.inAppMode = true
        if ns.SetTrinketAppMode then ns:SetTrinketAppMode(true, CONTENT_W, CONTENT_H) end
        f:SetParent(pages.trinkets)
        f:ClearAllPoints()
        f:SetPoint("TOPLEFT", pages.trinkets, "TOPLEFT", 0, 0)
        NormalizeFrameLevel(f)
        f:Show()
        if ns.RefreshTrinketPanel then ns:RefreshTrinketPanel() end

    elseif id == "raid" and ns.RaidFrame then
        local f = ns.RaidFrame
        f.inAppMode = true
        if ns.SetRaidAppMode then ns:SetRaidAppMode(true, CONTENT_W, CONTENT_H) end
        f:SetParent(pages.raid)
        f:ClearAllPoints()
        f:SetPoint("TOPLEFT", pages.raid, "TOPLEFT", 0, 0)
        NormalizeFrameLevel(f)
        f:Show()
        if ns.RefreshRaidOverview then ns:RefreshRaidOverview() end

    elseif id == "progression" and ns.ProgressionFrame then
        local f = ns.ProgressionFrame
        f.inAppMode = true
        f:SetParent(pages.progression)
        f:ClearAllPoints()
        f:SetPoint("TOPLEFT", pages.progression, "TOPLEFT", 0, HEADER_H)
        f:SetPoint("BOTTOMRIGHT", pages.progression, "BOTTOMRIGHT", 0, 0)
        NormalizeFrameLevel(f)
        f:Show()
        if ns.SetProgressionAppMode then ns:SetProgressionAppMode(true) end

    elseif id == "loot" then
        if not ns.LootBrowserFrame then
            ns:CreateLootBrowserFrame()
        end
        local f = ns.LootBrowserFrame
        f.inAppMode = true
        if ns.SetLootBrowserAppMode then ns:SetLootBrowserAppMode(true, CONTENT_W, CONTENT_H) end
        f:SetParent(pages.loot)
        f:ClearAllPoints()
        f:SetPoint("TOPLEFT", pages.loot, "TOPLEFT", 0, 0)
        NormalizeFrameLevel(f)
        f:Show()
        ns:LootBrowser_DetectSpec()
        ns:LootBrowser_ShowPage()

    elseif id == "consumables" and ns.ConsumablesFrame then
        local f = ns.ConsumablesFrame
        f.inAppMode = true
        if ns.SetConsumablesAppMode then ns:SetConsumablesAppMode(true, CONTENT_W, CONTENT_H) end
        f:SetParent(pages.consumables)
        f:ClearAllPoints()
        f:SetPoint("TOPLEFT", pages.consumables, "TOPLEFT", 0, 0)
        NormalizeFrameLevel(f)
        f:Show()
        if ns.RefreshConsumables then ns:RefreshConsumables() end
    end
end

function ns:ShowAppPage(id)
    LeaveCurrentPage()

    for pageId, page in pairs(pages) do
        if pageId == id then
            page:Show()
        else
            page:Hide()
        end
    end

    currentPage = id
    ns.currentAppPage = id

    if id == "home" then
        homeIcon:Show()
        homeTitle:Show()
        homeSub:Show()
        backBtn:Hide()
        pageTitle:Hide()
    else
        homeIcon:Hide()
        homeTitle:Hide()
        homeSub:Hide()
        backBtn:Show()
        local info = PAGE_INFO[id]
        pageTitle:SetText(info and info.title or id)
        pageTitle:Show()
        EnterPage(id)
    end
end

backBtn:SetScript("OnClick", function()
    ns:ShowAppPage("home")
end)

------------------------------------------------------------
-- Home page dashboard
------------------------------------------------------------

-- ── Row 1: Character bar ─────────────────────────────────
local charBar = CreateFrame("Frame", nil, homePage, "BackdropTemplate")
charBar:SetSize(CW, 50)
charBar:SetPoint("TOPLEFT", homePage, "TOPLEFT", PAD, -PAD)
charBar:SetBackdrop({
    bgFile   = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    edgeSize = 10,
    insets   = { left = 2, right = 2, top = 2, bottom = 2 },
})
charBar:SetBackdropColor(0.08, 0.08, 0.08, 0.9)
charBar:SetBackdropBorderColor(0.25, 0.25, 0.25, 0.5)
ns.SmoothFrame(charBar)

local charSpecFs = charBar:CreateFontString(nil, "OVERLAY", "GameFontNormal")
charSpecFs:SetPoint("LEFT", 14, 0)
ns.ApplyTextShadow(charSpecFs)

local charIlvlFs = charBar:CreateFontString(nil, "OVERLAY", "GameFontNormal")
charIlvlFs:SetPoint("CENTER", 0, 0)
ns.ApplyTextShadow(charIlvlFs)

-- Forward declare
local UpdateProfileDisplay
local RefreshDashboard

-- Profile dropdown button
local profileBtn = CreateFrame("Button", nil, charBar, "BackdropTemplate")
profileBtn:SetSize(140, 34)
profileBtn:SetPoint("RIGHT", -8, 0)
profileBtn:SetBackdrop({
    bgFile   = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    edgeSize = 8,
    insets   = { left = 2, right = 2, top = 2, bottom = 2 },
})
profileBtn:SetBackdropColor(0.08, 0.08, 0.08, 0.8)
profileBtn:SetBackdropBorderColor(0.3, 0.3, 0.3, 0.5)
ns.SmoothFrame(profileBtn)
ns.AddGlowHighlight(profileBtn, 0.06)

local profileNameFs = profileBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
profileNameFs:SetPoint("LEFT", 8, 0)
ns.ApplyTextShadow(profileNameFs)

local profileArrow = profileBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
profileArrow:SetPoint("RIGHT", -6, 0)
profileArrow:SetTextColor(unpack(ns.COLORS.TEXT_TERTIARY))
profileArrow:SetText("v")

-- Profile dropdown popup
local profileDropdown = CreateFrame("Frame", "YippYappProfileDropdown", UIParent, "BackdropTemplate")
profileDropdown:SetSize(220, 10)
profileDropdown:SetFrameStrata("DIALOG")
profileDropdown:SetClampedToScreen(true)
profileDropdown:SetBackdrop({
    bgFile   = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    edgeSize = 12,
    insets   = { left = 3, right = 3, top = 3, bottom = 3 },
})
profileDropdown:SetBackdropColor(0.06, 0.06, 0.06, 0.97)
profileDropdown:SetBackdropBorderColor(0.5, 0.5, 0.5, 0.8)
ns.SmoothFrame(profileDropdown)
profileDropdown:EnableMouse(true)
profileDropdown:Hide()

profileDropdown:SetScript("OnShow", function()
    tinsert(UISpecialFrames, "YippYappProfileDropdown")
end)
profileDropdown:SetScript("OnHide", function()
    for i = #UISpecialFrames, 1, -1 do
        if UISpecialFrames[i] == "YippYappProfileDropdown" then
            table.remove(UISpecialFrames, i)
            break
        end
    end
end)

local PROFILE_DESCS = {
    normal  = "Casual / Delves / M+ up to 8",
    heroic  = "Heroic Raid / M+ 10",
    mythic  = "Mythic Raid / M+ 10+",
}

local profileRows = {}
local yOff = 8
for i, p in ipairs(ns.PROFILES) do
    local row = CreateFrame("Button", nil, profileDropdown)
    row:SetSize(204, 28)
    row:SetPoint("TOPLEFT", 8, -yOff)

    local hl = row:CreateTexture(nil, "HIGHLIGHT")
    hl:SetAllPoints()
    hl:SetColorTexture(1, 1, 1, 0.05)
    hl:SetBlendMode("ADD")

    row.nameFs = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    row.nameFs:SetPoint("TOPLEFT", 6, -3)

    row.descFs = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    row.descFs:SetPoint("TOPLEFT", 6, -16)
    row.descFs:SetTextColor(unpack(ns.COLORS.TEXT_TERTIARY))
    row.descFs:SetText(PROFILE_DESCS[p.id] or "")

    row.check = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    row.check:SetPoint("RIGHT", -6, 0)

    local pid = p.id
    local pname = p.name
    row:SetScript("OnClick", function()
        YippYappHelperDB.profile = pid
        UpdateProfileDisplay()
        profileDropdown:Hide()
        -- Refresh dashboard (summary, farm guide depend on profile)
        RefreshDashboard()
        -- Refresh gear panel if open
        if ns.MainFrame and ns.MainFrame:IsShown() then
            if ns.RefreshAllSlots then ns:RefreshAllSlots() end
            if ns.RefreshSuggestions then ns:RefreshSuggestions() end
        end
    end)

    profileRows[i] = { row = row, id = pid, name = pname }
    yOff = yOff + 28
end
profileDropdown:SetHeight(yOff + 6)

UpdateProfileDisplay = function()
    local profile = ns:GetCurrentProfile()
    local color = "|cff00aaff"
    profileNameFs:SetText(color .. profile.name .. "|r")

    -- Update checkmarks in dropdown
    for _, pr in ipairs(profileRows) do
        local isActive = (pr.id == profile.id)
        local nameColor = isActive and "|cffffffff" or "|cff" .. "aaaaaa"
        pr.row.nameFs:SetText(nameColor .. pr.name .. "|r")
        pr.row.check:SetText(isActive and "|cff00ff00>|r" or "")

        if isActive then
            local bg = pr.row.bg
            if not bg then
                bg = pr.row:CreateTexture(nil, "BACKGROUND")
                bg:SetAllPoints()
                pr.row.bg = bg
            end
            bg:SetColorTexture(0, 1, 0, 0.04)
            bg:Show()
        elseif pr.row.bg then
            pr.row.bg:Hide()
        end
    end
end

profileBtn:SetScript("OnClick", function(self)
    if profileDropdown:IsShown() then
        profileDropdown:Hide()
    else
        UpdateProfileDisplay()
        profileDropdown:ClearAllPoints()
        profileDropdown:SetPoint("TOPRIGHT", self, "BOTTOMRIGHT", 0, -2)
        profileDropdown:Show()
    end
end)

UpdateProfileDisplay()

-- ── Row 2: Crest overview ────────────────────────────────
local CREST_ROW_Y = -(PAD + 50 + 10)
local CREST_BAR_H = 48
local CREST_COLS = 5
local CREST_GAP = 8
local CREST_W = math.floor((CW - (CREST_COLS - 1) * CREST_GAP) / CREST_COLS)

local CREST_TRACKS = {
    { track = "Adventurer", hex = "ff1eff00", r = 0.12, g = 1.00, b = 0.00 },
    { track = "Veteran",    hex = "ff0070dd", r = 0.00, g = 0.44, b = 0.87 },
    { track = "Champion",   hex = "ffa335ee", r = 0.64, g = 0.21, b = 0.93 },
    { track = "Hero",       hex = "ffff8000", r = 1.00, g = 0.50, b = 0.00 },
    { track = "Myth",       hex = "ffff0000", r = 1.00, g = 0.00, b = 0.00 },
}

local crestFrames = {}

for i, ct in ipairs(CREST_TRACKS) do
    local x = PAD + (i - 1) * (CREST_W + CREST_GAP)
    local bar = CreateFrame("Frame", nil, homePage, "BackdropTemplate")
    bar:SetSize(CREST_W, CREST_BAR_H)
    bar:SetPoint("TOPLEFT", homePage, "TOPLEFT", x, CREST_ROW_Y)
    bar:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 8,
        insets   = { left = 2, right = 2, top = 2, bottom = 2 },
    })
    bar:SetBackdropColor(0.08, 0.08, 0.08, 0.9)
    bar:SetBackdropBorderColor(ct.r * 0.4, ct.g * 0.4, ct.b * 0.4, 0.6)
    ns.SmoothFrame(bar)

    -- Track name (top-left, colored)
    local nameFs = bar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    nameFs:SetPoint("TOPLEFT", 8, -5)
    nameFs:SetText("|c" .. ct.hex .. ct.track .. "|r")
    ns.ApplyTextShadow(nameFs)

    -- Season cap info (top-right)
    local upgFs = bar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    upgFs:SetPoint("TOPRIGHT", -8, -5)
    upgFs:SetJustifyH("RIGHT")

    -- Crest icon (bottom-left)
    local iconTex = bar:CreateTexture(nil, "ARTWORK")
    iconTex:SetSize(20, 20)
    iconTex:SetPoint("BOTTOMLEFT", 8, 5)
    iconTex:SetTexCoord(0.08, 0.92, 0.08, 0.92)

    -- Count (large, next to icon, bottom)
    local countFs = bar:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    countFs:SetPoint("LEFT", iconTex, "RIGHT", 5, 0)
    countFs:SetText("0")

    crestFrames[ct.track] = { bar = bar, iconTex = iconTex, countFs = countFs, upgFs = upgFs }
end

-- ── Row 3: Upgrade summary (left) + Farm guide (right) ──
local SUMMARY_Y = CREST_ROW_Y - CREST_BAR_H - 14
local SUMMARY_GAP = 10
local SUMMARY_W = math.floor((CW - SUMMARY_GAP) / 2)
local SUMMARY_H = 160

-- Left: Upgrade Summary
local summaryPanel = CreateFrame("Frame", nil, homePage, "BackdropTemplate")
summaryPanel:SetSize(SUMMARY_W, SUMMARY_H)
summaryPanel:SetPoint("TOPLEFT", homePage, "TOPLEFT", PAD, SUMMARY_Y)
summaryPanel:SetBackdrop({
    bgFile   = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    edgeSize = 10,
    insets   = { left = 2, right = 2, top = 2, bottom = 2 },
})
summaryPanel:SetBackdropColor(0.08, 0.08, 0.08, 0.9)
summaryPanel:SetBackdropBorderColor(0.25, 0.25, 0.25, 0.5)
ns.SmoothFrame(summaryPanel)

local summaryTitle = summaryPanel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
summaryTitle:SetPoint("TOPLEFT", 14, -10)
summaryTitle:SetTextColor(unpack(ns.COLORS.TEXT_HEADER))
summaryTitle:SetText("Upgrade Summary")
ns.ApplyTextShadow(summaryTitle)

local summaryLines = {}
for i = 1, 6 do
    local fs = summaryPanel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    fs:SetPoint("TOPLEFT", 14, -30 - (i - 1) * 20)
    fs:SetWidth(SUMMARY_W - 28)
    fs:SetJustifyH("LEFT")
    fs:SetWordWrap(false)
    fs:SetText("")
    summaryLines[i] = fs
end

-- Right: Farm Guide
local farmPanel = CreateFrame("Frame", nil, homePage, "BackdropTemplate")
farmPanel:SetSize(SUMMARY_W, SUMMARY_H)
farmPanel:SetPoint("TOPLEFT", homePage, "TOPLEFT", PAD + SUMMARY_W + SUMMARY_GAP, SUMMARY_Y)
farmPanel:SetBackdrop({
    bgFile   = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    edgeSize = 10,
    insets   = { left = 2, right = 2, top = 2, bottom = 2 },
})
farmPanel:SetBackdropColor(0.08, 0.08, 0.08, 0.9)
farmPanel:SetBackdropBorderColor(0.25, 0.25, 0.25, 0.5)
ns.SmoothFrame(farmPanel)

local farmTitle = farmPanel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
farmTitle:SetPoint("TOPLEFT", 14, -10)
farmTitle:SetTextColor(unpack(ns.COLORS.TEXT_HEADER))
farmTitle:SetText("Farm Guide")
ns.ApplyTextShadow(farmTitle)

local farmLines = {}
for i = 1, 6 do
    local fs = farmPanel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    fs:SetPoint("TOPLEFT", 14, -30 - (i - 1) * 20)
    fs:SetWidth(SUMMARY_W - 28)
    fs:SetJustifyH("LEFT")
    fs:SetWordWrap(false)
    fs:SetText("")
    farmLines[i] = fs
end

-- ── Row 4: Navigation buttons ────────────────────────────
local NAV_DEFS = {
    { id = "gear",        label = "Gear Upgrades",   hex = "ff00ff00" },
    { id = "trinkets",    label = "Trinket Rankings", hex = "ffff8000" },
    { id = "raid",        label = "Raid Tools",       hex = "ff00aaff" },
    { id = "progression", label = "Progression",      hex = "ffffff00" },
    { id = "loot",        label = "Loot Browser",     hex = "ff00ccff" },
    { id = "consumables", label = "Consumables",      hex = "ffff00ff" },
}

local NAV_COLS = 3
local NAV_GAP = 10
local NAV_W = math.floor((CW - (NAV_COLS - 1) * NAV_GAP) / NAV_COLS)
local NAV_H = 36
local NAV_ROW_GAP = 8
local NAV_Y = SUMMARY_Y - SUMMARY_H - 12

for i, def in ipairs(NAV_DEFS) do
    local col = (i - 1) % NAV_COLS
    local row = math.floor((i - 1) / NAV_COLS)
    local x = PAD + col * (NAV_W + NAV_GAP)
    local btn = CreateFrame("Button", nil, homePage, "BackdropTemplate")
    btn:SetSize(NAV_W, NAV_H)
    btn:SetPoint("TOPLEFT", homePage, "TOPLEFT", x, NAV_Y - row * (NAV_H + NAV_ROW_GAP))
    btn:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 10,
        insets   = { left = 2, right = 2, top = 2, bottom = 2 },
    })
    btn:SetBackdropColor(0.10, 0.10, 0.10, 1)
    btn:SetBackdropBorderColor(0.25, 0.25, 0.25, 0.5)
    ns.SmoothFrame(btn)
    ns.AddGlowHighlight(btn, 0.08)

    local label = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    label:SetPoint("LEFT", 12, 0)
    label:SetText("|c" .. def.hex .. def.label .. "|r")
    ns.ApplyTextShadow(label)

    local arrow = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    arrow:SetPoint("RIGHT", -10, 0)
    arrow:SetTextColor(unpack(ns.COLORS.TEXT_TERTIARY))
    arrow:SetText(">")

    btn:SetScript("OnClick", function()
        ns:ShowAppPage(def.id)
    end)
end

------------------------------------------------------------
-- Dashboard refresh (called when home page is shown)
------------------------------------------------------------
RefreshDashboard = function()
    -- Character bar
    local specIndex = GetSpecialization and GetSpecialization()
    local specName = ""
    if specIndex then
        local _, name = GetSpecializationInfo(specIndex)
        specName = name or ""
    end
    local className = UnitClass("player") or ""
    local _, classFile = UnitClass("player")
    local classColor = classFile and RAID_CLASS_COLORS[classFile]
    if classColor then
        charSpecFs:SetText(classColor:WrapTextInColorCode(specName .. " " .. className))
    else
        charSpecFs:SetText(specName .. " " .. className)
    end

    local _, equipped = GetAverageItemLevel()
    charIlvlFs:SetText(string.format("|cffffffffilvl %d|r", math.floor(equipped or 0)))

    UpdateProfileDisplay()

    -- Crest bars
    local crestData = ns:GetCrestInfo()
    for _, ct in ipairs(CREST_TRACKS) do
        local cf = crestFrames[ct.track]
        if cf then
            local count = 0
            local totalEarned = 0
            local seasonCap = 0
            local earnedThisWeek = 0
            local weeklyCap = 0

            for _, cd in ipairs(crestData) do
                if cd.track == ct.track then
                    count = cd.quantity
                    totalEarned = cd.totalEarned or 0
                    seasonCap = cd.seasonCap or 0
                    earnedThisWeek = cd.earnedThisWeek or 0
                    weeklyCap = cd.weeklyMax or 0
                    if cd.icon then cf.iconTex:SetTexture(cd.icon) end
                    break
                end
            end

            cf.countFs:SetText("|cffffffff" .. count .. "|r")

            -- Show season earned/cap + weekly remaining
            local parts = {}
            if seasonCap > 0 then
                local seasonRemaining = math.max(seasonCap - totalEarned, 0)
                if seasonRemaining <= 0 then
                    table.insert(parts, "|cff555555" .. totalEarned .. "/" .. seasonCap .. "|r")
                else
                    table.insert(parts, "|cff888888" .. totalEarned .. "/" .. seasonCap .. "|r")
                end
            end
            if weeklyCap > 0 then
                local weekRemaining = math.max(weeklyCap - earnedThisWeek, 0)
                if weekRemaining > 0 then
                    table.insert(parts, "|cff666666+" .. weekRemaining .. "w|r")
                end
            end
            cf.upgFs:SetText(table.concat(parts, " "))
        end
    end

    -- Upgrade summary
    local freeCount = 0
    local upgradeableCount = 0
    local maxedCount = 0
    local bestUpgrade = nil
    local bestUpgradeSlot = nil

    for _, slotInfo in ipairs(ns.SLOT_IDS) do
        local canUp, upInfo = ns:CanUpgradeItem(slotInfo.slot)
        if upInfo then
            if upInfo.currUpgrade >= upInfo.maxUpgrade then
                maxedCount = maxedCount + 1
            else
                -- Check free upgrade via watermark
                local watermark = ns:GetFreeUpgradeIlvl(slotInfo.slot)
                if watermark > upInfo.currIlvl then
                    freeCount = freeCount + 1
                end
                if canUp then
                    upgradeableCount = upgradeableCount + 1
                    -- Track the highest-priority upgrade
                    if ns.GetRecommendation then
                        local ok, rec, reason = pcall(ns.GetRecommendation, ns, slotInfo.slot)
                        if ok and rec and (not bestUpgrade or (rec.priority or 99) < (bestUpgrade.priority or 99)) then
                            bestUpgrade = rec
                            bestUpgradeSlot = slotInfo
                            bestUpgrade.reason = reason
                        end
                    end
                end
            end
        end
    end

    local line = 1
    if freeCount > 0 then
        summaryLines[line]:SetText("|cff00ffff" .. freeCount .. " free upgrade" ..
            (freeCount > 1 and "s" or "") .. "|r available (high-water mark)")
        line = line + 1
    end

    summaryLines[line]:SetText("|cffffffff" .. upgradeableCount .. "|r item" ..
        (upgradeableCount ~= 1 and "s" or "") .. " can be upgraded  |cff666666\194\183  " ..
        maxedCount .. " maxed|r")
    line = line + 1

    if bestUpgrade and bestUpgradeSlot then
        summaryLines[line]:SetText("")
        line = line + 1
        summaryLines[line]:SetText("|cffaaaaaaTop recommendation:|r")
        line = line + 1
        local recColor = bestUpgrade.color or "ffffffff"
        summaryLines[line]:SetText("|c" .. recColor .. bestUpgradeSlot.name .. "|r  " ..
            (bestUpgrade.reason or ""))
        line = line + 1
    end

    -- Clear remaining lines
    for i = line, #summaryLines do
        summaryLines[i]:SetText("")
    end

    -- ── Farm Guide ──
    -- Figure out which crest type the player needs most
    local crestNeed = {}  -- track -> { needed, have, short, cost }
    for _, slotInfo in ipairs(ns.SLOT_IDS) do
        local canUp, upInfo = ns:CanUpgradeItem(slotInfo.slot)
        if canUp and upInfo then
            local track = upInfo.track
            local crestTrack = ns.TRACK_CREST[track]
            if crestTrack then
                local cost = ns:GetCrestCost(crestTrack)
                local ranksLeft = upInfo.maxUpgrade - upInfo.currUpgrade
                local totalCost = ranksLeft * cost
                if not crestNeed[crestTrack] then
                    crestNeed[crestTrack] = { needed = 0, items = 0 }
                end
                crestNeed[crestTrack].needed = crestNeed[crestTrack].needed + totalCost
                crestNeed[crestTrack].items = crestNeed[crestTrack].items + 1
            end
        end
    end

    -- Find the most-needed crest type (highest shortage)
    local farmLine = 1
    local sortedNeeds = {}
    for track, info in pairs(crestNeed) do
        local have = ns:GetCrestCountByTrack(track)
        local short = math.max(info.needed - have, 0)
        if short > 0 then
            table.insert(sortedNeeds, { track = track, needed = info.needed, have = have, short = short, items = info.items })
        end
    end
    table.sort(sortedNeeds, function(a, b)
        -- Prioritize by crest value (Myth > Hero > Champion etc.)
        local va = ns.CREST_VALUE[a.track] or 0
        local vb = ns.CREST_VALUE[b.track] or 0
        if va ~= vb then return va > vb end
        return a.short > b.short
    end)

    -- Source mapping for each crest type
    local FARM_SOURCES = {
        Myth       = { "|cffff0000M+ 9-12|r", "|cffff0000Mythic Raid|r" },
        Hero       = { "|cffff8000M+ 4-8|r", "|cffff8000Heroic Raid|r", "|cffff8000Delves T10-11|r" },
        Champion   = { "|cffa335eeM0, M+ 2-3|r", "|cffa335eeNormal Raid|r", "|cffa335eeDelves T7-9|r", "|cffa335eePrey NM|r" },
        Veteran    = { "|cff0070ddLFR|r", "|cff0070ddDelves T5-6|r", "|cff0070ddPrey Hard|r" },
        Adventurer = { "|cff1eff00Heroic Dungeon|r", "|cff1eff00Delves T4|r", "|cff1eff00Prey Normal|r" },
    }

    if #sortedNeeds > 0 then
        for _, need in ipairs(sortedNeeds) do
            if farmLine > 6 then break end
            local ct = CREST_TRACKS[1]  -- find color
            for _, c in ipairs(CREST_TRACKS) do
                if c.track == need.track then ct = c break end
            end
            farmLines[farmLine]:SetText("|c" .. ct.hex .. "Need " .. need.short .. " " .. need.track .. "|r  |cff666666(" .. need.items .. " items)|r")
            farmLine = farmLine + 1

            local sources = FARM_SOURCES[need.track]
            if sources and farmLine <= 6 then
                farmLines[farmLine]:SetText("  |cff555555>|r " .. table.concat(sources, "  |cff555555>|r "))
                farmLine = farmLine + 1
            end
        end
    else
        farmLines[farmLine]:SetText("|cff00ff00All crests covered!|r")
        farmLine = farmLine + 1
        farmLines[farmLine]:SetText("|cff666666No crest shortages for current gear|r")
        farmLine = farmLine + 1
    end

    for i = farmLine, #farmLines do
        farmLines[i]:SetText("")
    end
end

-- Hook: refresh dashboard when home page is shown
homePage:SetScript("OnShow", RefreshDashboard)
ns._refreshDashboard = RefreshDashboard

-- Default to home
ns:ShowAppPage("home")

------------------------------------------------------------
-- Public API
------------------------------------------------------------
function ns:ToggleApp()
    if app:IsShown() then
        app:Hide()
    else
        ns:ShowAppPage("home")
        app:Show()
    end
end

function ns:ToggleDashboard()
    ns:ToggleApp()
end
