local ADDON_NAME, ns = ...

ns = ns or {}

local ui = {}
local ROW_HEIGHT = 40
local ROW_GAP = 1
local SORT_MODES = { "name_asc", "name_desc", "date_desc", "date_asc" }
local SORT_LABELS = {
    name_asc = "Name A-Z",
    name_desc = "Name Z-A",
    date_desc = "Newest First",
    date_asc = "Oldest First",
}

local ACCENT_COLOR = { r = 0.486, g = 0.31, b = 0.878 }

local state = {
    selectedIDs = {},
    searchText = "",
    renameConfigID = nil,
}

local function formatTimestamp(ts)
    if not ts or ts == 0 then
        return "|cff555555-|r"
    end
    return date("%b %d, %Y", ts)
end

local function getFilteredLoadouts()
    local sortMode = (ns.db and ns.db.sortMode) or "name_asc"
    local list = ns.LoadoutData and ns.LoadoutData:GetSortedLoadouts(sortMode) or {}

    if state.searchText and state.searchText ~= "" then
        local filter = state.searchText:lower()
        local filtered = {}
        for _, entry in ipairs(list) do
            if (entry.name or ""):lower():find(filter, 1, true) then
                filtered[#filtered + 1] = entry
            end
        end
        return filtered
    end

    return list
end

local function getSelectedCount()
    local count = 0
    for _ in pairs(state.selectedIDs) do
        count = count + 1
    end
    return count
end

local function clearSelection()
    wipe(state.selectedIDs)
end

local function createNativeButton(parent, text, width)
    local btn = CreateFrame("Button", nil, parent, "UIPanelDynamicResizeButtonTemplate")
    btn:SetText(text)
    if width then
        btn:SetWidth(width)
    else
        DynamicResizeButton_Resize(btn)
    end
    return btn
end

local function saveWindowPosition()
    if not ns.db or not ns.db.window or not ui.main then
        return
    end
    local point, _, relPoint, x, y = ui.main:GetPoint()
    if point then
        ns.db.window.point = point
        ns.db.window.relPoint = relPoint
        ns.db.window.x = x
        ns.db.window.y = y
    end
end

local function restoreWindowPosition()
    if not ns.db or not ns.db.window or not ui.main then
        return
    end
    local w = ns.db.window
    if w.point then
        ui.main:ClearAllPoints()
        ui.main:SetPoint(w.point, UIParent, w.relPoint or w.point, w.x or 0, w.y or 0)
    end
end

local function createRow(parent, index)
    local row = CreateFrame("Frame", nil, parent)
    row:SetHeight(ROW_HEIGHT)

    row.highlight = row:CreateTexture(nil, "BACKGROUND")
    row.highlight:SetAllPoints()
    row.highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
    row.highlight:SetBlendMode("ADD")
    row.highlight:SetAlpha(0)

    row.selectedBg = row:CreateTexture(nil, "BACKGROUND", nil, -1)
    row.selectedBg:SetAllPoints()
    row.selectedBg:SetColorTexture(1, 0.82, 0, 0.15)
    row.selectedBg:Hide()

    row.selectedAccent = row:CreateTexture(nil, "BACKGROUND", nil, 0)
    row.selectedAccent:SetPoint("TOPLEFT", row, "TOPLEFT", 0, 0)
    row.selectedAccent:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 0, 0)
    row.selectedAccent:SetWidth(3)
    row.selectedAccent:SetColorTexture(1, 0.82, 0, 0.8)
    row.selectedAccent:Hide()

    row.activeBg = row:CreateTexture(nil, "BACKGROUND", nil, -2)
    row.activeBg:SetAllPoints()
    row.activeBg:SetColorTexture(0.2, 0.6, 0.2, 0.15)
    row.activeBg:Hide()

    row.activeAccent = row:CreateTexture(nil, "BACKGROUND", nil, 0)
    row.activeAccent:SetPoint("TOPLEFT", row, "TOPLEFT", 0, 0)
    row.activeAccent:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 0, 0)
    row.activeAccent:SetWidth(3)
    row.activeAccent:SetColorTexture(0.3, 0.8, 0.3, 0.9)
    row.activeAccent:Hide()

    row.stripe = row:CreateTexture(nil, "BACKGROUND", nil, -3)
    row.stripe:SetAllPoints()
    row.stripe:SetColorTexture(1, 1, 1, 0.03)
    row.stripe:SetShown(index % 2 == 0)

    row.checkbox = CreateFrame("CheckButton", nil, row, "SettingsCheckboxTemplate")
    row.checkbox:SetPoint("LEFT", row, "LEFT", 4, 0)
    row.checkbox:SetText("")
    row.checkbox:SetHitRectInsets(0, 0, 0, 0)
    row.checkbox:SetScript("OnClick", function(self)
        local configID = row.configID
        if not configID then
            return
        end
        if self:GetChecked() then
            state.selectedIDs[configID] = true
        else
            state.selectedIDs[configID] = nil
        end
        if ns.RefreshLoadoutPanel then
            ns.RefreshLoadoutPanel()
        end
    end)

    row.activeIcon = row:CreateTexture(nil, "OVERLAY")
    row.activeIcon:SetSize(8, 8)
    row.activeIcon:SetPoint("LEFT", row.checkbox, "RIGHT", 2, 0)
    row.activeIcon:SetColorTexture(0.3, 0.8, 0.3, 1)
    row.activeIcon:Hide()

    row.menuBtn = CreateFrame("Button", nil, row)
    row.menuBtn:SetSize(20, 20)
    row.menuBtn:SetPoint("RIGHT", row, "RIGHT", -4, 0)
    row.menuBtn.icon = row.menuBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    row.menuBtn.icon:SetPoint("CENTER", 0, 1)
    row.menuBtn.icon:SetText("|cffFFD100...|r")
    row.menuBtn:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight", "ADD")
    row.menuBtn:GetHighlightTexture():SetAlpha(0.3)
    row.menuBtn:SetScript("OnEnter", function(self)
        row.highlight:SetAlpha(0.15)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText("More Actions", 1, 1, 1)
        GameTooltip:Show()
    end)
    row.menuBtn:SetScript("OnLeave", function()
        row.highlight:SetAlpha(0)
        GameTooltip:Hide()
    end)
    row.menuBtn:SetScript("OnClick", function(self)
        if not row.configID then return end
        MenuUtil.CreateContextMenu(self, function(owner, rootDescription)
            rootDescription:CreateTitle(row.entry and row.entry.name or "Loadout")
            rootDescription:CreateButton("Export", function()
                local exportString = ns.LoadoutData:GetExportString(row.configID)
                if exportString and exportString ~= "" then
                    StaticPopup_Show("QUICKLOADOUT_EXPORT", nil, nil, exportString)
                else
                    ns:Print("Could not generate export string.")
                end
            end)
            rootDescription:CreateButton("Rename", function()
                state.renameConfigID = row.configID
                if ns.RefreshLoadoutPanel then
                    ns.RefreshLoadoutPanel()
                end
            end)
            local note = ns.LoadoutData:GetNote(row.configID)
            local noteLabel = (note and note ~= "") and "Edit Note" or "Add Note"
            rootDescription:CreateButton(noteLabel, function()
                StaticPopupDialogs.QUICKLOADOUT_EDIT_NOTE.configID = row.configID
                local currentNote = ns.LoadoutData:GetNote(row.configID) or ""
                local name = row.entry and row.entry.name or "Loadout"
                StaticPopupDialogs.QUICKLOADOUT_EDIT_NOTE.text =
                    "Edit note for |cffFFD100" .. name .. "|r:"
                StaticPopup_Show("QUICKLOADOUT_EDIT_NOTE", nil, nil, currentNote)
            end)
            rootDescription:CreateDivider()
            rootDescription:CreateButton("|cffFF4444Delete|r", function()
                local name = row.entry and row.entry.name or "this loadout"
                StaticPopupDialogs.QUICKLOADOUT_CONFIRM_SINGLE_DELETE.configID = row.configID
                StaticPopupDialogs.QUICKLOADOUT_CONFIRM_SINGLE_DELETE.text = "Delete loadout |cffFFFF00" .. name .. "|r?"
                StaticPopup_Show("QUICKLOADOUT_CONFIRM_SINGLE_DELETE")
            end)
        end)
    end)

    row.applyBtn = CreateFrame("Button", nil, row, "UIPanelDynamicResizeButtonTemplate")
    row.applyBtn:SetText("Use")
    row.applyBtn:SetHeight(22)
    DynamicResizeButton_Resize(row.applyBtn)
    row.applyBtn:SetPoint("RIGHT", row.menuBtn, "LEFT", -6, 0)
    row.applyBtn:SetScript("OnClick", function()
        if row.configID then
            ns.LoadoutData:LoadLoadout(row.configID)
        end
    end)
    row.applyBtn:SetScript("OnEnter", function(self)
        row.highlight:SetAlpha(0.15)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText("Apply this loadout", 1, 1, 1)
        GameTooltip:Show()
    end)
    row.applyBtn:SetScript("OnLeave", function()
        row.highlight:SetAlpha(0)
        GameTooltip:Hide()
    end)

    row.dateText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    row.dateText:SetPoint("RIGHT", row.applyBtn, "LEFT", -10, 0)
    row.dateText:SetWidth(85)
    row.dateText:SetJustifyH("CENTER")

    row.nameText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    row.nameText:SetPoint("LEFT", row.activeIcon, "RIGHT", 6, 0)
    row.nameText:SetPoint("RIGHT", row.dateText, "LEFT", -8, 0)
    row.nameText:SetJustifyH("LEFT")
    row.nameText:SetWordWrap(false)

    row.renameBox = CreateFrame("EditBox", nil, row, "InputBoxTemplate")
    row.renameBox:SetSize(180, 22)
    row.renameBox:SetPoint("LEFT", row.activeIcon, "RIGHT", 4, 0)
    row.renameBox:SetAutoFocus(false)
    row.renameBox:SetMaxLetters(64)
    row.renameBox:Hide()

    row.renameBox:SetScript("OnEnterPressed", function(self)
        local newName = self:GetText():match("^%s*(.-)%s*$")
        if newName and newName ~= "" and row.configID then
            ns.LoadoutData:RenameLoadout(row.configID, newName)
            ns.LoadoutData:ScanLoadouts()
        end
        state.renameConfigID = nil
        self:Hide()
        if ns.RefreshLoadoutPanel then
            ns.RefreshLoadoutPanel()
        end
    end)
    row.renameBox:SetScript("OnEscapePressed", function(self)
        state.renameConfigID = nil
        self:Hide()
        if ns.RefreshLoadoutPanel then
            ns.RefreshLoadoutPanel()
        end
    end)

    row:EnableMouse(true)
    row:SetScript("OnEnter", function(self)
        local entry = self.entry
        if not entry then
            return
        end
        self.highlight:SetAlpha(0.15)

        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:AddLine(entry.name or "Loadout", 1, 0.82, 0)
        if entry.isActive then
            GameTooltip:AddLine("|cff4ECC4EActive Loadout|r")
        end
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("Right-Click for more actions", 0.85, 0.85, 0.85)
        GameTooltip:AddLine("Shift-Click to link to chat", 0.85, 0.85, 0.85)
        if entry.createdAt and entry.createdAt > 0 then
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("Created: " .. date("%b %d, %Y", entry.createdAt), 0.6, 0.6, 0.6)
        end
        local note = ns.LoadoutData:GetNote(entry.configID)
        if note and note ~= "" then
            GameTooltip:AddLine("Note: " .. note, 0.7, 0.7, 0.9, true)
        end
        if entry.usesSharedActionBars then
            GameTooltip:AddLine("Uses Shared Action Bars", 0.5, 0.8, 0.5)
        end
        GameTooltip:Show()
    end)
    row:SetScript("OnLeave", function(self)
        self.highlight:SetAlpha(0)
        GameTooltip:Hide()
    end)
    row:SetScript("OnMouseUp", function(self, button)
        if button == "LeftButton" and IsShiftKeyDown() and self.configID then
            local exportString = ns.LoadoutData:GetExportString(self.configID)
            if not exportString or exportString == "" then
                return
            end
            local editBox = ChatEdit_GetActiveWindow and ChatEdit_GetActiveWindow()
            if not editBox then
                ChatFrame_OpenChat(exportString, DEFAULT_CHAT_FRAME)
            else
                editBox:Insert(exportString)
            end
        elseif button == "RightButton" and self.configID then
            MenuUtil.CreateContextMenu(self, function(owner, rootDescription)
                rootDescription:CreateTitle(self.entry and self.entry.name or "Loadout")
                rootDescription:CreateButton("Export", function()
                    local exportString = ns.LoadoutData:GetExportString(self.configID)
                    if exportString and exportString ~= "" then
                        StaticPopup_Show("QUICKLOADOUT_EXPORT", nil, nil, exportString)
                    else
                        ns:Print("Could not generate export string.")
                    end
                end)
                rootDescription:CreateButton("Rename", function()
                    state.renameConfigID = self.configID
                    if ns.RefreshLoadoutPanel then
                        ns.RefreshLoadoutPanel()
                    end
                end)
                local note = ns.LoadoutData:GetNote(self.configID)
                local noteLabel = (note and note ~= "") and "Edit Note" or "Add Note"
                rootDescription:CreateButton(noteLabel, function()
                    StaticPopupDialogs.QUICKLOADOUT_EDIT_NOTE.configID = self.configID
                    local currentNote = ns.LoadoutData:GetNote(self.configID) or ""
                    local name = self.entry and self.entry.name or "Loadout"
                    StaticPopupDialogs.QUICKLOADOUT_EDIT_NOTE.text =
                        "Edit note for |cffFFD100" .. name .. "|r:"
                    StaticPopup_Show("QUICKLOADOUT_EDIT_NOTE", nil, nil, currentNote)
                end)
                rootDescription:CreateDivider()
                rootDescription:CreateButton("|cffFF4444Delete|r", function()
                    local name = self.entry and self.entry.name or "this loadout"
                    StaticPopupDialogs.QUICKLOADOUT_CONFIRM_SINGLE_DELETE.configID = self.configID
                    StaticPopupDialogs.QUICKLOADOUT_CONFIRM_SINGLE_DELETE.text = "Delete loadout |cffFFFF00" .. name .. "|r?"
                    StaticPopup_Show("QUICKLOADOUT_CONFIRM_SINGLE_DELETE")
                end)
            end)
        end
    end)

    return row
