local _, addon = ...
local HLY = addon.HLY

local ICON_PATH = "Interface\\AddOns\\HLYqol\\Assets\\baguette.tga"
local SOUND_PATH = "Interface\\AddOns\\HLYqol\\Assets\\ohoho.mp3"
local DH_SOUND_PATH = "Interface\\AddOns\\HLYqol\\Assets\\achtung-dh.ogg"
local DH_CLASS = "DEMONHUNTER"
local WHITE_TEXTURE = "Interface\\Buttons\\WHITE8X8"
local FLAG_BLUE = { 0.0, 0.22, 0.65, 0.24 }
local FLAG_WHITE = { 1.0, 1.0, 1.0, 0.16 }
local FLAG_RED = { 0.9, 0.16, 0.22, 0.24 }

local knownApplicants = {}
local knownDHApplicants = {}

local function SafeCall(func, ...)
    if not func then
        return nil
    end

    local ok, result = pcall(func, ...)
    if ok then
        return result
    end

    return nil
end

local function SafeNumber(value)
    local ok, numberValue = pcall(tonumber, value)
    if ok and numberValue then
        local usable = pcall(function()
            return numberValue >= 0 or numberValue < 0
        end)
        if usable then
            return numberValue
        end
    end

    return 0
end

local function UTF8(...)
    return string.char(...)
end

local ACCENT_REPLACEMENTS = {
    [UTF8(195, 160)] = "a",
    [UTF8(195, 162)] = "a",
    [UTF8(195, 164)] = "a",
    [UTF8(195, 161)] = "a",
    [UTF8(195, 167)] = "c",
    [UTF8(195, 169)] = "e",
    [UTF8(195, 168)] = "e",
    [UTF8(195, 170)] = "e",
    [UTF8(195, 171)] = "e",
    [UTF8(195, 174)] = "i",
    [UTF8(195, 175)] = "i",
    [UTF8(195, 172)] = "i",
    [UTF8(195, 173)] = "i",
    [UTF8(195, 180)] = "o",
    [UTF8(195, 182)] = "o",
    [UTF8(195, 178)] = "o",
    [UTF8(195, 179)] = "o",
    [UTF8(195, 185)] = "u",
    [UTF8(195, 187)] = "u",
    [UTF8(195, 188)] = "u",
    [UTF8(195, 186)] = "u",
    [UTF8(195, 191)] = "y",
    [UTF8(197, 147)] = "oe",
    [UTF8(195, 166)] = "ae",
}

local FRENCH_REALMS = {
    arakarahm = true,
    archimonde = true,
    chantseternels = true,
    chogall = true,
    confrerieduthorium = true,
    conseildesombres = true,
    cultedelarivenoire = true,
    dalaran = true,
    drekthar = true,
    eldrethalas = true,
    eitrigg = true,
    elune = true,
    garona = true,
    hyjal = true,
    illidan = true,
    kaelthas = true,
    khazmodan = true,
    kirintor = true,
    krasus = true,
    lacroisadeecarlate = true,
    lesclairvoyants = true,
    lessentinelles = true,
    marecagedezangar = true,
    medivh = true,
    naxxramas = true,
    nerzhul = true,
    rashgarroth = true,
    sargeras = true,
    sinstralis = true,
    suramar = true,
    templenoir = true,
    throkferoth = true,
    uldaman = true,
    varimathras = true,
    voljin = true,
    ysondre = true,
}

local function IsEnabled()
    return HLY.db and HLY.db.frenchConnection and HLY.db.frenchConnection.enabled
end

local function IsDHAlertEnabled()
    return HLY.db and HLY.db.frenchConnection and HLY.db.frenchConnection.demonHunter and HLY.db.frenchConnection.demonHunter.enabled
end

local function GetApplicantMemberClassFile(applicantID, memberIdx)
    if not C_LFGList or not C_LFGList.GetApplicantMemberInfo then
        return nil
    end
    local ok, _, classFile = pcall(C_LFGList.GetApplicantMemberInfo, applicantID, memberIdx)
    if not ok then
        return nil
    end
    return classFile
