-- =========================================================================
-- FILE: Roster_DetailsPanel.lua
-- =========================================================================
local addonName, NS = ...
local GR = _G["GRS_MainFrame"]

-- Constants for Social Popup
local MONTHS = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}

-- =========================================================================
-- FACTION LOGIC: STRICT MAPS & DETERMINATION (CENTRALIZED)
-- =========================================================================

local RACE_MAP = {
    -- ALLIANCE (IDs)
    [1]="Alliance", [3]="Alliance", [4]="Alliance", [7]="Alliance", [11]="Alliance", [22]="Alliance", 
    [25]="Alliance", [29]="Alliance", [30]="Alliance", [32]="Alliance", [34]="Alliance", [37]="Alliance",
    
    -- ALLIANCE (Names - Spaced)
    ["Human"]="Alliance", ["Dwarf"]="Alliance", ["Night Elf"]="Alliance", ["Gnome"]="Alliance", 
    ["Draenei"]="Alliance", ["Worgen"]="Alliance", ["Pandaren (Alliance)"]="Alliance", 
    ["Void Elf"]="Alliance", ["Lightforged Draenei"]="Alliance", ["Kul Tiran"]="Alliance", 
    ["Dark Iron Dwarf"]="Alliance", ["Mechagnome"]="Alliance",
    
    -- ALLIANCE (Names - API Strict/NoSpaces)
    ["VoidElf"]="Alliance", ["LightforgedDraenei"]="Alliance", ["KulTiran"]="Alliance", 
    ["DarkIronDwarf"]="Alliance", ["Mechagnome"]="Alliance",

    -- HORDE (IDs)
    [2]="Horde", [5]="Horde", [6]="Horde", [8]="Horde", [9]="Horde", [10]="Horde", 
    [26]="Horde", [27]="Horde", [28]="Horde", [31]="Horde", [35]="Horde", [36]="Horde",
    
    -- HORDE (Names - Spaced)
    ["Orc"]="Horde", ["Undead"]="Horde", ["Tauren"]="Horde", ["Troll"]="Horde", 
    ["Goblin"]="Horde", ["Blood Elf"]="Horde", ["Pandaren (Horde)"]="Horde", 
    ["Nightborne"]="Horde", ["Highmountain Tauren"]="Horde", ["Zandalari Troll"]="Horde", 
    ["Vulpera"]="Horde", ["Mag'har Orc"]="Horde",

    -- HORDE (Names - API Strict/NoSpaces)
    ["BloodElf"]="Horde", ["Nightborne"]="Horde", ["HighmountainTauren"]="Horde", 
    ["ZandalariTroll"]="Horde", ["Vulpera"]="Horde", ["MagharOrc"]="Horde"
}

local AMBIGUOUS_RACES = {
    ["Pandaren"] = true, ["Dracthyr"] = true, ["Earthen"] = true,
    [24] = true, [52] = true, [70] = true, [84] = true
}

function GR:DetermineFaction(raceInput, raceID)
    -- 1. Check ID Map
    if raceID and RACE_MAP[raceID] then return RACE_MAP[raceID] end
    
    -- 2. Check Name Map (Cleaned)
    local cleanName = nil
    if raceInput and raceInput ~= "" and raceInput ~= "N/A" then
        if RACE_MAP[raceInput] then return RACE_MAP[raceInput] end
        cleanName = raceInput:gsub("^%s*(.-)%s*$", "%1")
        if RACE_MAP[cleanName] then return RACE_MAP[cleanName] end
    end
    
    -- 3. Check Ambiguous (Neutral Races)
    local isAmbiguous = false
    if raceID and AMBIGUOUS_RACES[raceID] then isAmbiguous = true end
    if cleanName and AMBIGUOUS_RACES[cleanName] then isAmbiguous = true end
    
    if isAmbiguous then
        return "Neutral"
    end

    return "Neutral"
end

-- =========================================================================
-- 1. VISUAL HELPERS (ANNOUNCEMENTS & TOASTS)
-- =========================================================================

function GR:ShowAnnouncement(msg)
    if not GR.AnnounceFrame then
        local f = CreateFrame("Frame", nil, UIParent); f:SetSize(800, 50); f:SetPoint("TOP", 0, -100); f:SetFrameStrata("FULLSCREEN_DIALOG")
        f.text = f:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge"); f.text:SetPoint("CENTER"); f.text:SetTextColor(1, 0.82, 0)
        f.ag = f:CreateAnimationGroup()
        local a1 = f.ag:CreateAnimation("Alpha"); a1:SetFromAlpha(0); a1:SetToAlpha(1); a1:SetDuration(0.3); a1:SetOrder(1)
        local a2 = f.ag:CreateAnimation("Alpha"); a2:SetFromAlpha(1); a2:SetToAlpha(1); a2:SetDuration(2.5); a2:SetOrder(2)
        local a3 = f.ag:CreateAnimation("Alpha"); a3:SetFromAlpha(1); a3:SetToAlpha(0); a3:SetDuration(0.5); a3:SetOrder(3)
        a3:SetScript("OnFinished", function() f:Hide() end); GR.AnnounceFrame = f
    end
    GR.AnnounceFrame.text:SetText(msg); GR.AnnounceFrame:Show(); GR.AnnounceFrame.ag:Stop(); GR.AnnounceFrame.ag:Play()