end

local function updateRow(row, entry, isRenaming)
    row.configID = entry.configID
    row.entry = entry

    row.activeBg:SetShown(entry.isActive)
    row.activeIcon:Hide()
    row.activeAccent:SetShown(entry.isActive)
    row.selectedBg:SetShown(state.selectedIDs[entry.configID] == true)
    row.selectedAccent:SetShown(state.selectedIDs[entry.configID] == true and not entry.isActive)

    if entry.isActive then
        row.nameText:SetFontObject("GameFontGreen")
        row.applyBtn:Disable()
    else
        row.nameText:SetFontObject("GameFontHighlight")
        row.applyBtn:Enable()
    end

    row.checkbox:SetChecked(state.selectedIDs[entry.configID] == true)

    if isRenaming then
        row.nameText:Hide()
        row.renameBox:Show()
        row.renameBox:SetText(entry.name or "")
        row.renameBox:SetFocus()
        row.renameBox:HighlightText()
    else
        row.nameText:Show()
        if entry.isActive then
            row.nameText:SetText((entry.name or "") .. " |cff4ECC4E(Active)|r")
        else
            row.nameText:SetText(entry.name or "")
        end
        row.renameBox:Hide()
    end

    row.dateText:SetText(formatTimestamp(entry.createdAt))