end

local function ApplicantHasDemonHunter(applicantID)
    local info = SafeCall(C_LFGList.GetApplicantInfo, applicantID)
    if not info or not info.numMembers then
        return false
    end
    for memberIdx = 1, SafeNumber(info.numMembers) do
        if GetApplicantMemberClassFile(applicantID, memberIdx) == DH_CLASS then
            return true
        end
    end
    return false
end


local function IsGroupFinderLoaded()
    if C_AddOns and C_AddOns.IsAddOnLoaded then
        return C_AddOns.IsAddOnLoaded("Blizzard_GroupFinder")
    end

    if IsAddOnLoaded then
        return IsAddOnLoaded("Blizzard_GroupFinder")
    end

    return LFGListFrame ~= nil
end

local function NormalizeRealmName(realmName)
    if type(realmName) ~= "string" or realmName == "" then
        return nil
    end

    local normalized = realmName:lower()
    for accented, replacement in pairs(ACCENT_REPLACEMENTS) do
        normalized = normalized:gsub(accented, replacement)
    end

    return normalized:gsub("[%s%-'%._]", "")
end

local function ExtractRealmFromFullName(fullName)
    if type(fullName) ~= "string" or fullName == "" then
        return nil
    end

    fullName = fullName:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
    local realmName = fullName:match("^[^-]+%-(.+)$")
    if not realmName or realmName == "" then
        return nil
    end

    return realmName
end

local function IsFrenchRealmPlayer(fullName)
    local ok, result = pcall(function()
        local realmName = ExtractRealmFromFullName(fullName)
        local normalizedRealm = NormalizeRealmName(realmName)
        return normalizedRealm and FRENCH_REALMS[normalizedRealm] or false
    end)

    return ok and result or false
end

local function EnsureMarkerTexture(parent, key, layer, subLevel, color)
    if not parent then
        return nil
    end

    local texture = parent[key]
    if not texture then
        texture = parent:CreateTexture(nil, layer or "BACKGROUND", nil, subLevel or 0)
        texture:SetTexture(WHITE_TEXTURE)
        parent[key] = texture
    end

    texture:SetColorTexture(color[1], color[2], color[3], color[4])
    return texture
end

local function EnsureIconTexture(parent, key, width, height)
    if not parent then
        return nil
    end

    local texture = parent[key]
    if not texture then
        texture = parent:CreateTexture(nil, "ARTWORK", nil, 5)
        texture:SetTexture(ICON_PATH)
        texture:SetBlendMode("BLEND")
        parent[key] = texture
    end

    texture:SetSize(width, height)
    return texture
end

local function LayoutApplicantFlag(member, blue, white, red)
    -- Span the full applicant row width: anchor horizontally to the parent row
    -- (member is only the name cell in Midnight), keep vertical bound to member.
    local row = member:GetParent() or member

    local dxLeft = 0
    local dxRight = 0
    local rowLeft, memberLeft = row:GetLeft(), member:GetLeft()
    if rowLeft and memberLeft then
        dxLeft = rowLeft - memberLeft
    end
    local rowRight, memberRight = row:GetRight(), member:GetRight()
    if rowRight and memberRight then
        dxRight = rowRight - memberRight
    end

    local totalWidth = math.max(1, math.floor((row:GetWidth() or member:GetWidth()) - 4))
    local firstThird = math.floor(totalWidth / 3)
    local secondThird = math.floor((totalWidth * 2) / 3)

    blue:ClearAllPoints()
    blue:SetPoint("TOPLEFT", member, "TOPLEFT", dxLeft + 2, 1)
    blue:SetPoint("BOTTOMLEFT", member, "BOTTOMLEFT", dxLeft + 2, -1)
    blue:SetWidth(firstThird)

    white:ClearAllPoints()
    white:SetPoint("TOPLEFT", blue, "TOPRIGHT", 0, 0)
    white:SetPoint("BOTTOMLEFT", blue, "BOTTOMRIGHT", 0, 0)
    white:SetWidth(secondThird - firstThird)

    red:ClearAllPoints()
    red:SetPoint("TOPLEFT", white, "TOPRIGHT", 0, 0)
    red:SetPoint("BOTTOMLEFT", white, "BOTTOMRIGHT", 0, 0)
    red:SetPoint("TOPRIGHT", member, "TOPRIGHT", dxRight - 2, 1)
    red:SetPoint("BOTTOMRIGHT", member, "BOTTOMRIGHT", dxRight - 2, -1)