end

function GR:ShowToast(text, isWarning)
    if not GR.ToastFrame then
        local f = CreateFrame("Frame", "GRS_Toast", UIParent); f:SetSize(420, 80); f:SetPoint("TOP", 0, -100); f:SetFrameStrata("DIALOG")
        f.text = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge"); f.text:SetPoint("CENTER", 0, 0); f.text:SetWidth(380); f.text:SetJustifyH("CENTER"); f.text:SetWordWrap(true)
        f.ag = f:CreateAnimationGroup(); f.fade = f.ag:CreateAnimation("Alpha"); f.fade:SetFromAlpha(1); f.fade:SetToAlpha(0); f.fade:SetDuration(1.0); f.fade:SetStartDelay(4.0)
        f.ag:SetScript("OnFinished", function() f:Hide() end); GR.ToastFrame = f
    end
    if isWarning then GR.ToastFrame.text:SetTextColor(1, 0.2, 0.2); PlaySound(SOUNDKIT.RAID_WARNING) else GR.ToastFrame.text:SetTextColor(1, 1, 1) end
    GR.ToastFrame.text:SetText(text); GR.ToastFrame:Show(); GR.ToastFrame:SetAlpha(1); GR.ToastFrame.ag:Stop(); GR.ToastFrame.ag:Play()
end

-- =========================================================================
-- 2. RIGHT SIDE PANES (TOP & BOTTOM)
-- =========================================================================