end

local function createMainWindow()
    if ui.main then
        return
    end

    local frame = CreateFrame("Frame", ADDON_NAME .. "Frame", UIParent, "ButtonFrameTemplate")
    frame:SetSize(580, 560)
    frame:SetPoint("CENTER")
    frame:SetToplevel(true)
    table.insert(UISpecialFrames, frame:GetName())

    ButtonFrameTemplate_HidePortrait(frame)
    ButtonFrameTemplate_HideButtonBar(frame)
    frame.Inset:Hide()

    frame:SetTitle("Quick Loadout Manager")

    local titleAccent = frame:CreateTexture(nil, "OVERLAY")
    titleAccent:SetPoint("TOPLEFT", frame.TitleContainer, "BOTTOMLEFT", 16, 2)
    titleAccent:SetPoint("TOPRIGHT", frame.TitleContainer, "BOTTOMRIGHT", -16, 2)
    titleAccent:SetHeight(2)
    titleAccent:SetColorTexture(ACCENT_COLOR.r, ACCENT_COLOR.g, ACCENT_COLOR.b, 0.6)

    frame:SetMovable(true)
    frame:SetClampedToScreen(true)
    frame:EnableMouse(true)
    frame:RegisterForDrag("LeftButton")
    frame:SetScript("OnDragStart", function(self)
        self:StartMoving()
        self:SetUserPlaced(false)
    end)
    frame:SetScript("OnDragStop", function(self)
        self:StopMovingOrSizing()
        self:SetUserPlaced(false)
        saveWindowPosition()
    end)
    frame:SetScript("OnMouseWheel", function() end)

    frame.CloseButton:SetScript("OnClick", function()
        frame:Hide()
    end)

    -- Toolbar row 1: Search, Sort, Select All
    local toolbar = CreateFrame("Frame", nil, frame)
    toolbar:SetPoint("TOPLEFT", frame, "TOPLEFT", 12, -28)
    toolbar:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -12, -28)
    toolbar:SetHeight(30)

    local searchBox = CreateFrame("EditBox", nil, toolbar, "InputBoxTemplate")
    searchBox:SetSize(150, 22)
    searchBox:SetPoint("LEFT", toolbar, "LEFT", 6, 0)
    searchBox:SetAutoFocus(false)
    searchBox:SetMaxLetters(48)
    ui.searchBox = searchBox

    local searchPlaceholder = searchBox:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    searchPlaceholder:SetPoint("LEFT", searchBox, "LEFT", 6, 0)
    searchPlaceholder:SetText("Search...")

    searchBox:SetScript("OnTextChanged", function(self)
        local text = self:GetText() or ""
        state.searchText = text
        searchPlaceholder:SetShown(text == "")
        if ns.RefreshLoadoutPanel then
            ns.RefreshLoadoutPanel()
        end
    end)
    searchBox:SetScript("OnEscapePressed", function(self)
        self:SetText("")
        self:ClearFocus()
    end)

    local sortDropdown = CreateFrame("DropdownButton", nil, toolbar, "WowStyle1DropdownTemplate")
    sortDropdown:SetWidth(130)
    sortDropdown:SetPoint("LEFT", searchBox, "RIGHT", 6, 0)
    sortDropdown:SetupMenu(function(dropdown, rootDescription)
        for _, mode in ipairs(SORT_MODES) do
            rootDescription:CreateRadio(
                SORT_LABELS[mode],
                function() return (ns.db and ns.db.sortMode or "name_asc") == mode end,
                function()
                    if ns.db then ns.db.sortMode = mode end
                    if ns.RefreshLoadoutPanel then ns.RefreshLoadoutPanel() end
                end,
                mode
            )
        end
    end)
    ui.sortDropdown = sortDropdown

    local selectAllBtn = createNativeButton(toolbar, "Select All", 85)
    selectAllBtn:SetPoint("LEFT", sortDropdown, "RIGHT", 4, 0)
    selectAllBtn:SetScript("OnClick", function()
        local list = getFilteredLoadouts()
        local allSelected = true
        for _, entry in ipairs(list) do
            if not state.selectedIDs[entry.configID] then
                allSelected = false
                break
            end
        end
        if allSelected then
            clearSelection()
        else
            for _, entry in ipairs(list) do
                state.selectedIDs[entry.configID] = true
            end
        end
        if ns.RefreshLoadoutPanel then
            ns.RefreshLoadoutPanel()
        end
    end)
    ui.selectAllBtn = selectAllBtn

    -- Toolbar row 2: Import, Save Current
    local toolbar2 = CreateFrame("Frame", nil, frame)
    toolbar2:SetPoint("TOPLEFT", toolbar, "BOTTOMLEFT", 0, -2)
    toolbar2:SetPoint("TOPRIGHT", toolbar, "BOTTOMRIGHT", 0, -2)
    toolbar2:SetHeight(28)

    local importBtn = createNativeButton(toolbar2, "Import", 80)
    importBtn:SetPoint("LEFT", toolbar2, "LEFT", 6, 0)
    importBtn:SetScript("OnClick", function()
        StaticPopup_Show("QUICKLOADOUT_IMPORT")
    end)
    importBtn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText("Import Loadout", 1, 1, 1)
        GameTooltip:AddLine("Paste a talent import string to create a new loadout", 0.85, 0.85, 0.85, true)
        GameTooltip:Show()
    end)
    importBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)

    local saveBtn = createNativeButton(toolbar2, "Save Current", 100)
    saveBtn:SetPoint("LEFT", importBtn, "RIGHT", 4, 0)
    saveBtn:SetScript("OnClick", function()
        StaticPopup_Show("QUICKLOADOUT_SAVE_CURRENT")
    end)
    saveBtn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText("Save Current Talents", 1, 1, 1)
        GameTooltip:AddLine("Save your current talent selection as a new loadout", 0.85, 0.85, 0.85, true)
        GameTooltip:Show()
    end)
    saveBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)

    -- List area
    local listInset = CreateFrame("Frame", nil, frame, "InsetFrameTemplate")
    listInset:SetPoint("TOPLEFT", toolbar2, "BOTTOMLEFT", -4, -4)
    listInset:SetPoint("TOPRIGHT", toolbar2, "BOTTOMRIGHT", 4, -4)
    listInset:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 8, 50)
    listInset:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -8, 50)

    local columnHeader = CreateFrame("Frame", nil, listInset)
    columnHeader:SetPoint("TOPLEFT", listInset, "TOPLEFT", 4, -4)
    columnHeader:SetPoint("TOPRIGHT", listInset, "TOPRIGHT", -4, -4)
    columnHeader:SetHeight(18)

    local headerBg = columnHeader:CreateTexture(nil, "BACKGROUND")
    headerBg:SetAllPoints()
    headerBg:SetColorTexture(0.1, 0.1, 0.1, 0.4)

    local colActions = columnHeader:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    colActions:SetPoint("RIGHT", columnHeader, "RIGHT", -6, 0)
    colActions:SetText("Actions")

    local colDate = columnHeader:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    colDate:SetPoint("RIGHT", colActions, "LEFT", -60, 0)
    colDate:SetWidth(85)
    colDate:SetJustifyH("CENTER")
    colDate:SetText("Created")

    local colName = columnHeader:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    colName:SetPoint("LEFT", columnHeader, "LEFT", 30, 0)
    colName:SetText("Loadout")

    local headerLine = listInset:CreateTexture(nil, "OVERLAY")
    headerLine:SetPoint("TOPLEFT", columnHeader, "BOTTOMLEFT", 0, -1)
    headerLine:SetPoint("TOPRIGHT", columnHeader, "BOTTOMRIGHT", 0, -1)
    headerLine:SetHeight(1)
    headerLine:SetColorTexture(1, 0.82, 0, 0.3)

    local scrollFrame = CreateFrame("ScrollFrame", nil, listInset, "UIPanelScrollFrameTemplate")
    scrollFrame:SetPoint("TOPLEFT", headerLine, "BOTTOMLEFT", 0, -2)
    scrollFrame:SetPoint("BOTTOMRIGHT", listInset, "BOTTOMRIGHT", -26, 4)
    ui.scrollFrame = scrollFrame

    local scrollChild = CreateFrame("Frame", nil, scrollFrame)
    scrollChild:SetWidth(scrollFrame:GetWidth())
    scrollChild:SetHeight(1)
    scrollFrame:SetScrollChild(scrollChild)
    ui.scrollChild = scrollChild
    ui.rows = {}

    scrollFrame:EnableMouseWheel(true)
    scrollFrame:SetScript("OnMouseWheel", function(self, delta)
        local current = self:GetVerticalScroll() or 0
        local maxScroll = math.max(0, (scrollChild:GetHeight() or 0) - (self:GetHeight() or 0))
        local step = ROW_HEIGHT * 3
        self:SetVerticalScroll(math.max(0, math.min(maxScroll, current - (delta * step))))
    end)

    -- Empty state
    local emptyState = CreateFrame("Frame", nil, listInset)
    emptyState:SetAllPoints(scrollFrame)
    emptyState:Hide()
    ui.emptyState = emptyState

    local emptyIcon = emptyState:CreateTexture(nil, "ARTWORK")
    emptyIcon:SetSize(48, 48)
    emptyIcon:SetPoint("CENTER", emptyState, "CENTER", 0, 30)
    emptyIcon:SetAtlas("Talents-Loadout-Add")
    emptyIcon:SetAlpha(0.4)

    local emptyText = emptyState:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    emptyText:SetPoint("TOP", emptyIcon, "BOTTOM", 0, -12)
    emptyText:SetWidth(320)
    emptyText:SetJustifyH("CENTER")
    emptyText:SetText("No saved loadouts found.\n\n|cffAAAAAAUse |cffFFD100Save Current|r|cffAAAAAA to save your talents,\nor |cffFFD100Import|r|cffAAAAAA to paste a build string.|r")

    local emptySearchText = emptyState:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    emptySearchText:SetPoint("CENTER", emptyState, "CENTER", 0, 0)
    emptySearchText:SetWidth(320)
    emptySearchText:SetJustifyH("CENTER")
    emptySearchText:SetText("|cffAAAAAANo loadouts match your search.|r")
    emptySearchText:Hide()
    ui.emptySearchText = emptySearchText
    ui.emptyIcon = emptyIcon
    ui.emptyText = emptyText

    -- Bottom bar
    local bottomBar = CreateFrame("Frame", nil, frame)
    bottomBar:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 12, 6)
    bottomBar:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -12, 6)
    bottomBar:SetHeight(40)

    local statusText = bottomBar:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    statusText:SetPoint("LEFT", bottomBar, "LEFT", 4, 0)
    statusText:SetJustifyH("LEFT")
    ui.statusText = statusText

    local bulkDeleteBtn = createNativeButton(bottomBar, "Bulk Delete", 140)
    bulkDeleteBtn:SetPoint("RIGHT", bottomBar, "RIGHT", 0, 0)
    bulkDeleteBtn:SetScript("OnClick", function()
        local count = getSelectedCount()
        if count == 0 then
            ns:Print("No loadouts selected.")
            return
        end
        StaticPopupDialogs.QUICKLOADOUT_CONFIRM_BULK_DELETE.text =
            "Delete |cffFF4444" .. count .. "|r selected loadout" .. (count > 1 and "s" or "") .. "?\n\nThis cannot be undone."
        StaticPopup_Show("QUICKLOADOUT_CONFIRM_BULK_DELETE")
    end)
    ui.bulkDeleteBtn = bulkDeleteBtn

    frame:Hide()
    ui.main = frame
