-- =========================================================================
-- FILE: Settings.lua (TBC)
-- Ported from Retail with Raider.IO / M+ / AXIOM Inspection removed
-- =========================================================================
local addonName, NS = ...

local GR = _G["GRS_MainFrame"] or NS.GR

if not GR then
    print("|cffff0000A.X.I.O.M. Error:|r Main Frame not found in Settings.lua")
    return
end

-- ... [Visual Helpers] ...
local function SafeCreateBg(f, r, g, b, a) if not f then return end; if f.bg then f.bg:SetColorTexture(r, g, b, a) else local t = f:CreateTexture(nil, "BORDER"); t:SetAllPoints(); t:SetColorTexture(r, g, b, a); f.bg = t end end
local function SafeCreateBorder(f, r, g, b, a) if not f then return end; if f.border then f.border:SetColorTexture(r, g, b, a) else local border = f:CreateTexture(nil, "BACKGROUND"); border:SetPoint("TOPLEFT", -1, 1); border:SetPoint("BOTTOMRIGHT", 1, -1); border:SetColorTexture(r, g, b, a); f.border = border end end
local function SafeSkinButton(btn) if not btn then return end; btn:SetNormalTexture(""); btn:SetHighlightTexture(""); btn:SetPushedTexture(""); btn:SetDisabledTexture(""); if btn.Left then btn.Left:SetAlpha(0) end; if btn.Middle then btn.Middle:SetAlpha(0) end; if btn.Right then btn.Right:SetAlpha(0) end; SafeCreateBg(btn, 0, 0, 0, 1); SafeCreateBorder(btn, 0, 0, 0, 1); if btn.GetFontString and btn:GetFontString() then btn:GetFontString():SetTextColor(1, 1, 1) end; btn:HookScript("OnEnter", function(s) if s.bg then s.bg:SetColorTexture(0.2, 0.2, 0.2, 1) end end); btn:HookScript("OnLeave", function(s) if s.bg then s.bg:SetColorTexture(0, 0, 0, 1) end end) end
local function CreateColorSlider(parent, label, getVal, setVal) local s = CreateFrame("Slider", nil, parent, "OptionsSliderTemplate"); s:SetWidth(180); s:SetHeight(15); s:SetMinMaxValues(0, 1); s:SetValueStep(0.01); s:SetObeyStepOnDrag(true); local txt = s:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); txt:SetPoint("BOTTOMLEFT", s, "TOPLEFT", 0, 2); txt:SetText(label); local val = s:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); val:SetPoint("BOTTOMRIGHT", s, "TOPRIGHT", 0, 2); local n = s:GetName(); if n then if _G[n.."Low"] then _G[n.."Low"]:SetText("") end; if _G[n.."High"] then _G[n.."High"]:SetText("") end; if _G[n.."Text"] then _G[n.."Text"]:SetText("") end end; local initialVal = 0; if getVal then initialVal = getVal() or 0 end; s:SetValue(initialVal); val:SetText(string.format("%.2f", initialVal)); s:SetScript("OnValueChanged", function(self, v) v = math.floor(v * 100 + 0.5) / 100; val:SetText(string.format("%.2f", v)); if setVal then setVal(v) end end); return s end

-- =========================================================================
-- GUILD IMPORT CONSTANTS (TBC - M+ Score removed)
-- =========================================================================

GR.GUILD_IMPORT_FIELDS = {
    { key = "Name", label = "Name", idx = 1 },
    { key = "Rank", label = "Rank", idx = 2 },
    { key = "Level", label = "Level", idx = 3 },
    { key = "Class", label = "Class", idx = 4 },
    { key = "Race", label = "Race", idx = 5 },
    { key = "Sex", label = "Sex", idx = 6 },
    { key = "LastOnline", label = "Last Online (Days)", idx = 7 },
    { key = "MainAlt", label = "Main/Alt", idx = 8 },
    { key = "Alts", label = "Player Alts", idx = 9 },
    { key = "JoinDate", label = "Join Date", idx = 10 },
    { key = "PromoDate", label = "Promo Date", idx = 11 },
    { key = "RankHist", label = "Rank History", idx = 12 },
    { key = "Birthday", label = "Birthday", idx = 13 },
    { key = "Rep", label = "Guild Rep", idx = 14 },
    { key = "PubNote", label = "Public Note", idx = 15 },
    { key = "OffNote", label = "Officer Note", idx = 16 },
    { key = "CustNote", label = "Custom Note", idx = 17 },
    -- M+ Score removed for TBC
    { key = "Faction", label = "Faction", idx = 19 },
    { key = "GUID", label = "Player GUID", idx = 20 },
}

local SHORT_MONTHS = { Jan="January", Feb="February", Mar="March", Apr="April", May="May", Jun="June", Jul="July", Aug="August", Sep="September", Oct="October", Nov="November", Dec="December" }
local INV_MONTHS = {}; for k,v in pairs(SHORT_MONTHS) do INV_MONTHS[v] = k end

local function ParseImportDate(dateStr)
    if not dateStr or dateStr == "" then return nil, nil end
    local d, m = string.match(dateStr, "^(%d+)%s+(%a+)")
    if d and m then local fullMonth = SHORT_MONTHS[m] or m; return fullMonth, d end
    return nil, nil
end

local function FormatImportDate(d, m, y)
    if not d or not m then return "" end
    local shortM = string.sub(m, 1, 3); if INV_MONTHS[m] then shortM = INV_MONTHS[m] end
    local yStr = y or date("%y"); return string.format("%s %s '%s", d, shortM, yStr)
end

-- =========================================================================
-- 1. SETTINGS VIEW BUILDER
-- =========================================================================