end

local function UpdateApplicantDecor(member, isFrench)
    if not member then
        return
    end

    local blue = EnsureMarkerTexture(member, "__HLYFrenchBlue", "BACKGROUND", -7, FLAG_BLUE)
    local white = EnsureMarkerTexture(member, "__HLYFrenchWhite", "BACKGROUND", -7, FLAG_WHITE)
    local red = EnsureMarkerTexture(member, "__HLYFrenchRed", "BACKGROUND", -7, FLAG_RED)
    local icon = EnsureIconTexture(member, "__HLYFrenchIcon", 20, 11)
    local showBaguette = HLY.db.frenchConnection.showBaguette

    LayoutApplicantFlag(member, blue, white, red)

    -- Shift the name horizontally by `shift` from its original (Blizzard) layout.
    -- Snapshot the original points once so toggling restores cleanly and repeated
    -- refreshes don't drift the name.
    local function applyNameShift(shift)
        if not member.Name then return end
        if not member.__HLYNameOrig then
            local pts = {}
            for i = 1, (member.Name:GetNumPoints() or 0) do
                pts[i] = { member.Name:GetPoint(i) }
            end
            member.__HLYNameOrig = pts
        end
        member.Name:ClearAllPoints()
        for _, p in ipairs(member.__HLYNameOrig) do
            member.Name:SetPoint(p[1], p[2], p[3], (p[4] or 0) + shift, p[5] or 0)
        end
    end

    if isFrench and IsEnabled() then
        if showBaguette and icon then
            icon:SetSize(16, 9)
            applyNameShift(22)
            -- Anchor LEFT-to-LEFT so the baguette is vertically centered on the
            -- whole member row, not on the (top-aligned) name text.
            icon:ClearAllPoints()
            icon:SetPoint("LEFT", member, "LEFT", 6, 0)
            icon:Show()
        else
            applyNameShift(0)
            if icon then icon:Hide() end
        end

        blue:Show()
        white:Show()
        red:Show()
    else
        applyNameShift(0)
        if icon then
            icon:Hide()
        end
        blue:Hide()
        white:Hide()
        red:Hide()
    end
end

local function UpdateSearchEntryDecor(entry, isFrench)
    if not entry then
        return
    end

    local blue = EnsureMarkerTexture(entry, "__HLYFrenchBlue", "ARTWORK", -1, FLAG_BLUE)
    local white = EnsureMarkerTexture(entry, "__HLYFrenchWhite", "ARTWORK", -1, FLAG_WHITE)
    local red = EnsureMarkerTexture(entry, "__HLYFrenchRed", "ARTWORK", -1, FLAG_RED)
    local icon = EnsureIconTexture(entry, "__HLYFrenchIcon", 24, 13)
    local showBaguette = HLY.db.frenchConnection.showBaguette

    blue:ClearAllPoints()
    blue:SetPoint("TOPLEFT", entry, "TOPLEFT", 6, -1)
    blue:SetPoint("BOTTOMLEFT", entry, "BOTTOMLEFT", 6, -2)
    blue:SetPoint("RIGHT", entry, "LEFT", 106, 0)

    white:ClearAllPoints()
    white:SetPoint("TOPLEFT", blue, "TOPRIGHT", 0, 0)
    white:SetPoint("BOTTOMLEFT", blue, "BOTTOMRIGHT", 0, 0)
    white:SetPoint("RIGHT", entry, "LEFT", 207, 0)

    red:ClearAllPoints()
    red:SetPoint("TOPLEFT", white, "TOPRIGHT", 0, 0)
    red:SetPoint("BOTTOMLEFT", white, "BOTTOMRIGHT", 0, 0)
    red:SetPoint("TOPRIGHT", entry, "TOPRIGHT", -2, -1)
    red:SetPoint("BOTTOMRIGHT", entry, "BOTTOMRIGHT", -2, -2)

    if isFrench and IsEnabled() then
        if showBaguette and icon then
            entry.Name:ClearAllPoints()
            entry.Name:SetPoint("TOPLEFT", entry, "TOPLEFT", 38, -6)
            icon:ClearAllPoints()
            icon:SetPoint("LEFT", entry, "LEFT", 10, 0)
            icon:Show()
        else
            if icon then
                icon:Hide()
            end
            entry.Name:ClearAllPoints()
            entry.Name:SetPoint("TOPLEFT", entry, "TOPLEFT", 10, -6)
        end

        blue:Show()
        white:Show()
        red:Show()
    else
        if icon then
            icon:Hide()
        end
        entry.Name:ClearAllPoints()
        entry.Name:SetPoint("TOPLEFT", entry, "TOPLEFT", 10, -6)
        blue:Hide()
        white:Hide()
        red:Hide()
    end