function GR:BuildRightSide(topPane, bottomPane)
    local function Skin(b)
        if NS.SkinButton then NS.SkinButton(b) else
            b:SetNormalTexture(""); b:SetHighlightTexture(""); b:SetPushedTexture(""); b:SetDisabledTexture("")
            local t = b:CreateTexture(nil, "BACKGROUND"); t:SetAllPoints(); t:SetColorTexture(0.2, 0.2, 0.2, 1); b.bg = t
            local border = b:CreateTexture(nil, "BORDER"); border:SetAllPoints(); border:SetColorTexture(0, 0, 0, 1); border:SetDrawLayer("BORDER", -1)
            local fs = b:GetFontString(); if fs then fs:SetTextColor(1, 1, 1) end
        end
    end

    local refBtn = CreateFrame("Button", nil, topPane, "UIPanelButtonTemplate")
    refBtn:SetSize(60, 20); refBtn:SetPoint("TOPRIGHT", -5, -5); refBtn:SetText(NS.L["REFRESH"] or "Refresh")
    Skin(refBtn); refBtn:SetScript("OnClick", function() GR:PerformSync() end)
    
    local togBtn = CreateFrame("Button", nil, topPane, "UIPanelButtonTemplate")
    togBtn:SetSize(180, 20); togBtn:SetPoint("RIGHT", refBtn, "LEFT", -5, 0); togBtn:SetText(NS.L["TOGGLE_OFFLINE_MEMBERS"] or "Toggle Offline Members")
    Skin(togBtn); togBtn:SetScript("OnClick", function(s) GR.ShowOffline = not GR.ShowOffline; s:GetFontString():SetTextColor(1, GR.ShowOffline and 1 or 0.5, GR.ShowOffline and 1 or 0); if #GR.RosterData > 0 then GR:BuildViewList() else GR:FetchGuildData() end end)

    local title = topPane:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    title:SetPoint("TOPLEFT", 10, -5); title:SetText(NS.L["MEMBER_DETAILS"] or "Member Details")
    
    local detailContent = CreateFrame("Frame", nil, topPane)
    detailContent:SetPoint("TOPLEFT", 0, -30); detailContent:SetPoint("BOTTOMRIGHT", 0, 0); detailContent:Hide(); GR.DetailContent = detailContent
    
    local dpTitle = detailContent:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge"); dpTitle:SetPoint("TOPLEFT", 10, 0); GR.DetailName = dpTitle
    local dpRank = detailContent:CreateFontString(nil, "OVERLAY", "GameFontHighlightMedium"); dpRank:SetPoint("TOPLEFT", 10, -25); GR.DetailRank = dpRank
    local dpClass = detailContent:CreateFontString(nil, "OVERLAY", "GameFontHighlightMedium"); dpClass:SetPoint("TOPLEFT", 10, -45); GR.DetailClass = dpClass
    
    local function CreateLabelEdit(labelTxt, y, maxChars)
        local lbl = detailContent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall"); lbl:SetPoint("TOPLEFT", 10, y); lbl:SetText(labelTxt)
        local eb = CreateFrame("EditBox", nil, detailContent, "InputBoxTemplate"); eb:SetPoint("LEFT", lbl, "RIGHT", 10, 0); eb:SetSize(250, 20); eb:SetAutoFocus(false); eb:SetMaxLetters(maxChars)
        return eb
    end
    GR.PublicNoteEdit = CreateLabelEdit(NS.L["PUBLIC_NOTE"] or "Public Note:", -75, 20)
    GR.OfficerNoteEdit = CreateLabelEdit(NS.L["OFFICER_NOTE"] or "Officer Note:", -100, 40)
    
    local btnSave = CreateFrame("Button", nil, detailContent, "UIPanelButtonTemplate")
    btnSave:SetSize(80, 22); btnSave:SetPoint("BOTTOMLEFT", 10, 10); btnSave:SetText(NS.L["SAVE"] or "Save"); Skin(btnSave)
    btnSave:SetScript("OnClick", function()
        if GR.SelectedMember then
            local key = GR.SelectedMember.dbKey
            if key then
                local pubText = GR.PublicNoteEdit:GetText()
                local offText = GR.OfficerNoteEdit:GetText()
                if not GRS_SocialDB[key] then GRS_SocialDB[key] = {} end
                GRS_SocialDB[key].pubNote = pubText
                GRS_SocialDB[key].offNote = offText
                GR.SelectedMember.publicNote = pubText
                GR.SelectedMember.officerNote = offText
                GR:ShowToast("Notes Updated.")
            end
        end
    end)
    
    local btnSocial = CreateFrame("Button", nil, detailContent, "UIPanelButtonTemplate")
    btnSocial:SetSize(110, 22); btnSocial:SetPoint("LEFT", btnSave, "RIGHT", 10, 0); btnSocial:SetText(NS.L["SOCIAL_ACTIVITY"] or "Social Activity"); Skin(btnSocial)
    btnSocial:SetScript("OnClick", function() if GR.SelectedMember then GR:OpenSocialPopup(GR.SelectedMember) end end)
    GR.DetailSocialBtn = btnSocial

    local btnCancel = CreateFrame("Button", nil, detailContent, "UIPanelButtonTemplate")
    btnCancel:SetSize(80, 22); btnCancel:SetPoint("LEFT", btnSocial, "RIGHT", 10, 0); btnCancel:SetText(NS.L["CANCEL"] or "Cancel"); Skin(btnCancel)
    btnCancel:SetScript("OnClick", function() GR:DeselectMember() end)

    local bg = bottomPane:CreateTexture(nil, "ARTWORK"); bg:SetAllPoints()
    local useCustom = false
    if GR.db and GR.db.customBackgrounds and GR.db.customBackgrounds.enabled then
        local file = GR.db.customBackgrounds.files and GR.db.customBackgrounds.files["GuildChat"]
        if file and file ~= "" then
            local dir = GR.db.customBackgrounds.directory
            if string.sub(dir, -1) ~= "\\" then dir = dir .. "\\" end
            bg:SetTexture(dir .. file); bg:SetAlpha(1); useCustom = true
        end
    end
    if not useCustom then bg:SetTexture("Interface\\AddOns\\" .. addonName .. "\\Media\\Background_G-chat.tga"); bg:SetAlpha(0.25) end
    GR:InitGuildChat(bottomPane)
end

-- =========================================================================
-- 3. SOCIAL POPUP & TOOLTIP LOGIC
-- =========================================================================

function GR:OpenSocialPopup(data)
    if not self.SocialPopup then self:CreateSocialPopup() end
    local f = self.SocialPopup
    local dbKey = data.dbKey
    f.targetKey = dbKey
    local s = GRS_SocialDB[dbKey] or {}

    f.raceSexDD:SetText(s.raceSex or "Male")
    f.joinMonthDD:SetText(s.joinMonth or "January"); f.joinDayDD:SetText(s.joinDay or "1"); f.joinYearDD:SetText(s.joinYear or "")
    f.promMonthDD:SetText(s.promMonth or "January"); f.promDayDD:SetText(s.promDay or "1"); f.promYearDD:SetText(s.promYear or "")
    f.birthMonthDD:SetText(s.birthMonth or "January"); f.birthDayDD:SetText(s.birthDay or "1"); f.birthYearDD:SetText(s.birthYear or "")
    f.pubNote:SetText(s.pubNote or ""); f.offNote:SetText(s.offNote or ""); f.customNote:SetText(s.customNote or "")
    
    local guidToDisplay = data.guid
    if not guidToDisplay or guidToDisplay == "" then
        -- ISOLATION: Check scoped DB first
        local db = GR:GetDB()
        if db.guids then
            guidToDisplay = db.guids[dbKey]
        end
    end
    if not guidToDisplay or guidToDisplay == "" then
        guidToDisplay = UnitGUID(Ambiguate(data.fullName, "none"))
    end
    
    f.guidLabel:SetText(guidToDisplay or "N/A")
    
    f:Show()
end

function GR:CreateSocialPopup()
    local f = CreateFrame("Frame", "GRS_SocialPopup", UIParent, "BackdropTemplate")
    f:SetSize(450, 600); 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)
    
    if NS.CreateBg then NS.CreateBg(f, 0, 0, 0, 0.95) end
    if NS.CreateBorder then NS.CreateBorder(f, 0.3, 0.3, 0.3, 1) end

    local hdr = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    hdr:SetPoint("TOP", 0, -15)
    hdr:SetText("A.X.I.O.M. | Social")
    hdr:SetTextColor(0.4, 0.8, 1, 1)

    local desc = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    desc:SetPoint("TOPLEFT", 20, -45); desc:SetWidth(410); desc:SetJustifyH("LEFT")
    desc:SetText("Here you can add a member’s birthday, the date they joined the guild, and any other notes that matter to you and the rest of the guild.\nIf your guild does RP, turn on the RP features.\nHave fun!")

    local INPUT_X = 140

    local function CreateBaseDropdown(parent, w)
        local dd = CreateFrame("Button", nil, parent)
        dd:SetSize(w, 24)
        if NS.CreateBg then NS.CreateBg(dd, 0.1, 0.1, 0.1, 1) end
        if NS.CreateBorder then NS.CreateBorder(dd, 0.5, 0.5, 0.5, 1) end
        
        local txt = dd:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        txt:SetPoint("LEFT", 5, 0)
        dd.text = txt
        
        dd.SetText = function(self, t) self.text:SetText(t) end
        dd.GetText = function(self) return self.text:GetText() end
        return dd
    end

    local function CreateSocialDropdown(y, label, items)
        local lbl = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall"); lbl:SetPoint("TOPLEFT", 20, y); lbl:SetText(label)
        
        local dd = CreateBaseDropdown(f, 150)
        dd:SetPoint("TOPLEFT", f, "TOPLEFT", INPUT_X, y + 2) 
        
        local list = CreateFrame("Frame", nil, dd); list:SetSize(150, 200); list:SetPoint("TOPLEFT", dd, "BOTTOMLEFT", 0, -2); list:Hide(); list:SetFrameStrata("TOOLTIP")
        if NS.CreateBg then NS.CreateBg(list, 0, 0, 0, 1) end
        local scroll = CreateFrame("ScrollFrame", nil, list, "UIPanelScrollFrameTemplate"); scroll:SetAllPoints()
        if NS.StyleScrollBar then NS.StyleScrollBar(scroll) end
        local content = CreateFrame("Frame", nil, scroll); content:SetSize(130, 10); scroll:SetScrollChild(content)
        
        dd:SetScript("OnClick", function() 
            if list:IsShown() then list:Hide() else
                local ySub = 0
                for i, v in ipairs(items) do
                    local b = CreateFrame("Button", nil, content); b:SetSize(130, 20); b:SetPoint("TOPLEFT", 0, ySub)
                    local bt = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); bt:SetPoint("LEFT", 5, 0); bt:SetText(v)
                    b:SetScript("OnClick", function() dd:SetText(v); list:Hide() end)
                    ySub = ySub - 20
                end
                content:SetHeight(math.abs(ySub)); list:Show()
            end
        end)
        return dd
    end

    f.raceSexDD = CreateSocialDropdown(-120, "Race Sex:", {"Male", "Female"})
    
    local function CreateDoubleDropdown(y, label, items1, items2)
        local lbl = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall"); lbl:SetPoint("TOPLEFT", 20, y); lbl:SetText(label)
        
        local dd1 = CreateBaseDropdown(f, 100)
        dd1:SetPoint("TOPLEFT", f, "TOPLEFT", INPUT_X, y + 2) 
        
        local dd2 = CreateBaseDropdown(f, 50)
        dd2:SetPoint("TOPLEFT", f, "TOPLEFT", INPUT_X + 105, y + 2) 
        
        local list1 = CreateFrame("Frame", nil, dd1); list1:SetSize(100, 200); list1:SetPoint("TOPLEFT", dd1, "BOTTOMLEFT", 0, -2); list1:Hide(); list1:SetFrameStrata("TOOLTIP")
        if NS.CreateBg then NS.CreateBg(list1, 0, 0, 0, 1) end
        local scroll1 = CreateFrame("ScrollFrame", nil, list1, "UIPanelScrollFrameTemplate"); scroll1:SetAllPoints()
        if NS.StyleScrollBar then NS.StyleScrollBar(scroll1) end
        local content1 = CreateFrame("Frame", nil, scroll1); content1:SetSize(80, 10); scroll1:SetScrollChild(content1)
        dd1:SetScript("OnClick", function()
             if list1:IsShown() then list1:Hide() else
                local ySub = 0
                for i, v in ipairs(items1) do
                    local b = CreateFrame("Button", nil, content1); b:SetSize(80, 20); b:SetPoint("TOPLEFT", 0, ySub)
                    local bt = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); bt:SetPoint("LEFT", 5, 0); bt:SetText(v)
                    b:SetScript("OnClick", function() dd1:SetText(v); list1:Hide() end)
                    ySub = ySub - 20
                end
                content1:SetHeight(math.abs(ySub)); list1:Show()
             end
        end)

        local list2 = CreateFrame("Frame", nil, dd2); list2:SetSize(50, 200); list2:SetPoint("TOPLEFT", dd2, "BOTTOMLEFT", 0, -2); list2:Hide(); list2:SetFrameStrata("TOOLTIP")
        if NS.CreateBg then NS.CreateBg(list2, 0, 0, 0, 1) end
        local scroll2 = CreateFrame("ScrollFrame", nil, list2, "UIPanelScrollFrameTemplate"); scroll2:SetAllPoints()
        if NS.StyleScrollBar then NS.StyleScrollBar(scroll2) end
        local content2 = CreateFrame("Frame", nil, scroll2); content2:SetSize(30, 10); scroll2:SetScrollChild(content2)
        dd2:SetScript("OnClick", function()
             if list2:IsShown() then list2:Hide() else
                local ySub = 0
                for i, v in ipairs(items2) do
                    local b = CreateFrame("Button", nil, content2); b:SetSize(30, 20); b:SetPoint("TOPLEFT", 0, ySub)
                    local bt = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); bt:SetPoint("LEFT", 5, 0); bt:SetText(v)
                    b:SetScript("OnClick", function() dd2:SetText(v); list2:Hide() end)
                    ySub = ySub - 20
                end
                content2:SetHeight(math.abs(ySub)); list2:Show()
             end
        end)

        return dd1, dd2
    end

    local days = {}; for i=1,31 do table.insert(days, tostring(i)) end
    local years = {}; for i=1950,2030 do table.insert(years, tostring(i)) end

    -- Helper: creates a year dropdown anchored to the right of an existing dropdown
    local function CreateYearDropdown(anchorDD)
        local dd = CreateBaseDropdown(f, 55)
        dd:SetPoint("LEFT", anchorDD, "RIGHT", 5, 0)

        local lst = CreateFrame("Frame", nil, dd); lst:SetSize(55, 200); lst:SetPoint("TOPLEFT", dd, "BOTTOMLEFT", 0, -2); lst:Hide(); lst:SetFrameStrata("TOOLTIP")
        if NS.CreateBg then NS.CreateBg(lst, 0, 0, 0, 1) end
        local scr = CreateFrame("ScrollFrame", nil, lst, "UIPanelScrollFrameTemplate"); scr:SetAllPoints()
        if NS.StyleScrollBar then NS.StyleScrollBar(scr) end
        local cnt = CreateFrame("Frame", nil, scr); cnt:SetSize(35, 10); scr:SetScrollChild(cnt)
        dd:SetScript("OnClick", function()
            if lst:IsShown() then lst:Hide() else
                local ySub = 0
                for _, v in ipairs(years) do
                    local b = CreateFrame("Button", nil, cnt); b:SetSize(35, 20); b:SetPoint("TOPLEFT", 0, ySub)
                    local bt = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); bt:SetPoint("LEFT", 5, 0); bt:SetText(v)
                    b:SetScript("OnClick", function() dd:SetText(v); lst:Hide() end)
                    ySub = ySub - 20
                end
                cnt:SetHeight(math.abs(ySub)); lst:Show()
            end
        end)
        return dd
    end

    f.joinMonthDD, f.joinDayDD = CreateDoubleDropdown(-150, "Join Date:", MONTHS, days)
    f.joinYearDD = CreateYearDropdown(f.joinDayDD)

    f.promMonthDD, f.promDayDD = CreateDoubleDropdown(-180, "Promotion Date:", MONTHS, days)
    f.promYearDD = CreateYearDropdown(f.promDayDD)

    f.birthMonthDD, f.birthDayDD = CreateDoubleDropdown(-210, "Birthday:", MONTHS, days)
    f.birthYearDD = CreateYearDropdown(f.birthDayDD)

    local function CreateSocialInput(y, label, max)
        local lbl = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall"); lbl:SetPoint("TOPLEFT", 20, y); lbl:SetText(label)
        local eb = CreateFrame("EditBox", nil, f, "InputBoxTemplate")
        eb:SetSize(280, 20)
        eb:SetPoint("TOPLEFT", f, "TOPLEFT", INPUT_X, y + 2) 
        eb:SetAutoFocus(false); eb:SetMaxLetters(max)
        return eb
    end

    f.pubNote = CreateSocialInput(-240, "Public Note:", 31)
    f.offNote = CreateSocialInput(-270, "Officer Note:", 31)
    f.customNote = CreateSocialInput(-300, "Custom Note:", 255)

    local guidLbl = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall"); guidLbl:SetPoint("TOPLEFT", 20, -340); guidLbl:SetText("Player GUID:")
    f.guidLabel = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); f.guidLabel:SetPoint("LEFT", guidLbl, "RIGHT", 10, 0)

    local btnSave = CreateFrame("Button", nil, f)
    btnSave:SetSize(120, 30); btnSave:SetPoint("BOTTOMLEFT", 20, 20); btnSave:SetText("Save")
    if NS.CreateBg then NS.CreateBg(btnSave, 0, 0, 0, 1) end
    if NS.CreateBorder then NS.CreateBorder(btnSave, 1, 1, 1, 1) end
    local fsS = btnSave:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); fsS:SetPoint("CENTER"); fsS:SetText("Save"); btnSave:SetFontString(fsS)
    
    btnSave:SetScript("OnClick", function()
        local key = f.targetKey
        if key then
            GRS_SocialDB[key] = {
                raceSex = f.raceSexDD:GetText(),
                joinMonth = f.joinMonthDD:GetText(), joinDay = f.joinDayDD:GetText(), joinYear = f.joinYearDD:GetText(),
                promMonth = f.promMonthDD:GetText(), promDay = f.promDayDD:GetText(), promYear = f.promYearDD:GetText(),
                birthMonth = f.birthMonthDD:GetText(), birthDay = f.birthDayDD:GetText(), birthYear = f.birthYearDD:GetText(),
                pubNote = f.pubNote:GetText(), offNote = f.offNote:GetText(), customNote = f.customNote:GetText()
            }
            GR:ShowToast("Social data saved.")
            f:Hide()
        end
    end)

    local btnCancel = CreateFrame("Button", nil, f)
    btnCancel:SetSize(120, 30); btnCancel:SetPoint("BOTTOMRIGHT", -20, 20); btnCancel:SetText("Cancel")
    if NS.CreateBg then NS.CreateBg(btnCancel, 0, 0, 0, 1) end
    if NS.CreateBorder then NS.CreateBorder(btnCancel, 1, 1, 1, 1) end
    local fsC = btnCancel:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); fsC:SetPoint("CENTER"); fsC:SetText("Cancel"); btnCancel:SetFontString(fsC)
    btnCancel:SetScript("OnClick", function() f:Hide() end)

    self.SocialPopup = f