function GR:BuildSettingsView()
    if not self.db then self.db = {} end
    if not self.db.theme then self.db.theme = {} end
    local d = self.db.theme
    if not d.header then d.header = {r=0.1, g=0.1, b=0.2, a=1} end
    if not d.background then d.background = {r=0.05, g=0.05, b=0.05, a=0.95} end
    if not d.buttons then d.buttons = {r=0.2, g=0.2, b=0.2, a=1} end

    local parent = CreateFrame("Frame", "GRS_SettingsView", self.ViewContainer); parent:SetAllPoints(); parent:Hide(); self.views[7] = parent
    
    -- Horizontal Scrollbar Factory (matches RaidRoster style)
    local function CreateHorizontalScrollbar(parentFrame, targetScrollFrame)
        local container = CreateFrame("Frame", nil, parentFrame, "BackdropTemplate")
        container:SetHeight(18)
        container:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = false, tileSize = 16, edgeSize = 12, insets = { left = 3, right = 3, top = 3, bottom = 3 } })
        container:SetBackdropColor(0, 0, 0, 0.8)
        container:SetBackdropBorderColor(0.6, 0.6, 0.6, 1)
        container:SetFrameLevel(parentFrame:GetFrameLevel() + 10)
        
        local leftBtn = CreateFrame("Button", nil, container)
        leftBtn:SetSize(16, 16); leftBtn:SetPoint("LEFT", 2, 0)
        leftBtn:SetNormalTexture("Interface\\Buttons\\UI-ScrollBar-ScrollUpButton-Up")
        leftBtn:GetNormalTexture():SetRotation(math.rad(90))
        leftBtn:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight")
        
        local rightBtn = CreateFrame("Button", nil, container)
        rightBtn:SetSize(16, 16); rightBtn:SetPoint("RIGHT", -2, 0)
        rightBtn:SetNormalTexture("Interface\\Buttons\\UI-ScrollBar-ScrollDownButton-Up")
        rightBtn:GetNormalTexture():SetRotation(math.rad(90))
        rightBtn:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight")
        
        local slider = CreateFrame("Slider", nil, container)
        slider:SetOrientation("HORIZONTAL")
        slider:SetPoint("LEFT", leftBtn, "RIGHT", 0, 0)
        slider:SetPoint("RIGHT", rightBtn, "LEFT", 0, 0)
        slider:SetHeight(18)
        local track = slider:CreateTexture(nil, "BACKGROUND"); track:SetAllPoints(); track:SetColorTexture(0.1, 0.1, 0.1, 1)
        local thumb = slider:CreateTexture(nil, "ARTWORK"); thumb:SetSize(24, 14); thumb:SetColorTexture(0.5, 0.5, 0.5, 1)
        slider:SetThumbTexture(thumb)
        
        local step = 40
        leftBtn:SetScript("OnClick", function() slider:SetValue(slider:GetValue() - step) end)
        rightBtn:SetScript("OnClick", function() slider:SetValue(slider:GetValue() + step) end)
        slider:SetScript("OnValueChanged", function(self, value) targetScrollFrame:SetHorizontalScroll(value); thumb:SetColorTexture(0.8, 0.6, 0, 1) end)
        slider:SetScript("OnMouseUp", function() thumb:SetColorTexture(0.5, 0.5, 0.5, 1) end)
        
        container.SetMinMaxValues = function(self, min, max) slider:SetMinMaxValues(min, max); if max <= min then leftBtn:Disable(); rightBtn:Disable(); thumb:Hide(); container:Hide() else leftBtn:Enable(); rightBtn:Enable(); thumb:Show(); container:Show() end end
        container.GetValue = function(self) return slider:GetValue() end
        container.SetValue = function(self, v) slider:SetValue(v) end
        container.slider = slider
        return container
    end
    
    -- Create scroll frame for horizontal scrolling
    local scrollFrame = CreateFrame("ScrollFrame", "GRS_SettingsScroll", parent)
    scrollFrame:SetPoint("TOPLEFT", 0, -75) -- Offset 75px from top
    scrollFrame:SetPoint("BOTTOMRIGHT", 0, 20) -- Leave room for horizontal scrollbar
    
    local canvas = CreateFrame("Frame", nil, scrollFrame)
    canvas:SetSize(1500, 600) -- Fixed width to accommodate all panes
    scrollFrame:SetScrollChild(canvas)
    SafeCreateBg(canvas, 0.1, 0.1, 0.1, 1)
    self.SettingsCanvas = canvas
    
    -- Horizontal scrollbar
    local hScrollbar = CreateHorizontalScrollbar(parent, scrollFrame)
    hScrollbar:SetPoint("BOTTOMLEFT", 15, 5)
    hScrollbar:SetPoint("BOTTOMRIGHT", -15, 5)
    hScrollbar:SetMinMaxValues(0, 0) -- Will be updated on size change
    
    -- Update scroll range when parent resizes
    parent:SetScript("OnSizeChanged", function(self, w, h)
        local canvasWidth = canvas:GetWidth()
        local viewWidth = scrollFrame:GetWidth()
        local maxScroll = math.max(0, canvasWidth - viewWidth)
        hScrollbar:SetMinMaxValues(0, maxScroll)
    end)
    
    -- Mouse wheel horizontal scroll (with shift key)
    scrollFrame:EnableMouseWheel(true)
    scrollFrame:SetScript("OnMouseWheel", function(self, delta)
        if IsShiftKeyDown() then
            local cur = hScrollbar:GetValue()
            hScrollbar:SetValue(cur - (delta * 40))
        end
    end)
    
    local function UpdateLiveTheme() GR:ApplyTheme(nil) end

    -- LEFT PANE (reduced from 500 to 420)
    local paneLeft = CreateFrame("Frame", nil, canvas); paneLeft:SetPoint("TOPLEFT", 15, -50); paneLeft:SetPoint("BOTTOMLEFT", 15, 30); paneLeft:SetWidth(420)
    SafeCreateBorder(paneLeft, 0.3, 0.3, 0.3, 1); SafeCreateBg(paneLeft, 0.08, 0.08, 0.08, 0.5)
    local hdrLeft = paneLeft:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge"); hdrLeft:SetPoint("TOPLEFT", 15, -15); hdrLeft:SetText(NS.L["GENERAL_SETTINGS"] or "General Settings")
    
    local lblScale = paneLeft:CreateFontString(nil, "OVERLAY", "GameFontNormal"); lblScale:SetPoint("TOPLEFT", 20, -60); lblScale:SetText(NS.L["UI_SCALE"] or "UI Scale")
    local valScale = paneLeft:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); valScale:SetPoint("LEFT", lblScale, "RIGHT", 10, 0); valScale:SetText(string.format("%.2f", self.db.scale or 1.0))
    local sliderScale = CreateFrame("Slider", "GRS_ScaleSlider", paneLeft, "OptionsSliderTemplate"); sliderScale:SetPoint("TOPLEFT", 20, -85); sliderScale:SetWidth(380); sliderScale:SetMinMaxValues(0.6, 1.4); sliderScale:SetValueStep(0.05); sliderScale:SetObeyStepOnDrag(true)
    local sName = sliderScale:GetName(); if _G[sName.."Low"] then _G[sName.."Low"]:SetText("") end; if _G[sName.."High"] then _G[sName.."High"]:SetText("") end; if _G[sName.."Text"] then _G[sName.."Text"]:SetText("") end
    sliderScale:SetValue(self.db.scale or 1.0)
    sliderScale:SetScript("OnValueChanged", function(s, val) local v = math.floor(val * 100 + 0.5) / 100; valScale:SetText(string.format("%.2f", v)) end)
    sliderScale:SetScript("OnMouseUp", function(s) local v = math.floor(s:GetValue() * 100 + 0.5) / 100; if GR.db then GR.db.scale = v end; GR:SetScale(v) end)

    local chkCompact = CreateFrame("CheckButton", "GRS_Settings_CompactCheck", paneLeft, "UICheckButtonTemplate"); chkCompact:SetSize(24, 24); chkCompact:SetPoint("TOPLEFT", 20, -125)
    chkCompact.text = chkCompact:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); chkCompact.text:SetPoint("LEFT", chkCompact, "RIGHT", 5, 0); chkCompact.text:SetText(NS.L["COMPACT_MODE"] or "Enable Compact mode"); chkCompact:SetChecked(self.db.compactMode or false)

    local chkBypass = CreateFrame("CheckButton", "GRS_Settings_BypassCheck", paneLeft, "UICheckButtonTemplate"); chkBypass:SetSize(24, 24); chkBypass:SetPoint("TOPLEFT", 20, -155)
    chkBypass.text = chkBypass:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); chkBypass.text:SetPoint("LEFT", chkBypass, "RIGHT", 5, 0); chkBypass.text:SetText(NS.L["BYPASS_FILTER"] or "Bypass class rotation filter"); chkBypass:SetChecked(self.db.bypassFilter or false)

    local btnApplySettings = CreateFrame("Button", nil, paneLeft, "UIPanelButtonTemplate"); btnApplySettings:SetSize(120, 30); btnApplySettings:SetPoint("TOPLEFT", 20, -190); btnApplySettings:SetText(NS.L["APPLY"] or "Apply"); SafeSkinButton(btnApplySettings)
    btnApplySettings:SetScript("OnClick", function() GR.db.compactMode = chkCompact:GetChecked(); GR.db.bypassFilter = chkBypass:GetChecked(); if GR.ToggleCompactMode then GR:ToggleCompactMode(GR.db.compactMode) end; print("|cff00ff00A.X.I.O.M.:|r " .. (NS.L["SETTINGS_APPLIED"] or "Settings Applied.")) end)

    local txtReload = paneLeft:CreateFontString(nil, "OVERLAY", "GameFontRedSmall"); txtReload:SetPoint("BOTTOMLEFT", 20, 50); txtReload:SetText(NS.L["RELOAD_UI_NOTE"] or "Reload UI for scale/theme changes")
    local btnReload = CreateFrame("Button", nil, paneLeft, "UIPanelButtonTemplate"); btnReload:SetSize(120, 30); btnReload:SetPoint("BOTTOMLEFT", 20, 15); btnReload:SetText(NS.L["RELOAD_UI"] or "Reload UI"); btnReload:SetScript("OnClick", function() ReloadUI() end); SafeSkinButton(btnReload)

    -- MID PANE
    local paneMid = CreateFrame("Frame", nil, canvas); paneMid:SetPoint("TOPLEFT", paneLeft, "TOPRIGHT", 0, 0); paneMid:SetPoint("BOTTOMLEFT", paneLeft, "BOTTOMRIGHT", 0, 0); paneMid:SetWidth(250); SafeCreateBorder(paneMid, 0.3, 0.3, 0.3, 1)
    local function CreateSubPane(relPoint, yOff, title, desc)
        local f = CreateFrame("Frame", nil, paneMid); if relPoint then f:SetPoint("TOPLEFT", relPoint, "BOTTOMLEFT", 0, yOff); f:SetPoint("TOPRIGHT", relPoint, "BOTTOMRIGHT", 0, yOff) else f:SetPoint("TOPLEFT", 0, 0); f:SetPoint("TOPRIGHT", 0, 0) end
        f:SetHeight((canvas:GetHeight() - 80) / 3); SafeCreateBorder(f, 0.2, 0.2, 0.2, 1)
        local t = f:CreateFontString(nil, "OVERLAY", "GameFontNormal"); t:SetPoint("TOP", 0, -10); t:SetText(title)
        if desc then local d = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); d:SetPoint("TOPLEFT", 10, -30); d:SetWidth(230); d:SetJustifyH("LEFT"); d:SetText(desc) end
        return f
    end
    local function MakeColorControls(parent, yStart, dbKey, useAlpha)
        local function GetCol(k) local t = GR.db.theme[dbKey]; return t and t[k] or (k == "a" and 1 or 0) end
        local function SetCol(k, v) if GR.db.theme[dbKey] then GR.db.theme[dbKey][k] = v; UpdateLiveTheme() end end
        local rS = CreateColorSlider(parent, "Red", function() return GetCol("r") end, function(v) SetCol("r", v) end); rS:SetPoint("TOP", 0, yStart)
        local gS = CreateColorSlider(parent, "Green", function() return GetCol("g") end, function(v) SetCol("g", v) end); gS:SetPoint("TOP", 0, yStart - 35)
        local bS = CreateColorSlider(parent, "Blue", function() return GetCol("b") end, function(v) SetCol("b", v) end); bS:SetPoint("TOP", 0, yStart - 70)
        if useAlpha then local aS = CreateColorSlider(parent, "Alpha", function() return GetCol("a") end, function(v) SetCol("a", v) end); aS:SetPoint("TOP", 0, yStart - 105)
        else if GR.db.theme[dbKey] then GR.db.theme[dbKey].a = 1 end end
    end
    local subTop = CreateSubPane(nil, 0, NS.L["HEADER_SIDEBAR"] or "Header / Sidebar", NS.L["TITLE_SIDEBAR_COLORS"] or "Title Bar & Sidebar Colors"); MakeColorControls(subTop, -50, "header", false)
    local subMid = CreateSubPane(subTop, 0, NS.L["BACKGROUND_PANE"] or "Background Pane", nil); MakeColorControls(subMid, -40, "background", true)
    local subBot = CreateSubPane(subMid, 0, NS.L["BUTTONS_TEXT"] or "Buttons & Text", nil); MakeColorControls(subBot, -40, "buttons", false)

    -- RIGHT PANE
    local paneRight = CreateFrame("Frame", nil, canvas); paneRight:SetPoint("TOPLEFT", paneMid, "TOPRIGHT", 0, 0); paneRight:SetPoint("BOTTOMLEFT", paneMid, "BOTTOMRIGHT", 0, 0); paneRight:SetWidth(300); SafeCreateBorder(paneRight, 0.3, 0.3, 0.3, 1); SafeCreateBg(paneRight, 0.05, 0.05, 0.05, 0.5)
    local hdrRight = paneRight:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge"); hdrRight:SetPoint("TOPLEFT", 15, -15); hdrRight:SetText(NS.L["ADVANCED_TOOLS"] or "Advanced Tools")
    
    local chkMacro = CreateFrame("CheckButton", "GRS_Settings_MacroCheck", paneRight, "UICheckButtonTemplate"); chkMacro:SetSize(24, 24); chkMacro:SetPoint("TOPLEFT", 20, -50)
    chkMacro.text = chkMacro:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); chkMacro.text:SetPoint("LEFT", chkMacro, "RIGHT", 5, 0); chkMacro.text:SetText(NS.L["ENABLE_MACRO_TOOLS"] or "Enable Macro Tools")
    if DB_Rmap_Tools and DB_Rmap_Tools.settings then chkMacro:SetChecked(DB_Rmap_Tools.settings.isMacroPanelOpen) else chkMacro:SetChecked(false) end
    chkMacro:SetScript("OnClick", function(self)
        local isEnabled = self:GetChecked(); if DB_Rmap_Tools then if not DB_Rmap_Tools.settings then DB_Rmap_Tools.settings = {} end; DB_Rmap_Tools.settings.isMacroPanelOpen = isEnabled end
        if isEnabled then if GR.InitMacroDashboard then GR:InitMacroDashboard() end; if GR.MacroPanel then GR.MacroPanel:Show() end else if GR.MacroPanel then GR.MacroPanel:Hide() end end
    end)

    local chkSocial = CreateFrame("CheckButton", "GRS_Settings_SocialCheck", paneRight, "UICheckButtonTemplate"); chkSocial:SetSize(24, 24); chkSocial:SetPoint("TOPLEFT", 20, -110)
    chkSocial.text = chkSocial:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); chkSocial.text:SetPoint("LEFT", chkSocial, "RIGHT", 5, 0); chkSocial.text:SetText(NS.L["SOCIAL_GUILD_ACTIVITIES"] or "Social Guild Activities"); chkSocial:SetChecked(self.db.socialEnabled or false)
    chkSocial:SetScript("OnClick", function(self) GR.db.socialEnabled = self:GetChecked(); print("|cff00ff00A.X.I.O.M.:|r Social Activities " .. (GR.db.socialEnabled and "Enabled" or "Disabled")); if GR.UpdateRosterList then GR:UpdateRosterList() end end)
    local noteSocial = paneRight:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); noteSocial:SetPoint("TOPLEFT", 20, -140); noteSocial:SetWidth(260); noteSocial:SetJustifyH("LEFT"); noteSocial:SetText(NS.L["SOCIAL_NOTE"] or "Enables birthdates, anniversaries, and custom social notes for guild members."); noteSocial:SetTextColor(0.6, 0.6, 0.6)

    -- [[ AXIOM INSPECTION / M+ ROSTER TOGGLES REMOVED FOR TBC ]]

    -- [[ IMPORT / EXPORT BUTTONS - at bottom of pane ]]
    local btnImportGRM = CreateFrame("Button", nil, paneRight, "UIPanelButtonTemplate"); btnImportGRM:SetSize(260, 25); btnImportGRM:SetPoint("BOTTOM", 0, 50); btnImportGRM:SetText(NS.L["IMPORT_FROM_GRM"] or "Import from GRM"); SafeSkinButton(btnImportGRM)
    btnImportGRM:SetScript("OnClick", function() GR:OpenGuildImportPopup("IMPORT") end)

    local btnExportGRM = CreateFrame("Button", nil, paneRight, "UIPanelButtonTemplate"); btnExportGRM:SetSize(260, 25); btnExportGRM:SetPoint("TOP", btnImportGRM, "BOTTOM", 0, -10); btnExportGRM:SetText("Export to GRM"); SafeSkinButton(btnExportGRM)
    btnExportGRM:SetScript("OnClick", function() GR:OpenGuildImportPopup("EXPORT") end)

    -- CUSTOM BG PANE (Fixed width to allow Language pane)
    local paneCustom = CreateFrame("Frame", nil, canvas); paneCustom:SetPoint("TOPLEFT", paneRight, "TOPRIGHT", 0, 0); paneCustom:SetPoint("BOTTOMLEFT", paneRight, "BOTTOMRIGHT", 0, 0); paneCustom:SetWidth(200); SafeCreateBorder(paneCustom, 0.3, 0.3, 0.3, 1); SafeCreateBg(paneCustom, 0.05, 0.05, 0.05, 0.5)
    local hdrCustom = paneCustom:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge"); hdrCustom:SetPoint("TOPLEFT", 10, -15); hdrCustom:SetText(NS.L["CUSTOM_BACKGROUND"] or "Custom Background")
    local chkEnableBg = CreateFrame("CheckButton", nil, paneCustom, "UICheckButtonTemplate"); chkEnableBg:SetSize(24, 24); chkEnableBg:SetPoint("TOPLEFT", 10, -40)
    chkEnableBg.text = chkEnableBg:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); chkEnableBg.text:SetPoint("LEFT", chkEnableBg, "RIGHT", 5, 0); chkEnableBg.text:SetText(NS.L["ENABLE_CUSTOM_BG"] or "Enable Custom Background")
    if not self.db.customBackgrounds then self.db.customBackgrounds = {} end; chkEnableBg:SetChecked(self.db.customBackgrounds.enabled or false)
    local lblDir = paneCustom:CreateFontString(nil, "OVERLAY", "GameFontNormal"); lblDir:SetPoint("TOPLEFT", 10, -125); lblDir:SetText(NS.L["SET_DIRECTORY"] or "Set directory")
    local edtDir = CreateFrame("EditBox", nil, paneCustom, "InputBoxTemplate"); edtDir:SetSize(140, 20); edtDir:SetPoint("TOPLEFT", 10, -145); edtDir:SetAutoFocus(false); edtDir:SetFontObject("ChatFontSmall"); edtDir:SetText(self.db.customBackgrounds.directory or "Interface\\AddOns\\GuildRecruiterManagement\\Media")
    local btnDir = CreateFrame("Button", nil, paneCustom, "UIPanelButtonTemplate"); btnDir:SetSize(40, 20); btnDir:SetPoint("LEFT", edtDir, "RIGHT", 5, 0); btnDir:SetText("Set"); SafeCreateBg(btnDir, 0, 0, 0, 1); SafeCreateBorder(btnDir, 0.3, 0.3, 0.3, 1)
    btnDir:SetScript("OnClick", function() GR.db.customBackgrounds.directory = edtDir:GetText(); edtDir:ClearFocus() end)
    local rowsContainer = CreateFrame("Frame", nil, paneCustom); rowsContainer:SetPoint("TOPLEFT", 0, -170); rowsContainer:SetPoint("BOTTOMRIGHT", 0, 0)
    local function CreateBgRow(parent, index, labelText, defaultFile, yOffset)
        local label = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); label:SetPoint("TOPLEFT", 10, yOffset); label:SetText(labelText)
        local edit = CreateFrame("EditBox", nil, parent, "InputBoxTemplate"); edit:SetSize(140, 20); edit:SetPoint("TOPLEFT", 10, yOffset - 15); edit:SetAutoFocus(false)
        if not GR.db.customBackgrounds.files then GR.db.customBackgrounds.files = {} end; edit:SetText(GR.db.customBackgrounds.files[index] or defaultFile or "")
        local btn = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate"); btn:SetSize(40, 20); btn:SetPoint("LEFT", edit, "RIGHT", 5, 0); btn:SetText("Set"); SafeCreateBg(btn, 0, 0, 0, 1); SafeCreateBorder(btn, 0.3, 0.3, 0.3, 1)
        btn:SetScript("OnClick", function() GR.db.customBackgrounds.files[index] = edit:GetText(); edit:ClearFocus() end)
        return edit, btn
    end
    local bgInputs = {}; local y = 0
    if NS.Constants and NS.Constants.TABS then for i = 1, 6 do local tabData = NS.Constants.TABS[i]; local title = tabData and tabData.title or ("Tab " .. i); local e, b = CreateBgRow(rowsContainer, i, title, nil, y); table.insert(bgInputs, {e, b}); y = y - 45 end end
    local e7, b7 = CreateBgRow(rowsContainer, "GuildChat", "Guild Chat", "Background_G-chat.tga", y); table.insert(bgInputs, {e7, b7})
    local function ToggleCustomControls(enable) local alpha = enable and 1 or 0.5; edtDir:SetAlpha(alpha); btnDir:SetAlpha(alpha); rowsContainer:SetAlpha(alpha) end
    chkEnableBg:SetScript("OnClick", function(self) GR.db.customBackgrounds.enabled = self:GetChecked(); ToggleCustomControls(GR.db.customBackgrounds.enabled) end)
    ToggleCustomControls(self.db.customBackgrounds.enabled)

    -- LANGUAGE PANE (Checkbox list like Retail)
    local paneLanguage = CreateFrame("Frame", nil, canvas); paneLanguage:SetPoint("TOPLEFT", paneCustom, "TOPRIGHT", 20, 0); paneLanguage:SetPoint("BOTTOMRIGHT", canvas, "BOTTOMRIGHT", -15, 30); SafeCreateBorder(paneLanguage, 0.3, 0.3, 0.3, 1); SafeCreateBg(paneLanguage, 0.05, 0.05, 0.05, 0.5)
    local hdrLanguage = paneLanguage:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge"); hdrLanguage:SetPoint("TOPLEFT", 10, -15); hdrLanguage:SetText(NS.L["LANGUAGE"] or "Language")
    
    -- Initialize language DB (single selection now stored as string)
    if not self.db.selectedLanguage then self.db.selectedLanguage = nil end
    
    -- Language list with display names and WoW locale keys
    local LANGUAGES = {
        { key = "enUS", label = "English" },
        { key = "deDE", label = "Deutsch" },
        { key = "esES", label = "Español (EU)" },
        { key = "frFR", label = "Français" },
        { key = "itIT", label = "Italiano" },
        { key = "ruRU", label = "Русский" },
        { key = "esMX", label = "Español (AL)" },
        { key = "ptBR", label = "Português" },
        { key = "koKR", label = "한국어" },
        { key = "zhCN", label = "简体中文" },
        { key = "zhTW", label = "繁體中文" },
    }
    
    -- Store references to all checkboxes for radio button behavior
    local langCheckboxes = {}
    
    local langYOffset = -45
    for _, lang in ipairs(LANGUAGES) do
        local chkLang = CreateFrame("CheckButton", "GRS_Settings_Lang_" .. lang.key, paneLanguage, "UICheckButtonTemplate")
        chkLang:SetSize(24, 24); chkLang:SetPoint("TOPLEFT", 10, langYOffset)
        chkLang.text = chkLang:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
        chkLang.text:SetPoint("LEFT", chkLang, "RIGHT", 5, 0); chkLang.text:SetText(lang.label)
        chkLang:SetChecked(self.db.selectedLanguage == lang.key)
        chkLang.langKey = lang.key
        langCheckboxes[lang.key] = chkLang
        
        chkLang:SetScript("OnClick", function(cb)
            -- Radio button behavior: uncheck all others
            for key, otherCb in pairs(langCheckboxes) do
                if key ~= cb.langKey then
                    otherCb:SetChecked(false)
                end
            end
            -- Ensure this one stays checked (or allow toggle off)
            if cb:GetChecked() then
                GR.db.selectedLanguage = cb.langKey
            else
                GR.db.selectedLanguage = nil
            end
        end)
        langYOffset = langYOffset - 28
    end
    
    -- Apply Language Button (black rectangular button with white text)
    local btnApplyLang = CreateFrame("Button", nil, paneLanguage, "UIPanelButtonTemplate")
    btnApplyLang:SetSize(140, 30); btnApplyLang:SetPoint("TOPLEFT", 10, langYOffset - 10)
    btnApplyLang:SetText(NS.L["APPLY_LANGUAGE"] or "Apply Language")
    -- Style: Black background, white text
    btnApplyLang:SetNormalTexture("")
    btnApplyLang:SetHighlightTexture("")
    btnApplyLang:SetPushedTexture("")
    btnApplyLang:SetDisabledTexture("")
    if btnApplyLang.Left then btnApplyLang.Left:SetAlpha(0) end
    if btnApplyLang.Middle then btnApplyLang.Middle:SetAlpha(0) end
    if btnApplyLang.Right then btnApplyLang.Right:SetAlpha(0) end
    local btnBg = btnApplyLang:CreateTexture(nil, "BACKGROUND")
    btnBg:SetAllPoints(); btnBg:SetColorTexture(0, 0, 0, 1)
    local btnBorder = btnApplyLang:CreateTexture(nil, "BORDER")
    btnBorder:SetPoint("TOPLEFT", -1, 1); btnBorder:SetPoint("BOTTOMRIGHT", 1, -1); btnBorder:SetColorTexture(0.3, 0.3, 0.3, 1)
    btnApplyLang.bg = btnBg
    if btnApplyLang:GetFontString() then btnApplyLang:GetFontString():SetTextColor(1, 1, 1) end
    btnApplyLang:HookScript("OnEnter", function(s) if s.bg then s.bg:SetColorTexture(0.2, 0.2, 0.2, 1) end end)
    btnApplyLang:HookScript("OnLeave", function(s) if s.bg then s.bg:SetColorTexture(0, 0, 0, 1) end end)
    btnApplyLang:SetScript("OnClick", function()
        local lang = GR.db.selectedLanguage
        if lang then
            print("|cff00ff00A.X.I.O.M.:|r " .. (NS.L["LANGUAGE_SET_TO"] or "Language set to:") .. " " .. lang)
            ReloadUI()
        else
            print("|cff00ff00A.X.I.O.M.:|r " .. (NS.L["NO_LANGUAGE_SELECTED"] or "No language selected."))
        end
    end)