end

local function ApplicantHasFrenchMember(applicantID)
    local info = SafeCall(C_LFGList.GetApplicantInfo, applicantID)
    if not info or not info.numMembers then
        return false
    end

    for memberIdx = 1, SafeNumber(info.numMembers) do
        local fullName = SafeCall(C_LFGList.GetApplicantMemberInfo, applicantID, memberIdx)
        if IsFrenchRealmPlayer(fullName) then
            return true
        end
    end

    return false
end

function HLY:UpdateFrenchApplicantSounds()
    local frenchSoundEnabled = IsEnabled() and self.db.frenchConnection.playSound
    local dhEnabled = IsDHAlertEnabled()

    if (not frenchSoundEnabled and not dhEnabled) or not C_LFGList or not C_LFGList.GetApplicants then
        return
    end

    local currentApplicants = {}
    local applicants = SafeCall(C_LFGList.GetApplicants) or {}

    for _, applicantID in ipairs(applicants) do
        currentApplicants[applicantID] = true

        if not knownApplicants[applicantID] then
            knownApplicants[applicantID] = true
            if frenchSoundEnabled and ApplicantHasFrenchMember(applicantID) then
                PlaySoundFile(SOUND_PATH, "Master")
            end
            if dhEnabled and not knownDHApplicants[applicantID] and ApplicantHasDemonHunter(applicantID) then
                knownDHApplicants[applicantID] = true
                PlaySoundFile(DH_SOUND_PATH, "Master")
            end
        end
    end

    for applicantID in pairs(knownApplicants) do
        if not currentApplicants[applicantID] then
            knownApplicants[applicantID] = nil
            knownDHApplicants[applicantID] = nil
        end
    end
end


function HLY:RefreshFrenchConnection()
    if not IsGroupFinderLoaded() then
        return
    end

    if LFGListFrame and LFGListFrame.ApplicationViewer and LFGListFrame.ApplicationViewer.ScrollBox then
        local scrollTarget = LFGListFrame.ApplicationViewer.ScrollBox.ScrollTarget
        if scrollTarget and scrollTarget.GetChildren then
            for _, child in ipairs({ scrollTarget:GetChildren() }) do
                if child and child.applicantID and child.Members then
                    for _, member in ipairs(child.Members) do
                        if member and member.memberIdx then
                            local fullName = SafeCall(C_LFGList.GetApplicantMemberInfo, child.applicantID, member.memberIdx)
                            UpdateApplicantDecor(member, IsFrenchRealmPlayer(fullName))
                        end
                    end
                end
            end
        end
    end

    if LFGListFrame and LFGListFrame.SearchPanel and LFGListFrame.SearchPanel.ScrollBox then
        local scrollTarget = LFGListFrame.SearchPanel.ScrollBox.ScrollTarget
        if scrollTarget and scrollTarget.GetChildren then
            for _, child in ipairs({ scrollTarget:GetChildren() }) do
                if child and child.resultID and SafeCall(C_LFGList.HasSearchResultInfo, child.resultID) then
                    local searchResultInfo = SafeCall(C_LFGList.GetSearchResultInfo, child.resultID)
                    UpdateSearchEntryDecor(child, searchResultInfo and IsFrenchRealmPlayer(searchResultInfo.leaderName))
                end
            end
        end
    end