end

function GR:ShowSocialTooltip(data)
    if not self.db.socialEnabled then return end
    if not self.SocialTooltip then self:CreateSocialTooltip() end
    local t = self.SocialTooltip
    local s = GRS_SocialDB[data.dbKey] or {}

    t.name:SetText(data.name)
    
    local factionDisplay = data.faction
    if (not factionDisplay or factionDisplay == "Neutral") then
        local db = GR:GetDB()
        if db.factions and db.factions[data.dbKey] then
             factionDisplay = db.factions[data.dbKey]
        end
    end
    t.faction:SetText("Faction: " .. (factionDisplay or "Unknown"))
    
    t.class:SetText("Class: " .. (data.class or "Unknown"))
    
    local rDisp = s.raceSex
    if not rDisp or rDisp == "" or rDisp == "N/A" then rDisp = data.race end
    t.raceSex:SetText("Race/Sex: " .. (rDisp or "N/A"))
    
    local function IsAnniversary(mStr, dStr)
        if not mStr or not dStr then return false end
        local monthIdx = 0; for i,v in ipairs(MONTHS) do if v == mStr then monthIdx = i break end end
        local today = date("*t")
        local target = {month = monthIdx, day = tonumber(dStr), year = today.year}
        local targetTime = time(target); local nowTime = time(today)
        local diff = math.abs(difftime(targetTime, nowTime)) / 86400
        return diff <= 10
    end

    local isJoinAnni = IsAnniversary(s.joinMonth, s.joinDay)
    local jYear = (s.joinYear and s.joinYear ~= "") and (" " .. s.joinYear) or ""
    t.join:SetText("Join Date: " .. (s.joinMonth or "N/A") .. " " .. (s.joinDay or "") .. jYear)
    t.join.isAnniversary = isJoinAnni

    local pYear = (s.promYear and s.promYear ~= "") and (" " .. s.promYear) or ""
    t.prom:SetText("Promotion: " .. (s.promMonth or "N/A") .. " " .. (s.promDay or "") .. pYear)
    local bYear = (s.birthYear and s.birthYear ~= "") and (" " .. s.birthYear) or ""
    t.birth:SetText("Birthday: " .. (s.birthMonth or "N/A") .. " " .. (s.birthDay or "") .. bYear)
    t.pub:SetText("Public Note: " .. (s.pubNote or "N/A"))
    t.off:SetText("Officer Note: " .. (s.offNote or "N/A"))
    t.custom:SetText("Custom Note: " .. (s.customNote or "N/A"))

    t:ClearAllPoints()
    t:SetPoint("TOPRIGHT", GR, "TOPLEFT", -10, 0)
    t:Show()
