EbonholdHub.BuildList = {}

local UI = EbonholdHub.UI
local C  = UI.Colors
local FLAT = "Interface\\Buttons\\WHITE8X8"
local ST = EbonholdHub.SiteTheme
local SW = EbonholdHub.SiteWidgets
local SC = ST and ST.C
local SL = ST and ST.Layout

local ROW_SINGLE   = 56
local ROW_DOUBLE   = 74
local SITE_ROW_H   = 56
local CARD_MARGIN  = 3
local SITE_CARD_GAP = 4
local CLASS_COORDS = CLASS_ICON_TCOORDS
local CLASS_TEXTURE = "Interface\\TargetingFrame\\UI-Classes-Circles"

local CLASS_COLORS = (ST and ST.CLASS_COLORS) or {
    WARRIOR     = { 0.78, 0.61, 0.43 },
    PALADIN     = { 0.96, 0.55, 0.73 },
    HUNTER      = { 0.67, 0.83, 0.45 },
    ROGUE       = { 1.0,  0.96, 0.41 },
    PRIEST      = { 1.0,  1.0,  1.0  },
    DEATHKNIGHT = { 0.77, 0.12, 0.23 },
    SHAMAN      = { 0.0,  0.44, 0.87 },
    MAGE        = { 0.41, 0.8,  0.94 },
    WARLOCK     = { 0.58, 0.51, 0.79 },
    DRUID       = { 1.0,  0.49, 0.04 },
}

local mounts = {}
local countLabel
local titleMeasureFont
local activeNavId = "myBuild"
local TITLE_MAX_W = 154

local LOCKED_ICON_SIZE = 20
local LOCKED_X_START = 30
local LOCKED_STEP    = 24
local SITE_CLASS_ICON = 28
local SITE_LOCKED_X   = 8 + SITE_CLASS_ICON + 8

local function SetClassIcon(tex, classToken)
    local coords = classToken and CLASS_COORDS[classToken]
    if coords then
        tex:SetTexture(CLASS_TEXTURE)
        tex:SetTexCoord(coords[1], coords[2], coords[3], coords[4])
    else
        tex:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
        tex:SetTexCoord(0, 1, 0, 1)
    end
end

local function NeedsTwoLines(text, maxW, useSiteFont)
    if not text or text == "" or not titleMeasureFont then return false end
    titleMeasureFont:SetText(text)
    if useSiteFont and SW then
        SW.SetFont(titleMeasureFont, 12, "medium")
    else
        UI.SetFont(titleMeasureFont, UI.FONT_SIZE_NORMAL, C.text)
    end
    local w = titleMeasureFont:GetStringWidth() or 0
    return w > (maxW or TITLE_MAX_W)
end

local function TruncateToWidth(text, maxW, useSiteFont)
    text = text or "Untitled"
    if text == "" then return "Untitled" end
    if not titleMeasureFont then return text end

    titleMeasureFont:SetText(text)
    if useSiteFont and SW then
        SW.SetFont(titleMeasureFont, 12, "medium")
    else
        UI.SetFont(titleMeasureFont, UI.FONT_SIZE_NORMAL, C.text)
    end
    if (titleMeasureFont:GetStringWidth() or 0) <= maxW then
        return text
    end

    local ellipsis = "..."
    titleMeasureFont:SetText(ellipsis)
    local ellW = titleMeasureFont:GetStringWidth() or 0
    local avail = maxW - ellW
    if avail <= 0 then return ellipsis end

    local lo, hi = 1, #text
    while lo < hi do
        local mid = math.ceil((lo + hi) / 2)
        titleMeasureFont:SetText(text:sub(1, mid))
        if (titleMeasureFont:GetStringWidth() or 0) <= avail then
            lo = mid
        else
            hi = mid - 1
        end
    end
    return text:sub(1, lo) .. ellipsis
end

local function WireNavigate(btn, build)
    btn:SetScript("OnClick", function()
        EbonholdHub.Build.SetActive(build.id)
        EbonholdHub.BuildList.SetActiveNav("myBuild")
        EbonholdHub.ViewRouter.Show("buildOverview", { build = build })
    end)
end

local function CreateIconButton(parent, size)
    local btn = CreateFrame("Button", nil, parent)
    btn:SetWidth(size)
    btn:SetHeight(size)
    local icon = btn:CreateTexture(nil, "ARTWORK")
    icon:SetAllPoints(btn)
    icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
    btn._icon = icon
    return btn
end