end

function HLY:InstallFrenchConnectionHooks()
    if self.frenchConnectionHooksInstalled or not IsGroupFinderLoaded() then
        return
    end

    hooksecurefunc("LFGListApplicationViewer_UpdateApplicantMember", function(member, applicantID, memberIdx)
        local fullName = SafeCall(C_LFGList.GetApplicantMemberInfo, applicantID, memberIdx)
        UpdateApplicantDecor(member, IsFrenchRealmPlayer(fullName))
    end)

    hooksecurefunc("LFGListSearchEntry_Update", function(entry)
        if not entry or not entry.resultID or not SafeCall(C_LFGList.HasSearchResultInfo, entry.resultID) then
            return
        end

        local searchResultInfo = SafeCall(C_LFGList.GetSearchResultInfo, entry.resultID)
        UpdateSearchEntryDecor(entry, searchResultInfo and IsFrenchRealmPlayer(searchResultInfo.leaderName))
    end)

    self.frenchConnectionHooksInstalled = true
    self:RefreshFrenchConnection()
end

function HLY:HandleFrenchConnectionAddonLoaded(_, loadedAddon)
    if loadedAddon == "Blizzard_GroupFinder" then
        self:InstallFrenchConnectionHooks()
    end
end

function HLY:SetupFrenchConnection()
    if self.frenchConnectionSetup then
        self:InstallFrenchConnectionHooks()
        return
    end

    self:RegisterEvent("ADDON_LOADED", "HandleFrenchConnectionAddonLoaded")
    self:RegisterEvent("LFG_LIST_APPLICANT_LIST_UPDATED", "UpdateFrenchApplicantSounds")
    self:InstallFrenchConnectionHooks()
    self.frenchConnectionSetup = true
end

-- LibHLYUI options panel (v2) -----------------------------------------------
function HLY:BuildFrenchConnectionOptions(tab)
    tab:TabGroup({
        {
            name = "Baguette Detector",
            build = function(p)
                p:Note("Erkennt Spieler von französischen Realms im Gruppenfinder und markiert sie mit einer Tricolore. Optional ein Baguette-Icon und ein Sound bei jeder Bewerbung.")
                p:Row({
                    function(c) c:Checkbox({
                        label = "Enable",
                        get = function() return self.db.frenchConnection.enabled end,
                        set = function(v)
                            self.db.frenchConnection.enabled = v
                            self:RefreshFrenchConnection()
                        end,
                    }) end,
                    function(c) c:Checkbox({
                        label = "Sound",
                        get = function() return self.db.frenchConnection.playSound end,
                        set = function(v) self.db.frenchConnection.playSound = v end,
                    }) end,
                    function(c) c:Checkbox({
                        label = "Icon (Baguette)",
                        get = function() return self.db.frenchConnection.showBaguette end,
                        set = function(v)
                            self.db.frenchConnection.showBaguette = v
                            self:RefreshFrenchConnection()
                        end,
                    }) end,
                })
            end,
        },
        {
            name = "Achtung! Demon Hunter!",
            build = function(p)
                p:Note("Spielt einen Sound ab, sobald sich ein Demon Hunter im Gruppenfinder bewirbt. Unabhängig von der French Connection schaltbar.")
                p:Checkbox({
                    label = "Enable",
                    get = function() return self.db.frenchConnection.demonHunter.enabled end,
                    set = function(v) self.db.frenchConnection.demonHunter.enabled = v end,
                })
            end,
        },
    })
end

addon.options = addon.options or {}
addon.options[#addon.options + 1] = {
    group = "Group Finder", groupOrder = 250,
    name = "Other", order = 99, ownTabs = true,
    build = "BuildFrenchConnectionOptions",
}