end

function GR:CreateSocialTooltip()
    local t = CreateFrame("Frame", "GRS_SocialTooltip", UIParent, "BackdropTemplate")
    t:SetSize(250, 320); t:SetFrameStrata("TOOLTIP")
    if NS.CreateBg then NS.CreateBg(t, 0, 0, 0, 0.9) end
    if NS.CreateBorder then NS.CreateBorder(t, 0.5, 0.5, 0.5, 1) end

    t.name = t:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge"); t.name:SetPoint("TOPLEFT", 15, -15)
    t.faction = t:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); t.faction:SetPoint("TOPLEFT", 15, -45)
    t.class = t:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); t.class:SetPoint("TOPLEFT", 15, -60)
    t.raceSex = t:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); t.raceSex:SetPoint("TOPLEFT", 15, -75)
    t.join = t:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); t.join:SetPoint("TOPLEFT", 15, -95)
    t.prom = t:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); t.prom:SetPoint("TOPLEFT", 15, -110)
    t.birth = t:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); t.birth:SetPoint("TOPLEFT", 15, -125)
    t.pub = t:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); t.pub:SetPoint("TOPLEFT", 15, -145); t.pub:SetWidth(220); t.pub:SetJustifyH("LEFT")
    t.off = t:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); t.off:SetPoint("TOPLEFT", 15, -175); t.off:SetWidth(220); t.off:SetJustifyH("LEFT")
    t.custom = t:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); t.custom:SetPoint("TOPLEFT", 15, -205); t.custom:SetWidth(220); t.custom:SetJustifyH("LEFT")

    t:SetScript("OnUpdate", function(self, elapsed)
        if self.join.isAnniversary then
            local cycle = (GetTime() % 2) / 2
            if cycle < 0.5 then self.join:SetTextColor(0, 1, 0) 
            else self.join:SetTextColor(1, 0.82, 0) end 
        else self.join:SetTextColor(1, 1, 1) end
    end)

    self.SocialTooltip = t