end

function GR:OpenSettings() self._settingsPrevTab = self.activeTab or 1; self:SwitchView(7) end
function GR:CloseSettings() local back = self._settingsPrevTab or 1; if back == 7 then back = 1 end; self:SwitchView(back) end

-- =========================================================================
-- 2. RECOLOR / THEME ENGINE
-- =========================================================================
local function RecursiveColor(frame, theme, depth)
    if not frame or depth > 20 then return end 
    local isButton = frame:IsObjectType("Button")
    if frame.bg and frame.bg.SetColorTexture then if isButton then frame.bg:SetColorTexture(theme.buttons.r, theme.buttons.g, theme.buttons.b, theme.buttons.a) else frame.bg:SetColorTexture(theme.background.r, theme.background.g, theme.background.b, theme.background.a) end end
    if frame.border and frame.border.SetColorTexture then if isButton then frame.border:SetColorTexture(0, 0, 0, 1) else frame.border:SetColorTexture(theme.buttons.r, theme.buttons.g, theme.buttons.b, 1) end end
    if isButton and frame.GetFontString and frame:GetFontString() then frame:GetFontString():SetTextColor(1, 0.82, 0) end
    local children = {frame:GetChildren()}; for _, child in ipairs(children) do if not child:IsForbidden() then RecursiveColor(child, theme, depth + 1) end end