local function CreateSiteOverviewRow(parent)
    local row = CreateFrame("Button", nil, parent)
    row._siteStyle = true

    local stripe = row:CreateTexture(nil, "BACKGROUND")
    stripe:SetTexture(ST.FLAT)
    stripe:SetWidth(4)
    stripe:SetPoint("TOPLEFT", 0, 0)
    stripe:SetPoint("BOTTOMLEFT", 0, 0)
    row._stripe = stripe

    local card = CreateFrame("Frame", nil, row)
    card:SetPoint("TOPLEFT", stripe, "TOPRIGHT", 0, 0)
    card:SetPoint("BOTTOMRIGHT", 0, 0)
    row._card = card
    row._bg = SW.Fill(card, SC.statCellBg)
    row._border = SW.ThinBorder(card, SC.border, 1)

    local iconHolder = SW.CreateClassIconHolder(card, "PRIEST", 1, 28)
    iconHolder:SetPoint("TOPLEFT", 8, -8)
    row._iconHolder = iconHolder

    local titleLabel = card:CreateFontString(nil, "OVERLAY")
    SW.SetFont(titleLabel, 12, "medium")
    titleLabel:SetPoint("TOPLEFT", iconHolder, "TOPRIGHT", 8, -1)
    titleLabel:SetJustifyH("LEFT")
    titleLabel:SetWordWrap(false)
    titleLabel:SetHeight(18)
    row._titleLabel = titleLabel

    local lockedN = EbonholdHub.Build.GetLockedSlotCount()
    row._lockedBtns = {}
    for i = 1, lockedN do
        local btn = CreateIconButton(card, LOCKED_ICON_SIZE)
        btn:SetFrameLevel(card:GetFrameLevel() + 5)
        btn:Hide()
        row._lockedBtns[i] = btn
    end

    row:SetScript("OnEnter", function(self)
        if not self._isActive then
            SW.SetBorderColor(self._border, SC.borderHover)
        end
    end)
    row:SetScript("OnLeave", function(self)
        if not self._isActive then
            SW.SetBorderColor(self._border, SC.border)
        end
    end)

    return row
end

local function StyleSiteOverviewRow(row, build, isActive)
    local classToken = build.class or "PRIEST"
    local cc = CLASS_COLORS[classToken] or SC.text

    row._isActive = isActive
    if isActive then
        ST.ApplyClassStripe(row._stripe, cc)
        row._bg:SetVertexColor(unpack(ST.BlendRgb(cc, SC.sidebarBg, 0.16)))
        SW.SetBorderColor(row._border, SC.accentBorder55)
        row._titleLabel:SetTextColor(cc[1], cc[2], cc[3], 1)
    else
        row._stripe:SetTexture(ST.FLAT)
        row._stripe:SetVertexColor(cc[1], cc[2], cc[3], 0.45)
        row._bg:SetVertexColor(unpack(SC.statCellBg))
        SW.SetBorderColor(row._border, SC.border)
        row._titleLabel:SetTextColor(unpack(SC.textDim))
    end

    if row._iconHolder then
        if row._iconHolder._classIcon then
            SW.SetClassIcon(row._iconHolder._classIcon, classToken)
        end
        if row._iconHolder._specTex then
            local _, specIconPath = ST.SpecDisplay(classToken, build.spec)
            if specIconPath then
                row._iconHolder._specTex:SetTexture(specIconPath)
                row._iconHolder._specTex:GetParent():Show()
            end
        end
    end
end

local function PopulateSiteOverviewRow(mount, row, build, activeId, yOffset)
    local isActive = (build.id == activeId)
    local titleMaxW = mount.titleMaxW or 190
    local lockeds = build.lockedEchoes

    row:SetHeight(SITE_ROW_H)
    row:ClearAllPoints()
    row:SetPoint("LEFT", mount.scrollChild, "LEFT", 0, 0)
    row:SetPoint("RIGHT", mount.scrollChild, "RIGHT", 0, 0)
    row:SetPoint("TOP", mount.scrollChild, "TOP", 0, -yOffset)

    StyleSiteOverviewRow(row, build, isActive)

    row._titleLabel:SetWidth(titleMaxW)
    row._titleLabel:SetText(TruncateToWidth(build.title, titleMaxW, true))

    local lockedCount = row._lockedBtns and #row._lockedBtns or EbonholdHub.Build.GetLockedSlotCount()
    for i = 1, lockedCount do
        local btn = row._lockedBtns[i]
        local spellId = lockeds and lockeds[i]
        btn:ClearAllPoints()
        btn:SetPoint("BOTTOMLEFT", row._card, "BOTTOMLEFT", SITE_LOCKED_X + (i - 1) * LOCKED_STEP, 6)
        if spellId then
            btn._icon:SetTexture(select(3, GetSpellInfo(spellId)))
            btn._spellId = spellId
            btn:Show()
            btn:SetScript("OnEnter", function(self)
                local spellName = GetSpellInfo(self._spellId)
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:ClearLines()
                if spellName then GameTooltip:AddLine(spellName, 1, 0.82, 0) end
                GameTooltip:Show()
            end)
            btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
            WireNavigate(btn, build)
        else
            btn:Hide()
        end
    end

    WireNavigate(row, build)
    row:Show()