end

-- =========================================================================
-- 4. INSPECTION & GUILD CHAT
-- =========================================================================

function GR:SelectMember(data)
    GR.SelectedMember = data
    if not GR.DetailContent then return end
    local namePart, realmPart = strsplit("-", data.fullName or "")
    if not realmPart or realmPart == "" then realmPart = GetRealmName() end
    GR.DetailName:SetText(namePart .. " - " .. (realmPart or ""))
    GR.DetailRank:SetText("Rank: " .. (data.rank or "Unknown"))
    local cColor = C_ClassColor.GetClassColor((data.class or "PRIEST"):upper()) or GRAY_FONT_COLOR
    GR.DetailClass:SetText(cColor:WrapTextInColorCode(data.class) .. " " .. (data.race or ""))
    
    local myName, myRealm = UnitFullName("player"); local myCanonical = myName .. "-" .. (myRealm or "")
    local isMe = (data.dbKey == myCanonical); local isOfficer = GR:IsOfficer() or GR.devOfficerMode
    
    -- AXIOM-exclusive notes: read from GRS_SocialDB first, fall back to WoW API data
    local axiomPub, axiomOff = "", ""
    if data.dbKey and GRS_SocialDB[data.dbKey] then
        axiomPub = GRS_SocialDB[data.dbKey].pubNote or ""
        axiomOff = GRS_SocialDB[data.dbKey].offNote or ""
    end
    if axiomPub == "" then axiomPub = data.publicNote or "" end
    if axiomOff == "" then axiomOff = data.officerNote or "" end
    GR.PublicNoteEdit:SetText(axiomPub); GR.OfficerNoteEdit:SetText(axiomOff)
    if isMe or isOfficer then GR.PublicNoteEdit:Enable() else GR.PublicNoteEdit:Disable() end
    if isOfficer then GR.OfficerNoteEdit:Enable(); GR.OfficerNoteEdit:Show(); GR.DetailSocialBtn:Show() else GR.OfficerNoteEdit:Disable(); GR.OfficerNoteEdit:Hide(); GR.DetailSocialBtn:Hide() end
    
    if not GR.db.socialEnabled then GR.DetailSocialBtn:Hide() end

    GR.DetailContent:Show()