end

function GR:ApplyTheme(inputTheme)
    local t = inputTheme or (self.db and self.db.theme) or { header={r=0.1, g=0.1, b=0.2, a=1}, background={r=0.05, g=0.05, b=0.05, a=0.95}, buttons={r=0.2, g=0.2, b=0.2, a=1} }
    if self.bg then
        local useTexture = false
        if self.db.customBackgrounds and self.db.customBackgrounds.enabled then
            local file = self.db.customBackgrounds.files and self.db.customBackgrounds.files[self.activeTab]
            if file and file ~= "" then local dir = self.db.customBackgrounds.directory; if string.sub(dir, -1) ~= "\\" then dir = dir .. "\\" end; self.bg:SetTexture(dir .. file); self.bg:SetBlendMode("BLEND"); self.bg:SetVertexColor(1, 1, 1, 1); useTexture = true end
        end
        if not useTexture then self.bg:SetTexture(nil); self.bg:SetColorTexture(t.background.r, t.background.g, t.background.b, t.background.a) end
    end
    if self.TitleBar and self.TitleBar.bg then self.TitleBar.bg:SetColorTexture(t.header.r, t.header.g, t.header.b, t.header.a) end
    if self.SideBar and self.SideBar.bg then self.SideBar.bg:SetColorTexture(t.header.r, t.header.g, t.header.b, t.header.a) end
    if self.SettingsCanvas then RecursiveColor(self.SettingsCanvas, t, 0) end