end

function ns.RefreshLoadoutPanel()
    if not ui.main then
        return
    end

    local list = getFilteredLoadouts()
    local selectedCount = getSelectedCount()
    local totalLoadouts = ns.LoadoutData and #ns.LoadoutData:GetCachedLoadouts() or 0

    if ui.sortDropdown then
        ui.sortDropdown:GenerateMenu()
    end

    if ui.selectAllBtn then
        local allSelected = #list > 0
        for _, entry in ipairs(list) do
            if not state.selectedIDs[entry.configID] then
                allSelected = false
                break
            end
        end
        ui.selectAllBtn:SetText(allSelected and "Deselect All" or "Select All")
        DynamicResizeButton_Resize(ui.selectAllBtn)
    end

    for _, row in ipairs(ui.rows) do
        row:Hide()
    end

    -- Empty state handling
    if #list == 0 then
        if ui.emptyState then
            ui.emptyState:Show()
            local isSearching = state.searchText and state.searchText ~= ""
            ui.emptySearchText:SetShown(isSearching)
            ui.emptyIcon:SetShown(not isSearching)
            ui.emptyText:SetShown(not isSearching)
        end
        ui.scrollFrame:Hide()
    else
        if ui.emptyState then
            ui.emptyState:Hide()
        end
        ui.scrollFrame:Show()
    end

    for i, entry in ipairs(list) do
        if not ui.rows[i] then
            ui.rows[i] = createRow(ui.scrollChild, i)
        end
        local row = ui.rows[i]
        row:ClearAllPoints()
        row:SetPoint("TOPLEFT", ui.scrollChild, "TOPLEFT", 0, -((i - 1) * (ROW_HEIGHT + ROW_GAP)))
        row:SetPoint("RIGHT", ui.scrollChild, "RIGHT", 0, 0)
        row.stripe:SetShown(i % 2 == 0)
        updateRow(row, entry, state.renameConfigID == entry.configID)
        row:Show()
    end

    local childHeight = math.max(1, #list * (ROW_HEIGHT + ROW_GAP))
    ui.scrollChild:SetHeight(childHeight)

    if ui.statusText then
        local statusParts = {}
        statusParts[#statusParts + 1] = totalLoadouts .. " loadout" .. (totalLoadouts ~= 1 and "s" or "")
        if state.searchText and state.searchText ~= "" then
            statusParts[#statusParts + 1] = #list .. " shown"
        end
        if selectedCount > 0 then
            statusParts[#statusParts + 1] = "|cffFFD100" .. selectedCount .. " selected|r"
        end
        ui.statusText:SetText(table.concat(statusParts, "   "))
    end

    if ui.bulkDeleteBtn then
        if selectedCount > 0 then
            ui.bulkDeleteBtn:SetText("Delete " .. selectedCount .. " Selected")
            ui.bulkDeleteBtn:Enable()
        else
            ui.bulkDeleteBtn:SetText("Bulk Delete")
            ui.bulkDeleteBtn:Disable()
        end
        DynamicResizeButton_Resize(ui.bulkDeleteBtn)
    end
end

-- Static Popup Dialogs

StaticPopupDialogs.QUICKLOADOUT_EXPORT = {
    text = "Copy this talent loadout string (Ctrl+C):",
    button1 = CLOSE,
    hasEditBox = true,
    editBoxWidth = 420,
    timeout = 0,
    whileDead = true,
    hideOnEscape = true,
    preferredIndex = STATICPOPUP_NUMDIALOGS,
    OnShow = function(self, data)
        local str = data or self.data
        local eb = self.editBox or _G[self:GetName() .. "EditBox"]
        if eb and str then
            eb:SetText(str)
            eb:HighlightText()
            eb:SetFocus()
        end
    end,
    EditBoxOnEnterPressed = function(self)
        self:HighlightText()
    end,
    EditBoxOnEscapePressed = function(self)
        self:GetParent():Hide()
    end,
}

StaticPopupDialogs.QUICKLOADOUT_IMPORT = {
    text = "Paste a talent loadout string below:",
    button1 = "Import",
    button2 = CANCEL,
    hasEditBox = true,
    editBoxWidth = 420,
    timeout = 0,
    whileDead = true,
    hideOnEscape = true,
    preferredIndex = STATICPOPUP_NUMDIALOGS,
    OnShow = function(self)
        local eb = self.editBox or _G[self:GetName() .. "EditBox"]
        if eb then
            eb:SetText("")
            eb:SetFocus()
        end
        self.button1:Disable()
    end,
    EditBoxOnTextChanged = function(self)
        local text = self:GetText() or ""
        local parent = self:GetParent()
        if text:len() > 5 then
            parent.button1:Enable()
        else
            parent.button1:Disable()
        end
    end,
    OnAccept = function(self)
        local eb = self.editBox or _G[self:GetName() .. "EditBox"]
        if not eb then return end
        local importString = eb:GetText():match("^%s*(.-)%s*$")
        if not importString or importString == "" then
            ns:Print("No import string provided.")
            return
        end
        local success, err = ns.LoadoutData:ImportLoadout(importString)
        if success then
            ns:Print("Import initiated. Check your Talents window.")
        else
            ns:Print(err or "Import failed.")
        end
    end,
    EditBoxOnEnterPressed = function(self)
        local text = self:GetText() or ""
        if text:len() > 5 then
            local parent = self:GetParent()
            StaticPopupDialogs.QUICKLOADOUT_IMPORT.OnAccept(parent)
            parent:Hide()
        end
    end,
    EditBoxOnEscapePressed = function(self)
        self:GetParent():Hide()
    end,
}

StaticPopupDialogs.QUICKLOADOUT_SAVE_CURRENT = {
    text = "Enter a name for the new loadout:",
    button1 = "Save",
    button2 = CANCEL,
    hasEditBox = true,
    editBoxWidth = 260,
    timeout = 0,
    whileDead = true,
    hideOnEscape = true,
    preferredIndex = STATICPOPUP_NUMDIALOGS,
    OnShow = function(self)
        local eb = self.editBox or _G[self:GetName() .. "EditBox"]
        if eb then
            eb:SetText("")
            eb:SetFocus()
            eb:SetMaxLetters(64)
        end
        self.button1:Disable()
    end,
    EditBoxOnTextChanged = function(self)
        local text = self:GetText() or ""
        local parent = self:GetParent()
        if text:match("%S") then
            parent.button1:Enable()
        else
            parent.button1:Disable()
        end
    end,
    OnAccept = function(self)
        local eb = self.editBox or _G[self:GetName() .. "EditBox"]
        if not eb then return end
        local name = eb:GetText():match("^%s*(.-)%s*$")
        if not name or name == "" then
            ns:Print("Name cannot be empty.")
            return
        end
        local success, err = ns.LoadoutData:SaveCurrentLoadout(name)
        if success then
            ns:Print("Loadout |cffFFD100" .. name .. "|r saved!")
        else
            ns:Print(err or "Failed to save loadout.")
        end
    end,
    EditBoxOnEnterPressed = function(self)
        local text = self:GetText() or ""
        if text:match("%S") then
            local parent = self:GetParent()
            StaticPopupDialogs.QUICKLOADOUT_SAVE_CURRENT.OnAccept(parent)
            parent:Hide()
        end
    end,
    EditBoxOnEscapePressed = function(self)
        self:GetParent():Hide()
    end,
}

StaticPopupDialogs.QUICKLOADOUT_EDIT_NOTE = {
    text = "Edit note:",
    button1 = "Save",
    button2 = CANCEL,
    hasEditBox = true,
    editBoxWidth = 300,
    timeout = 0,
    whileDead = true,
    hideOnEscape = true,
    preferredIndex = STATICPOPUP_NUMDIALOGS,
    OnShow = function(self, data)
        local eb = self.editBox or _G[self:GetName() .. "EditBox"]
        if eb then
            eb:SetText(data or "")
            eb:SetFocus()
            eb:SetMaxLetters(200)
            eb:HighlightText()
        end
    end,
    OnAccept = function(self)
        local eb = self.editBox or _G[self:GetName() .. "EditBox"]
        if not eb then return end
        local configID = StaticPopupDialogs.QUICKLOADOUT_EDIT_NOTE.configID
        if not configID then return end
        local note = eb:GetText() or ""
        ns.LoadoutData:SetNote(configID, note)
        if ns.RefreshLoadoutPanel then
            ns.RefreshLoadoutPanel()
        end
    end,
    EditBoxOnEnterPressed = function(self)
        local parent = self:GetParent()
        StaticPopupDialogs.QUICKLOADOUT_EDIT_NOTE.OnAccept(parent)
        parent:Hide()
    end,
    EditBoxOnEscapePressed = function(self)
        self:GetParent():Hide()
    end,
}

StaticPopupDialogs.QUICKLOADOUT_CONFIRM_BULK_DELETE = {
    text = "Delete selected loadouts?",
    button1 = "Delete",
    button2 = CANCEL,
    timeout = 0,
    whileDead = true,
    hideOnEscape = true,
    preferredIndex = STATICPOPUP_NUMDIALOGS,
    OnAccept = function()
        local ids = {}
        for configID in pairs(state.selectedIDs) do
            ids[#ids + 1] = configID
        end
        clearSelection()
        local total = #ids
        ns:Print("Deleting " .. total .. " loadout" .. (total ~= 1 and "s" or "") .. "...")
        ns.LoadoutData:BulkDelete(ids,
            function(current, count)
                ns:Print("Deleting " .. current .. "/" .. count .. "...")
            end,
            function(deleted, failed)
                ns:Print("Done! Deleted " .. deleted .. " loadout" .. (deleted ~= 1 and "s" or "") ..
                    (failed > 0 and (" (" .. failed .. " failed)") or "") .. ".")
            end
        )
    end,
}

StaticPopupDialogs.QUICKLOADOUT_CONFIRM_SINGLE_DELETE = {
    text = "Delete this loadout?",
    button1 = "Delete",
    button2 = CANCEL,
    timeout = 0,
    whileDead = true,
    hideOnEscape = true,
    preferredIndex = STATICPOPUP_NUMDIALOGS,
    OnAccept = function()
        local configID = StaticPopupDialogs.QUICKLOADOUT_CONFIRM_SINGLE_DELETE.configID
        if configID then
            local success = ns.LoadoutData:DeleteLoadout(configID)
            if success then
                state.selectedIDs[configID] = nil
                ns.LoadoutData:ScanLoadouts()
                ns:Print("Loadout deleted.")
            else
                ns:Print("Failed to delete loadout.")
            end
        end
        if ns.RefreshLoadoutPanel then
            ns.RefreshLoadoutPanel()
        end
    end,
}

function ns:ToggleMainWindow()
    if InCombatLockdown() then
        self:Print("Cannot open during combat.")
        return
    end

    if not ui.main then
        createMainWindow()
    end

    if ui.main:IsShown() then
        ui.main:Hide()
    else
        if ns.LoadoutData then
            ns.LoadoutData:ScanLoadouts()
        end
        clearSelection()
        state.renameConfigID = nil
        restoreWindowPosition()
        ui.main:Show()
        ns.RefreshLoadoutPanel()
        if ui.searchBox then
            ui.searchBox:SetFocus()
        end
    end
end

SLASH_QUICKLOADOUT1 = "/qlm"
SLASH_QUICKLOADOUT2 = "/quickloadout"

SlashCmdList.QUICKLOADOUT = function()
    if ChatEdit_GetActiveWindow and ChatEdit_DeactivateChat then
        local activeEditBox = ChatEdit_GetActiveWindow()
        if activeEditBox then
            ChatEdit_DeactivateChat(activeEditBox)
        end
    end
    ns:ToggleMainWindow()
end

local function hookTalentFrame()
    local talentsTab = ClassTalentFrame and ClassTalentFrame.TalentsTab
        or (PlayerSpellsFrame and PlayerSpellsFrame.TalentsFrame)
    if not talentsTab then
        return
    end

    if talentsTab.qlmButton then
        return
    end

    local btn = CreateFrame("Button", nil, talentsTab, "UIPanelDynamicResizeButtonTemplate")
    btn:SetText("Loadout Manager")
    DynamicResizeButton_Resize(btn)
    btn:SetFrameStrata("HIGH")

    local searchBox = talentsTab.SearchBox or talentsTab.SearchPreviewContainer
    local loadoutDropdown = talentsTab.LoadoutDropDown
        or (talentsTab.LoadSystem and talentsTab.LoadSystem.Dropdown)
    if searchBox then
        btn:SetPoint("LEFT", searchBox, "RIGHT", 8, 0)
    elseif loadoutDropdown then
        btn:SetPoint("LEFT", loadoutDropdown, "RIGHT", 220, 0)
    else
        btn:SetPoint("BOTTOMLEFT", talentsTab, "BOTTOMLEFT", 400, 8)
    end

    btn:SetScript("OnClick", function()
        ns:ToggleMainWindow()
    end)
    btn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_TOP")
        GameTooltip:SetText("Open Quick Loadout Manager", 1, 1, 1)
        GameTooltip:AddLine("Manage, sort, import, and export your loadouts", 0.8, 0.8, 0.8)
        GameTooltip:Show()
    end)
    btn:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)

    talentsTab.qlmButton = btn
end

local function hookLoadoutDropdownMenu()
    if not Menu or not Menu.ModifyMenu then
        return
    end

    local tags = {
        "MENU_CLASS_TALENT_PROFILE",
        "MENU_CLASS_TALENT_LOADOUT",
        "MENU_TALENT_LOADOUT",
        "MENU_PLAYER_SPELLS_LOADOUT",
    }
    for _, tag in ipairs(tags) do
        pcall(function()
            Menu.ModifyMenu(tag, function(owner, rootDescription, contextData)
                rootDescription:CreateDivider()
                rootDescription:CreateButton("|cffFFD100Loadout Manager|r", function()
                    ns:ToggleMainWindow()
                    return MenuResponse.CloseAll
                end)
            end)
        end)
    end
end

local hookFrame = CreateFrame("Frame")
hookFrame:RegisterEvent("ADDON_LOADED")
hookFrame:SetScript("OnEvent", function(_, _, addonName)
    if addonName == "Blizzard_ClassTalentUI" or addonName == "Blizzard_PlayerSpells" then
        C_Timer.After(0, function()
            hookTalentFrame()
            hookLoadoutDropdownMenu()
        end)
    end
end)

if C_AddOns.IsAddOnLoaded("Blizzard_ClassTalentUI") or C_AddOns.IsAddOnLoaded("Blizzard_PlayerSpells") then
    C_Timer.After(0, function()
        hookTalentFrame()
        hookLoadoutDropdownMenu()
    end)
end