end

function GR:DeselectMember()
    GR.SelectedMember = nil
    if GR.DetailContent then GR.DetailContent:Hide() end
end

function GR:InitGuildChat(parent)
    local gc = CreateFrame("ScrollingMessageFrame", nil, parent)
    gc:SetPoint("TOPLEFT", 5, -5); gc:SetPoint("BOTTOMRIGHT", -25, 25)
    gc:SetFontObject("ChatFontNormal"); gc:SetJustifyH("LEFT"); gc:SetFading(false); gc:SetMaxLines(GR.MAX_HISTORY_LINES)
    gc:SetHyperlinksEnabled(true)
    gc:SetScript("OnHyperlinkClick", function(_, link) SetItemRef(link, link, "LeftButton") end)
    GR.GuildChatFrame = gc
    
    local sb = GR:CreateRobustVerticalScrollbar(parent, gc)
    sb:SetPoint("TOPRIGHT", -2, -2); sb:SetPoint("BOTTOMRIGHT", -2, 28)

    local eb = CreateFrame("EditBox", nil, parent, "InputBoxTemplate")
    eb:SetHeight(20); eb:SetPoint("BOTTOMLEFT", 5, 5); eb:SetPoint("BOTTOMRIGHT", -5, 5); eb:SetAutoFocus(false)
    eb:SetScript("OnEnterPressed", function(self)
        local msg = self:GetText()
        if msg and msg ~= "" then
            if IsInGuild() then SendChatMessage(msg, "GUILD") end
            self:SetText("")
        end
        self:ClearFocus()
    end)
    GR.GuildChatEditBox = eb

    if not GR.chatHooked then
        ChatFrame_AddMessageEventFilter("CHAT_MSG_GUILD", function(self, event, msg, sender, ...)
            if GR.GuildChatFrame then
                local _, chatDB = GR:GetDB()
                local name = Ambiguate(sender, "none")
                local timestamp = date("%H:%M")
                local r, g, b = 0.25, 1, 0.25 
                GR.GuildChatFrame:AddMessage("|cff888888["..timestamp.."]|r |cff00ff00["..name.."]:|r " .. msg, r, g, b)
                if chatDB then
                    table.insert(chatDB, {ts=time(), sender=name, msg=msg})
                    if #chatDB > GR.MAX_HISTORY_LINES then table.remove(chatDB, 1) end
                end
            end
            return false
        end)
        ChatFrame_AddMessageEventFilter("CHAT_MSG_GUILD_ACHIEVEMENT", function(self, event, msg, sender, ...)
            if GR.GuildChatFrame then GR.GuildChatFrame:AddMessage("|cffffff00" .. msg) end
            return false
        end)
        GR.chatHooked = true
    end
    GR:LoadChatHistory()