end

-- =========================================================================
-- 3. GUILD IMPORT POPUP LOGIC
-- =========================================================================

function GR:OpenGuildImportPopup(mode)
    if not self.GRMPopup then self:CreateGRMPopup() end
    local f = self.GRMPopup
    f.mode = mode
    f.title:SetText(mode == "IMPORT" and "Import from GuildImport" or "Export for GuildImport")
    f.actionBtn:SetText(mode == "IMPORT" and "Import" or "Export")
    
    if mode == "IMPORT" then
        f.warning:Show()
        f.editBox:SetText(""); f.editBox:Enable()
        if C_GuildInfo and C_GuildInfo.GuildRoster then C_GuildInfo.GuildRoster() elseif _G.GuildRoster then _G.GuildRoster() end
        if GR.FetchGuildData then GR:FetchGuildData(); local count = 0; if GR.GUIDRegistry then for k,v in pairs(GR.GUIDRegistry) do count = count + 1 end end; print("|cff00ff00A.X.I.O.M.:|r Roster Scanned. " .. count .. " GUIDs registered for import.") end
    else
        f.warning:Hide(); f.editBox:SetText(""); f.editBox:Disable() 
    end
    for _, cb in pairs(f.checks) do cb:SetChecked(false) end
    if f.selectAll then f.selectAll:SetChecked(false) end
    if f.logPanel then f.logPanel:Hide() end
    f.actionBtn:Disable(); f:Show()
end