end

local function CreateRow(parent)
    local row = CreateFrame("Button", nil, parent)
    row:SetPoint("LEFT",  parent, "LEFT",  0, 0)
    row:SetPoint("RIGHT", parent, "RIGHT", 0, 0)
    row:SetHeight(ROW_SINGLE)
    local stripe = row:CreateTexture(nil, "BACKGROUND")
    stripe:SetTexture(FLAT)
    stripe:SetPoint("TOPLEFT",      row, "TOPLEFT",      2, -2)
    stripe:SetPoint("BOTTOMLEFT",   row, "BOTTOMLEFT",   2,  2)
    stripe:SetWidth(4)
    row._stripe = stripe
    local bg = row:CreateTexture(nil, "BACKGROUND")
    bg:SetTexture(FLAT)
    bg:SetPoint("TOPLEFT",      row, "TOPLEFT",      6, -2)
    bg:SetPoint("BOTTOMRIGHT",  row, "BOTTOMRIGHT", -2,  2)
    row._bg = bg
    local selected = row:CreateTexture(nil, "BACKGROUND")
    selected:SetAllPoints(row)
    selected:SetTexture(FLAT)
    selected:SetVertexColor(unpack(C.accentDim))
    selected:Hide()
    row._selected = selected
    local hl = row:CreateTexture(nil, "HIGHLIGHT")
    hl:SetPoint("TOPLEFT",      row, "TOPLEFT",      6, -2)
    hl:SetPoint("BOTTOMRIGHT",  row, "BOTTOMRIGHT", -2,  2)
    hl:SetTexture(FLAT)
    hl:SetVertexColor(1, 1, 1, 0.06)
    local classBtn = CreateIconButton(row, 22)
    classBtn:SetPoint("TOPLEFT", row, "TOPLEFT", 10, -6)
    row._classBtn = classBtn
    local titleLabel = row:CreateFontString(nil, "OVERLAY")
    UI.SetFont(titleLabel, UI.FONT_SIZE_NORMAL, C.text)
    titleLabel:SetPoint("TOPLEFT",  classBtn, "TOPRIGHT",  6, -2)
    titleLabel:SetPoint("RIGHT",    row,      "RIGHT",    -8, 0)
    titleLabel:SetJustifyH("LEFT")
    titleLabel:SetHeight(0)
    row._titleLabel = titleLabel
    local specBtn = CreateIconButton(row, 14)
    specBtn:SetPoint("TOPLEFT", classBtn, "BOTTOMLEFT", 0, -2)
    row._specBtn = specBtn
    local lockedN = EbonholdHub.Build.GetLockedSlotCount()
    row._lockedBtns = {}
    for i = 1, lockedN do
        local btn = CreateIconButton(row, LOCKED_ICON_SIZE)
        btn:Hide()
        row._lockedBtns[i] = btn
    end
    return row
end