end

function GR:LoadChatHistory()
    local _, chatDB = GR:GetDB()
    if not GR.GuildChatFrame or not chatDB then return end
    GR.GuildChatFrame:Clear()
    for _, entry in ipairs(chatDB) do
        local d = date("*t", entry.ts); local tsStr = string.format("%02d:%02d", d.hour, d.min)
        GR.GuildChatFrame:AddMessage("|cff888888["..tsStr.."]|r |cff00ff00["..entry.sender.."]:|r " .. entry.msg)
    end
end

-- =========================================================================
-- 5. UNIFIED INSPECTION ROUTER
-- =========================================================================

function GR:OpenInspectWindow(dbKey)
    if not dbKey then return end
    
    -- [REVISED] SELF-INSPECT DETECTION
    local myName, myRealm = UnitFullName("player")
    if not myRealm or myRealm == "" then myRealm = GetRealmName() end
    local myCanonical = myName .. "-" .. myRealm

    if dbKey == myCanonical then
        -- ToggleCharacter triggers the PaperDollFrame logic, which AXIOM hooks.
        if _G.AXIOM_Armory_Frame_Reboot then
             -- [PATCHED] Always Toggle. Remove the IsShown() check to prevent "stuck" states.
             -- If user clicks it while open, it will close (standard toggling).
             ToggleCharacter("PaperDollFrame") 
        else
             -- Safety Fallback
             ToggleCharacter("PaperDollFrame") 
        end
        return
    end

    -- CASE 2: ROSTER/TARGET INSPECT (Trigger DB Snapshot)
    local data = GRS_InspectionDB[dbKey]
    if not data then 
        GR:ShowToast("No inspection data available for " .. Ambiguate(dbKey, "none"), true)
        return 
    end

    -- RETRIEVE META DATA (RACE/SEX/GUID) FOR MANNEQUIN
    local meta = { race = nil, sex = nil, guid = nil }
    
    -- Store GUID for unit-based model loading
    if GR.guids and GR.guids[dbKey] then meta.guid = GR.guids[dbKey] end
    if not meta.guid then
        local db = GR:GetDB()
        if db and db.guids then meta.guid = db.guids[dbKey] end
    end
    
    -- PRIORITY 1: Use snapshot appearance data (authoritative, sent by remote player's ScanSelf)
    if data.appearance then
        local appRaceID = nil
        local appSexID = nil
        if type(data.appearance) == "table" then
            appRaceID = data.appearance.raceID
            appSexID = data.appearance.sexID
        elseif type(data.appearance) == "string" then
            local r, s = strsplit(":", data.appearance)
            appRaceID = tonumber(r)
            appSexID = tonumber(s)
        end
        if appRaceID and appRaceID > 0 then
            -- Reverse-lookup race name from ID for snapshotMeta compatibility
            local raceInfo = C_CreatureInfo.GetRaceInfo(appRaceID)
            if raceInfo and raceInfo.clientFileString then meta.race = raceInfo.clientFileString end
            meta.sex = appSexID
        end
    end

    -- PRIORITY 2: GUID lookup (fallback for snapshots without appearance data)
    if not meta.race or not meta.sex then
        local guid = nil
        if GR.guids and GR.guids[dbKey] then guid = GR.guids[dbKey] end 
        if not guid then
             local db = GR:GetDB()
             if db.guids then guid = db.guids[dbKey] end
        end

        if guid then
             local _, _, _, englishRace, sex = GetPlayerInfoByGUID(guid)
             if not meta.race and englishRace then meta.race = englishRace end
             if not meta.sex and sex then meta.sex = sex end
        end
    end
    
    -- PRIORITY 3: SocialDB (last resort)
    if (not meta.race or not meta.sex) and GRS_SocialDB and GRS_SocialDB[dbKey] then
         if not meta.race then meta.race = GRS_SocialDB[dbKey].raceSex end 
         if GRS_SocialDB[dbKey].raceSex then
              local sVal = GRS_SocialDB[dbKey].raceSex
              if sVal == "Male" or sVal == "Female" then meta.sex = sVal end
         end
    end

    if _G.AXIOM_Target_Inspect_Frame then
        if _G.AXIOM_Target_Inspect_Frame.SetSnapshot then
            _G.AXIOM_Target_Inspect_Frame:SetSnapshot(data, dbKey, meta)
        else
            GR:ShowToast("Inspector version outdated.", true)
        end
    else
        GR:ShowToast("Target Inspector not loaded.", true)
    end
end