function GR:CreateGRMPopup()
    -- Styled checkbox helper (matches Settings tab design: dark box, gold check mark)
    local function CreatePopupCheckbox(parentFrame, label, xOff, yOff)
        local f = CreateFrame("Button", nil, parentFrame)
        f:SetSize(20, 20)
        f:SetPoint("TOPLEFT", xOff or 0, yOff or 0)
        local box = f:CreateTexture(nil, "BORDER"); box:SetAllPoints(); box:SetColorTexture(0.08, 0.08, 0.08, 1); f.box = box
        local brd = f:CreateTexture(nil, "BACKGROUND"); brd:SetPoint("TOPLEFT", -1, 1); brd:SetPoint("BOTTOMRIGHT", 1, -1); brd:SetColorTexture(0.35, 0.35, 0.35, 1); f.brd = brd
        local mark = f:CreateTexture(nil, "ARTWORK"); mark:SetSize(14, 14); mark:SetPoint("CENTER"); mark:SetColorTexture(0.85, 0.65, 0, 1); mark:Hide(); f.mark = mark
        if label then
            local txt = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); txt:SetPoint("LEFT", f, "RIGHT", 6, 0); txt:SetText(label); txt:SetTextColor(0.9, 0.9, 0.9); f.label = txt
        end
        f.checked = false
        f.SetChecked = function(self, val) self.checked = val; if val then self.mark:Show(); self.brd:SetColorTexture(0.85, 0.65, 0, 1) else self.mark:Hide(); self.brd:SetColorTexture(0.35, 0.35, 0.35, 1) end end
        f.GetChecked = function(self) return self.checked end
        f:SetScript("OnClick", function(self)
            self.checked = not self.checked
            self:SetChecked(self.checked)
            if self.onClickHandler then self:onClickHandler() end
        end)
        f:SetScript("OnEnter", function(self) if not self.checked then self.box:SetColorTexture(0.15, 0.15, 0.15, 1) end end)
        f:SetScript("OnLeave", function(self) if not self.checked then self.box:SetColorTexture(0.08, 0.08, 0.08, 1) end end)
        return f
    end

    local f = CreateFrame("Frame", "GRS_GRM_Popup", UIParent, "BackdropTemplate")
    f:SetSize(600, 500); f:SetPoint("CENTER"); f:SetFrameStrata("FULLSCREEN_DIALOG"); f:EnableMouse(true); f:SetMovable(true); f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving); f:SetScript("OnDragStop", f.StopMovingOrSizing)
    local bg = f:CreateTexture(nil, "BACKGROUND"); bg:SetAllPoints(); bg:SetTexture("Interface\\AddOns\\GuildRecruiterManagement\\RaidRoster\\Import_BG.tga"); f.bg = bg
    local overlay = f:CreateTexture(nil, "BORDER"); overlay:SetAllPoints(); overlay:SetColorTexture(0, 0, 0, 0.2); f.overlay = overlay
    SafeCreateBorder(f, 0.3, 0.3, 0.3, 1)

    f.title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge"); f.title:SetPoint("TOP", 0, -15)
    f.warning = f:CreateFontString(nil, "OVERLAY", "GameFontNormal"); f.warning:SetPoint("TOP", 0, -40); f.warning:SetText("|cffff0000Please only import the data WITHOUT headers. The addon is configured with delimiters.|r"); f.warning:SetWidth(550); f.warning:SetWordWrap(true)
    
    -- Styled Checkbox Grid
    local chkContainer = CreateFrame("Frame", nil, f); chkContainer:SetSize(550, 200); chkContainer:SetPoint("TOP", 0, -60)
    f.checks = {}; local colWidth = 180; local cols = 3

    -- Select All checkbox
    local selectAll = CreatePopupCheckbox(chkContainer, "|cff00ff00Select All|r", 10, 0)
    selectAll.onClickHandler = function(self)
        local state = self:GetChecked()
        for _, cb in pairs(f.checks) do cb:SetChecked(state) end
        if state then f.actionBtn:Enable() else f.actionBtn:Disable() end
    end
    f.selectAll = selectAll

    -- Individual field checkboxes
    local startY = -28
    for i, field in ipairs(GR.GUILD_IMPORT_FIELDS) do
        local col = (i - 1) % cols
        local row = math.floor((i - 1) / cols)
        local cb = CreatePopupCheckbox(chkContainer, field.label, 10 + (col * colWidth), startY - (row * 25))
        cb.fieldIdx = field.idx; cb.key = field.key
        cb.onClickHandler = function()
            local anyChecked = false; local allChecked = true
            for _, c in pairs(f.checks) do
                if c:GetChecked() then anyChecked = true else allChecked = false end
            end
            if anyChecked then f.actionBtn:Enable() else f.actionBtn:Disable() end
            f.selectAll:SetChecked(allChecked)
        end
        f.checks[field.key] = cb
    end
    
    local scroll = CreateFrame("ScrollFrame", "GRS_GRM_Scroll", f, "UIPanelScrollFrameTemplate"); scroll:SetPoint("TOPLEFT", 20, -280); scroll:SetPoint("BOTTOMRIGHT", -40, 60); SafeCreateBg(scroll, 0, 0, 0, 0.5)
    if NS.StyleScrollBar then NS.StyleScrollBar(scroll) end
    scroll:EnableMouse(true); scroll:SetScript("OnMouseDown", function() if f.editBox then f.editBox:SetFocus() end end)
    local eb = CreateFrame("EditBox", nil, scroll); eb:SetMultiLine(true); eb:SetFontObject("ChatFontNormal"); eb:SetWidth(scroll:GetWidth() - 20); eb:SetHeight(200); eb:SetAutoFocus(false); scroll:SetScrollChild(eb); f.editBox = eb
    
    local btnAction = CreateFrame("Button", nil, f, "UIPanelButtonTemplate"); btnAction:SetSize(120, 30); btnAction:SetPoint("BOTTOMLEFT", 20, 20); SafeSkinButton(btnAction); f.actionBtn = btnAction
    btnAction:SetScript("OnClick", function()
        if f.mode == "IMPORT" then
            local logEntries = GR:ProcessGuildImport(f.editBox:GetText(), f.checks)
            f.actionBtn:Disable()
            if logEntries and #logEntries > 0 then
                GR:ShowImportLog(f, logEntries)
            end
        else
            local exportText, exportLog = GR:GenerateGuildExport(f.checks)
            f.editBox:SetText(exportText); f.editBox:HighlightText(); f.editBox:SetFocus()
            if exportLog and #exportLog > 0 then
                GR:ShowImportLog(f, exportLog)
            end
        end
    end)
    local btnClose = CreateFrame("Button", nil, f, "UIPanelButtonTemplate"); btnClose:SetSize(120, 30); btnClose:SetPoint("BOTTOMRIGHT", -20, 20); btnClose:SetText("Close"); SafeSkinButton(btnClose)
    btnClose:SetScript("OnClick", function() f:Hide(); if f.logPanel then f.logPanel:Hide() end end)

    -- ==========================================
    -- GOBLIN LOG PANEL (anchored LEFT of popup)
    -- ==========================================
    local log = CreateFrame("Frame", "GRS_GRM_LogPanel", f, "BackdropTemplate")
    log:SetSize(300, 500); log:SetPoint("TOPRIGHT", f, "TOPLEFT", -2, 0)
    log:SetFrameStrata("FULLSCREEN_DIALOG"); log:EnableMouse(true); log:SetMovable(true)
    SafeCreateBg(log, 0.04, 0.04, 0.04, 0.95); SafeCreateBorder(log, 0.3, 0.3, 0.3, 1)
    log:Hide()

    local logTitle = log:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge"); logTitle:SetPoint("TOP", 0, -10)
    logTitle:SetText("|cffD4A017~ Ledger of the Unwilling Scribe ~|r")

    local logScroll = CreateFrame("ScrollingMessageFrame", nil, log)
    logScroll:SetPoint("TOPLEFT", 10, -35); logScroll:SetPoint("BOTTOMRIGHT", -10, 40)
    logScroll:SetFontObject("GameFontHighlightSmall"); logScroll:SetJustifyH("LEFT")
    logScroll:SetMaxLines(500); logScroll:SetFading(false)
    logScroll:EnableMouseWheel(true)
    logScroll:SetScript("OnMouseWheel", function(self, delta) if delta > 0 then self:ScrollUp() else self:ScrollDown() end end)
    log.msgFrame = logScroll

    local btnLogClose = CreateFrame("Button", nil, log, "UIPanelButtonTemplate"); btnLogClose:SetSize(100, 25); btnLogClose:SetPoint("BOTTOM", 0, 10); btnLogClose:SetText("Dismiss"); SafeSkinButton(btnLogClose)
    btnLogClose:SetScript("OnClick", function() log:Hide() end)

    f.logPanel = log
    self.GRMPopup = f
end

function GR:ShowImportLog(popup, logEntries)
    local log = popup.logPanel
    log.msgFrame:Clear()
    log:Show()

    log.session = (log.session or 0) + 1
    local currentSession = log.session
    local idx = 0

    local function PlayNext()
        if log.session ~= currentSession then return end
        idx = idx + 1
        if idx > #logEntries then return end
        log.msgFrame:AddMessage("|cffD4A017>|r " .. logEntries[idx])
        local delay = 0.4 + math.random() * 0.3
        C_Timer.After(delay, PlayNext)
    end
    PlayNext()
end

-- =========================================================================
-- 4. IMPORT / EXPORT DATA LOGIC
-- =========================================================================