local function PopulateRow(mount, row, build, activeId, yOffset)
    local isActive = (build.id == activeId)
    local classToken = build.class
    local cc = CLASS_COLORS[classToken] or { 0.5, 0.5, 0.5 }
    local titleMaxW = mount.titleMaxW or TITLE_MAX_W

    local twoLines = NeedsTwoLines(build.title, titleMaxW)
    local rowHeight = twoLines and ROW_DOUBLE or ROW_SINGLE
    row:SetHeight(rowHeight)

    row:ClearAllPoints()
    row:SetPoint("LEFT",  mount.scrollChild, "LEFT",  0, 0)
    row:SetPoint("RIGHT", mount.scrollChild, "RIGHT", 0, 0)
    row:SetPoint("TOP",   mount.scrollChild, "TOP",   0, -yOffset)
    if isActive then
        row._stripe:SetWidth(6)
        row._stripe:SetVertexColor(cc[1], cc[2], cc[3], 1.0)
        row._bg:SetVertexColor(cc[1], cc[2], cc[3], 0.18)
        row._selected:Show()
    else
        row._stripe:SetWidth(4)
        row._stripe:SetVertexColor(cc[1], cc[2], cc[3], 0.6)
        row._bg:SetVertexColor(unpack(C.elementBg))
        row._selected:Hide()
    end
    SetClassIcon(row._classBtn._icon, classToken)
    row._classBtn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText(classToken or "Unknown", 1, 1, 1)
        GameTooltip:Show()
    end)
    row._classBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
    WireNavigate(row._classBtn, build)
    local title = build.title or "Untitled"
    row._titleLabel:SetText(title)
    row._titleLabel:SetTextColor(cc[1], cc[2], cc[3], 1)
    if twoLines then
        row._titleLabel:SetWidth(titleMaxW)
        row._titleLabel:SetHeight(28)
    else
        row._titleLabel:SetWidth(0)
        row._titleLabel:SetHeight(0)
    end
    local specs = EbonholdHub.SpecData and EbonholdHub.SpecData[classToken]
    local specEntry = specs and specs[build.spec or 1]
    if specEntry then
        row._specBtn._icon:SetTexture(specEntry.icon)
        row._specBtn:Show()
        row._specBtn:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:SetText(specEntry.name, 1, 1, 1)
            GameTooltip:Show()
        end)
        row._specBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
        WireNavigate(row._specBtn, build)
    else
        row._specBtn:Hide()
    end
    local lockeds = build.lockedEchoes
    local lockedCount = row._lockedBtns and #row._lockedBtns or EbonholdHub.Build.GetLockedSlotCount()
    for i = 1, lockedCount do
        local btn = row._lockedBtns[i]
        local spellId = lockeds and lockeds[i]
        btn:ClearAllPoints()
        btn:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", LOCKED_X_START + (i - 1) * LOCKED_STEP, 4)
        if spellId then
            btn._icon:SetTexture(select(3, GetSpellInfo(spellId)))
            btn._spellId = spellId
            btn:Show()
            btn:SetScript("OnEnter", function(self)
                local spellName = GetSpellInfo(self._spellId)
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:ClearLines()
                if spellName then GameTooltip:AddLine(spellName, 1, 0.82, 0) end
                GameTooltip:Show()
            end)
            btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
            WireNavigate(btn, build)
        else
            btn:Hide()
        end
    end

    WireNavigate(row, build)
    row:Show()
end