function GR:ProcessGuildImport(text, checks)
    if not text or text == "" then return {} end
    local count = 0
    local skipped = 0
    local logEntries = {}

    -- Goblin quip banks
    local GOBLIN_INTRO = {
        "Yeah yeah... Working on it....",
        "Ugh, more paperwork. Fine, let's see what we got...",
        "OI BOSS, Hold on, let me adjust the ledger...",
        "*grumbles* ...who made ME the record keeper?",
    }
    local GOBLIN_MAIN = {
        "marked as |cff00ff00MAIN|r. Big shot, huh.",
        "set as the head honcho. |cff00ff00MAIN|r. Done.",
        "is the |cff00ff00MAIN|r. All roads lead to this one.",
    }
    local GOBLIN_ALT = {
        "is the alt of |cffD4A017%s|r?! Tedious work...",
        "linked to |cffD4A017%s|r. More connections than a spider web...",
        "scribbled under |cffD4A017%s|r. Alt duty.",
    }
    local GOBLIN_SKIP = {
        "Couldn't find |cffff0000%s|r in the guild roster. SKIPPED!",
        "|cffff0000%s|r? Never heard of 'em. NEXT!",
        "Who is |cffff0000%s|r?! Not in the books. Moving on.",
    }
    local GOBLIN_DONE = {
        "Phew! Done! |cff00ff00%d|r members adjusted. Skipped |cffff0000%d|r. Now leave me alone.",
        "That's it! |cff00ff00%d|r souls accounted for. |cffff0000%d|r skipped. My hand is cramping...",
        "FINALLY! |cff00ff00%d|r entries processed. |cffff0000%d|r skipped. Don't come back!",
    }
    local GOBLIN_OUTBURST = {
        "|cffFF3333*GRUNT* THIS IS SLAVE LABOR, I TELL YA!|r",
        "|cffFF3333OI! WHO DECIDED I HAVE TO DO THIS?! I WANT A RAISE!|r",
        "|cffFF3333*SMASHES INKWELL* I HATE THIS JOB!|r",
        "|cffFF3333UGGGHH... MY HAND IS CRAMPING! SOMEONE GET ME A POTION!|r",
        "|cffFF3333WHY ARE THERE SO MANY OF THEM?! IS THIS A GUILD OR AN ARMY?!|r",
        "|cffFF3333*SPITS ON FLOOR* ANOTHER NAME. ANOTHER. NAME.|r",
        "|cffFF3333I DIDN'T SIGN UP FOR THIS! ...ACTUALLY I WAS FORCED.|r",
        "|cffFF3333CAN I TAKE A BREAK?! NO?! FINE!|r",
        "|cffFF3333*THROWS QUILL* I QUIT! ...no wait, they'll feed me to the worg.|r",
        "|cffFF3333MY BACK HURTS, MY EYES HURT, MY SOUL HURTS!|r",
        "|cffFF3333*SLAMS FIST ON TABLE* HOW MANY MORE?!|r",
        "|cffFF3333I COULD BE MINING GOLD RIGHT NOW. GOLD!|r",
    }
    local function RandQuip(bank) return bank[math.random(#bank)] end
    
    if not GRS_SocialDB then GRS_SocialDB = {} end
    local db = GR:GetDB()
    if not db.mains then db.mains = {} end
    if not db.alts then db.alts = {} end
    if not db.times then db.times = {} end
    if not db.factions then db.factions = {} end
    db.ghostFinished = true

    local rosterMap = {}
    local num = GetNumGuildMembers()
    for i = 1, num do
        local fullName = GetGuildRosterInfo(i)
        if fullName then local short = Ambiguate(fullName, "none"); local canonical = GR:GetCanonicalName(fullName); rosterMap[short] = canonical end
    end
    if not GR.GUIDRegistry or next(GR.GUIDRegistry) == nil then GR:FetchGuildData() end

    -- Count total lines for intro
    local totalLines = 0
    for line in string.gmatch(text, "[^\r\n]+") do totalLines = totalLines + 1 end

    -- Intro quips
    table.insert(logEntries, RandQuip(GOBLIN_INTRO))
    table.insert(logEntries, string.format("Let's see... Ok, total of |cffD4A017%d|r entries need to be processed in the ledger... hmm...", totalLines))
    table.insert(logEntries, RandQuip(GOBLIN_OUTBURST))
    local nextOutburst = math.random(3, 7)
    
    for line in string.gmatch(text, "[^\r\n]+") do
        local cols = { strsplit(";", line) }
        local rawName = cols[1]; local guid = cols[20]
        local mainAltStatus = cols[8]; local altsList = cols[9]
        local raceInput = cols[5]
        
        local targetKey = nil
        if guid and guid ~= "" and GR.GUIDRegistry[guid] then targetKey = GR.GUIDRegistry[guid]
        elseif rawName and rawName ~= "" then local shortImport = Ambiguate(rawName, "none"); targetKey = rosterMap[shortImport] end
        
        if targetKey then
            if not GRS_SocialDB[targetKey] then GRS_SocialDB[targetKey] = {} end
            local sData = GRS_SocialDB[targetKey]
            local playerDisplay = Ambiguate(targetKey, "none")
            local playerActions = {}
            
            if GR.DetermineFaction then
                local faction = GR:DetermineFaction(raceInput)
                db.factions[targetKey] = faction
                if faction and faction ~= "" then
                    table.insert(playerActions, "|cff888888Faction:|r " .. faction)
                end
            end
            
            local timestamp = GetServerTime()
            if mainAltStatus == "Main" then
                db.mains[targetKey] = true; db.alts[targetKey] = nil; db.times[targetKey] = timestamp
                table.insert(logEntries, string.format("|cffD4A017%s|r - %s", playerDisplay, RandQuip(GOBLIN_MAIN)))
            elseif mainAltStatus == "Alt" then
                local linkedParent = nil
                if altsList and altsList ~= "" then
                    for entry in string.gmatch(altsList, "([^,]+)") do
                        if string.find(entry, "%(main%)") then
                            local parentRaw = string.gsub(entry, "%(main%)", "")
                            parentRaw = parentRaw:gsub("^%s*(.-)%s*$", "%1") 
                            local parentKey = nil
                            local parentShort = Ambiguate(parentRaw, "none")
                            if rosterMap[parentShort] then parentKey = rosterMap[parentShort]
                            elseif GR:GetCanonicalName(parentRaw) then parentKey = parentRaw end
                            
                            if parentKey and parentKey ~= targetKey then
                                db.mains[targetKey] = nil; db.alts[targetKey] = parentKey; db.times[targetKey] = timestamp
                                linkedParent = Ambiguate(parentKey, "none")
                            end
                            break
                        end
                    end
                end
                if linkedParent then
                    table.insert(logEntries, string.format("|cffD4A017%s|r - %s", playerDisplay, string.format(RandQuip(GOBLIN_ALT), linkedParent)))
                else
                    table.insert(logEntries, string.format("|cffD4A017%s|r - processed as Alt. No main found in list.", playerDisplay))
                end
            else
                table.insert(logEntries, string.format("|cffD4A017%s|r - processed. Next!", playerDisplay))
            end

            -- Social data logging
            if checks["Birthday"] and checks["Birthday"]:GetChecked() then local m, d = ParseImportDate(cols[13]); if m and d then sData.birthMonth = m; sData.birthDay = d; table.insert(playerActions, "|cff888888Birthday:|r " .. d .. "/" .. m) end end
            if checks["JoinDate"] and checks["JoinDate"]:GetChecked() then local m, d = ParseImportDate(cols[10]); if m and d then sData.joinMonth = m; sData.joinDay = d; table.insert(playerActions, "|cff888888Joined:|r " .. d .. "/" .. m) end end
            if checks["PromoDate"] and checks["PromoDate"]:GetChecked() then local m, d = ParseImportDate(cols[11]); if m and d then sData.promMonth = m; sData.promDay = d; table.insert(playerActions, "|cff888888Promoted:|r " .. d .. "/" .. m) end end
            if checks["PubNote"] and checks["PubNote"]:GetChecked() and cols[15] ~= "" then sData.pubNote = cols[15]; table.insert(playerActions, "|cff888888PubNote:|r set") end
            if checks["OffNote"] and checks["OffNote"]:GetChecked() and cols[16] ~= "" then sData.offNote = cols[16]; table.insert(playerActions, "|cff888888OffNote:|r set") end
            if checks["CustNote"] and checks["CustNote"]:GetChecked() and cols[17] ~= "" then sData.customNote = cols[17]; table.insert(playerActions, "|cff888888CustNote:|r set") end

            if #playerActions > 0 then
                table.insert(logEntries, "    " .. table.concat(playerActions, " | "))
            end

            count = count + 1
            if count >= nextOutburst then
                table.insert(logEntries, RandQuip(GOBLIN_OUTBURST))
                nextOutburst = nextOutburst + math.random(3, 7)
            end
        else
            skipped = skipped + 1
            local skipName = rawName or "???"
            table.insert(logEntries, string.format(RandQuip(GOBLIN_SKIP), skipName))
        end
    end

    -- Summary
    table.insert(logEntries, RandQuip(GOBLIN_OUTBURST))
    table.insert(logEntries, " ")
    table.insert(logEntries, string.format(RandQuip(GOBLIN_DONE), count, skipped))

    print("|cff00ff00A.X.I.O.M.:|r Imported data for " .. count .. " players.")
    if skipped > 0 then print("|cffff0000A.X.I.O.M.:|r Skipped " .. skipped .. " entries (No match found).") end
    if GR.UpdateRosterList then GR:UpdateRosterList() end
    if GR.PerformSync then GR:PerformSync(true) end

    return logEntries
end

function GR:GenerateGuildExport(checks)
    local out = ""
    local logEntries = {}

    -- Greedy goblin quip banks
    local GREEDY_INTRO = {
        "|cff00ff00Cha-ching!|r Time to audit the books... FOR A FEE.",
        "|cff00ff00Business is business!|r Let's see who's worth exporting...",
        "*adjusts monocle* RIGHT, let's appraise the merchandise...",
        "Export, you say? That's gonna cost ya. |cff00ff00Per head.|r",
    }
    local GREEDY_MAIN = {
        "is a |cff00ff00MAIN|r. Premium client. That's |cffFFD700+5g|r for the VIP ledger.",
        "registered as |cff00ff00MAIN|r. Big spender energy. |cffFFD700Cha-ching!|r",
        "|cff00ff00MAIN|r status. I'm chargin' double for that title.",
    }
    local GREEDY_ALT = {
        "is an Alt of |cffD4A017%s|r. Discount rate... |cffFFD700+2g|r. I'm generous.",
        "linked to |cffD4A017%s|r. Alt tax applies. |cffFFD700+2g|r.",
        "under |cffD4A017%s|r. Bundle deal. |cffFFD700Still costs ya.|r",
    }
    local GREEDY_REGULAR = {
        "exported. |cffFFD700+3g|r unregistering fee.",
        "added to the ledger. That's |cffFFD700+3g|r for processing.",
        "filed away. |cffFFD700+3g|r. This ink ain't free!",
        "recorded. |cffFFD700+3g|r. Premium parchment costs, y'know.",
    }
    local GREEDY_GREED = {
        "|cffFFD700*COUNTS GOLD* Hehehehe... the pile grows!|r",
        "|cffFFD700I SMELL PROFIT! BEAUTIFUL, BEAUTIFUL PROFIT!|r",
        "|cffFFD700*BITES COIN* Yep, that's real gold. MINE NOW.|r",
        "|cffFFD700MORE NAMES = MORE GOLD! KEEP 'EM COMING!|r",
        "|cffFFD700*RUBS HANDS TOGETHER* The treasury swells!|r",
        "|cffFFD700I should charge INTEREST on these exports...|r",
        "|cffFFD700AUCTIONEER?! I'M BETTER THAN AN AUCTIONEER!|r",
        "|cffFFD700*POLISHES GOLD TEETH* Business is BOOMING!|r",
        "|cffFFD700You want it FASTER? That's a RUSH FEE!|r",
        "|cffFFD700At this rate, I can buy BOOTY BAY!|r",
        "|cffFFD700Every name I write down puts GOLD in my pocket!|r",
        "|cffFFD700Trade Prince Gallywix would be PROUD of me!|r",
    }
    local GREEDY_DONE = {
        "FINAL TALLY: |cff00ff00%d|r souls exported. Estimated profit: |cffFFD700%dg|r. PLEASURE DOING BUSINESS!",
        "All done! |cff00ff00%d|r entries processed. My cut: |cffFFD700%dg|r. Same time next week?",
        "EXPORT COMPLETE! |cff00ff00%d|r members on the manifest. Invoice total: |cffFFD700%dg|r. PAY UP!",
    }
    local function RandQuip(bank) return bank[math.random(#bank)] end

    local num = GetNumGuildMembers()

    -- Intro
    table.insert(logEntries, RandQuip(GREEDY_INTRO))
    table.insert(logEntries, string.format("Scanning guild roster... |cffD4A017%d|r members detected. That's a LOT of |cffFFD700gold|r...", num))
    table.insert(logEntries, RandQuip(GREEDY_GREED))

    local count = 0
    local totalGold = 0
    local nextGreed = math.random(3, 7)

    for i = 1, num do
        local name, rank, rankIndex, level, class, zone, pubNote, offNote, online, status, classFileName = GetGuildRosterInfo(i)
        if name then
            local cName = GR:GetCanonicalName(name)
            local sData = GRS_SocialDB and GRS_SocialDB[cName] or {}
            local edb = GR:GetDB()
            local guid = (edb.guids and edb.guids[cName]) or UnitGUID(name) or ""
            local playerDisplay = Ambiguate(name, "none")
            
            local f = {}; for j=1, 20 do f[j] = "" end
            if checks["Name"] and checks["Name"]:GetChecked() then f[1] = Ambiguate(name, "none") end
            if checks["Rank"] and checks["Rank"]:GetChecked() then f[2] = rank end
            if checks["Level"] and checks["Level"]:GetChecked() then f[3] = level end
            if checks["Class"] and checks["Class"]:GetChecked() then f[4] = class end
            if checks["Race"] and checks["Race"]:GetChecked() then f[5] = sData.raceSex or "Unknown" end
            if checks["Sex"] and checks["Sex"]:GetChecked() then f[6] = "Male" end
            if checks["LastOnline"] and checks["LastOnline"]:GetChecked() then local y, m, d, h = GetGuildRosterLastOnline(i); local days = (y or 0)*365 + (m or 0)*30 + (d or 0) + (h or 0)/24; f[7] = string.format("%.2f", days) end
            
            local isMain, parent = GR:GetIdentity(cName)
            if checks["MainAlt"] and checks["MainAlt"]:GetChecked() then f[8] = isMain and "Main" or "Alt" end
            if checks["Alts"] and checks["Alts"]:GetChecked() then 
                if isMain then
                    -- If we are main, list children logic could go here
                else
                     if parent then f[9] = parent .. "(main)" end
                end
            end 
            
            if checks["JoinDate"] and checks["JoinDate"]:GetChecked() then f[10] = FormatImportDate(sData.joinDay, sData.joinMonth) end
            if checks["PromoDate"] and checks["PromoDate"]:GetChecked() then f[11] = FormatImportDate(sData.promDay, sData.promMonth) end
            if checks["Birthday"] and checks["Birthday"]:GetChecked() then f[13] = FormatImportDate(sData.birthDay, sData.birthMonth) end
            if checks["Rep"] and checks["Rep"]:GetChecked() then f[14] = "Neutral" end
            if checks["PubNote"] and checks["PubNote"]:GetChecked() then f[15] = pubNote end
            if checks["OffNote"] and checks["OffNote"]:GetChecked() then f[16] = offNote end
            if checks["CustNote"] and checks["CustNote"]:GetChecked() then f[17] = sData.customNote or "" end
            -- M+ Score removed for TBC (field 18 stays empty)
            if checks["Faction"] and checks["Faction"]:GetChecked() then 
                f[19] = (edb.factions and edb.factions[cName]) or "Neutral" 
            end
            if checks["GUID"] and checks["GUID"]:GetChecked() then f[20] = guid end
            out = out .. table.concat(f, ";") .. "\n"
            
            -- Goblin logging per player
            count = count + 1
            local gold = 3
            if isMain then
                gold = 5
                table.insert(logEntries, string.format("|cffD4A017%s|r - %s", playerDisplay, RandQuip(GREEDY_MAIN)))
            elseif parent then
                gold = 2
                local parentDisplay = Ambiguate(parent, "none")
                table.insert(logEntries, string.format("|cffD4A017%s|r - %s", playerDisplay, string.format(RandQuip(GREEDY_ALT), parentDisplay)))
            else
                table.insert(logEntries, string.format("|cffD4A017%s|r - %s", playerDisplay, RandQuip(GREEDY_REGULAR)))
            end
            totalGold = totalGold + gold

            -- Periodic greed outbursts
            if count >= nextGreed then
                table.insert(logEntries, RandQuip(GREEDY_GREED))
                nextGreed = nextGreed + math.random(3, 7)
            end
        end
    end

    -- Summary
    table.insert(logEntries, RandQuip(GREEDY_GREED))
    table.insert(logEntries, " ")
    table.insert(logEntries, string.format(RandQuip(GREEDY_DONE), count, totalGold))

    return out, logEntries
end

-- =========================================================================
-- 5. INITIALIZATION & HOOKS
-- =========================================================================

hooksecurefunc(GR, "SwitchView", function(self, viewIndex) if not self.db or not self.db.theme then return end; self:ApplyTheme(nil) end)
hooksecurefunc(GR, "Initialize", function(self)
    C_Timer.After(0.2, function()
        if not self.db.theme then self.db.theme = {} end
        local d = self.db.theme
        if not d.header then d.header = {r=0.1, g=0.1, b=0.2, a=1} end
        if not d.background then d.background = {r=0.05, g=0.05, b=0.05, a=0.95} end
        if not d.buttons then d.buttons = {r=0.2, g=0.2, b=0.2, a=1} end
        self:ApplyTheme(nil)
        
        -- [[ COMPACT MODE INIT ]]
        if GR.db.compactMode and GR.ToggleCompactMode then
            GR:ToggleCompactMode(true)
        end
    end)
end)