local function RenderMount(mount)
    if not mount or not mount.scrollChild then return end
    local builds   = EbonholdHub.Build.List()
    local activeId = EbonholdHubCharDB.activeBuildId
    if mount.isMain and countLabel then
        countLabel:SetText(tostring(#builds))
    end
    mount.rowPool = mount.rowPool or {}
    local gap = mount.isOverview and SITE_CARD_GAP or CARD_MARGIN
    local yOffset = 0
    for i = 1, #builds do
        if not mount.rowPool[i] then
            if mount.isOverview then
                mount.rowPool[i] = CreateSiteOverviewRow(mount.scrollChild)
            else
                mount.rowPool[i] = CreateRow(mount.scrollChild)
            end
        end
        if mount.isOverview then
            PopulateSiteOverviewRow(mount, mount.rowPool[i], builds[i], activeId, yOffset)
            yOffset = yOffset + SITE_ROW_H + gap
        else
            PopulateRow(mount, mount.rowPool[i], builds[i], activeId, yOffset)
            yOffset = yOffset + (NeedsTwoLines(builds[i].title, mount.titleMaxW) and ROW_DOUBLE or ROW_SINGLE) + gap
        end
    end
    for i = #builds + 1, #mount.rowPool do mount.rowPool[i]:Hide() end

    mount.scrollChild:SetHeight(math.max(1, yOffset))
    if mount.scrollHolder and mount.scrollHolder.UpdateThumb then
        mount.scrollHolder.UpdateThumb()
    end
end

local function RefreshAll()
    if mounts.main then RenderMount(mounts.main) end
    if mounts.overview then RenderMount(mounts.overview) end
end

EbonholdHub.BuildList.Refresh = RefreshAll

function EbonholdHub.BuildList.SetActiveNav(navId)
    activeNavId = navId or "myBuild"
    if EbonholdHub.MainNav and EbonholdHub.MainNav.SetActiveId then
        if navId == "catalog" or navId == "echoMap" then
            EbonholdHub.MainNav.SetActiveId(navId)
        else
            EbonholdHub.MainNav.SetActiveId("myBuild")
        end
    end
end

local function CreateBuildFromCharacter()
    local build, summary = EbonholdHub.Build.CreateFromCharacter()
    if not build then
        EbonholdHub.Toast.Show(summary or "Could not copy character.")
        return
    end

    local parts = {}
    if summary.echoes and summary.echoes > 0 then
        parts[#parts + 1] = summary.echoes .. " echo" .. (summary.echoes == 1 and "" or "es") .. " in tiers"
    end
    if summary.locked and summary.locked > 0 then
        parts[#parts + 1] = summary.locked .. " locked slot" .. (summary.locked == 1 and "" or "s")
    end
    if summary.affixes and summary.affixes > 0 then
        parts[#parts + 1] = summary.affixes .. " affix" .. (summary.affixes == 1 and "" or "es")
    end
    if summary.gear and summary.gear > 0 then
        parts[#parts + 1] = summary.gear .. " gear slot" .. (summary.gear == 1 and "" or "s")
    end
    if summary.talents and summary.talents > 0 then
        parts[#parts + 1] = summary.talents .. " talent point" .. (summary.talents == 1 and "" or "s")
    end

    local message = "New build created."
    if #parts > 0 then
        message = message .. " Copied " .. table.concat(parts, ", ") .. "."
    end
    EbonholdHub.Toast.Show(message)
    EbonholdHub.ViewRouter.Show("buildOverview", { build = build })
end

EbonholdHub.BuildList.CreateFromCharacter = CreateBuildFromCharacter

local function MountSiteBuildList(parent, opts, mountKey)
    opts = opts or {}
    local mount = {
        parent = parent,
        titleMaxW = opts.titleMaxW or 190,
        rowPool = {},
        isOverview = true,
    }

    local scroll, child
    if SW and SW.CreatePlainScroll then
        scroll, child = SW.CreatePlainScroll(parent)
        scroll:SetAllPoints(parent)
    else
        local holder = UI.CreateScrollFrame(parent)
        holder:SetAllPoints(parent)
        scroll = holder
        child = holder.scrollChild
    end

    mount.scrollHolder = scroll
    mount.scrollChild = child
    if not titleMeasureFont then
        titleMeasureFont = parent:CreateFontString(nil, "OVERLAY")
        UI.SetFont(titleMeasureFont, UI.FONT_SIZE_NORMAL, C.text)
        titleMeasureFont:Hide()
    end
    local function SyncWidth()
        local w = parent:GetWidth() or 260
        child:SetWidth(w)
        mount.titleMaxW = math.max(120, w - 52)
        RenderMount(mount)
    end
    child:SetWidth(parent:GetWidth() or 260)

    parent:SetScript("OnSizeChanged", SyncWidth)

    mounts[mountKey] = mount
    RenderMount(mount)
    return mount
end

function EbonholdHub.BuildList.MountInOverview(parent, opts)
    return MountSiteBuildList(parent, opts, "overview")
end

function EbonholdHub.BuildList.Init(parent)
    local mount = {
        parent = parent,
        titleMaxW = TITLE_MAX_W,
        rowPool = {},
        isMain = true,
    }

    local header = CreateFrame("Frame", nil, parent)
    header:SetPoint("TOPLEFT",  parent, "TOPLEFT",  0, 0)
    header:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, 0)
    header:SetHeight(28)

    local label = UI.CreateLabel(header, "Select Build", UI.FONT_SIZE_NORMAL, C.text)
    label:SetPoint("LEFT", header, "LEFT", 0, 0)

    countLabel = UI.CreateLabel(header, "0", UI.FONT_SIZE_SMALL, C.textDim)
    countLabel:SetPoint("RIGHT", header, "RIGHT", 0, 0)

    local sep = header:CreateTexture(nil, "ARTWORK")
    sep:SetTexture(FLAT)
    sep:SetVertexColor(unpack(C.border))
    sep:SetPoint("BOTTOMLEFT", 0, 0)
    sep:SetPoint("BOTTOMRIGHT", 0, 0)
    sep:SetHeight(1)

    local holder = UI.CreateScrollFrame(parent)
    holder:SetPoint("TOPLEFT", header, "BOTTOMLEFT", 0, -6)
    holder:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 0, 0)

    mount.scrollHolder = holder
    mount.scrollChild = holder.scrollChild
    mounts.main = mount

    if not titleMeasureFont then
        titleMeasureFont = parent:CreateFontString(nil, "OVERLAY")
        UI.SetFont(titleMeasureFont, UI.FONT_SIZE_NORMAL, C.text)
        titleMeasureFont:Hide()
    end

    mount.scrollChild:SetWidth(parent:GetWidth())
    parent:SetScript("OnSizeChanged", function()
        mount.scrollChild:SetWidth(parent:GetWidth())
        RenderMount(mount)
    end)

    RenderMount(mount)
    EbonholdHub.BuildList.SetActiveNav("myBuild")

    if EbonholdHub.Build and EbonholdHub.Build.OnActiveChanged then
        EbonholdHub.Build.OnActiveChanged(RefreshAll)
    end
end
