-- PugaHelper by Vaelhys
-- Affiche les symboles L'Ura en temps réel pour tout le raid
-- Macros à créer manuellement ou avec /pugamacro : PugaTriangle → /yell . + /ping -- PugaDiamond  → /rw .  + /ping -- PugaCircle   → /ping -- PugaX        → /rw . -- PugaT        → /yell .
-- /puga pour afficher ou cacher la fenêtre
local addonName = ...
-- chemins vers les images des symboles
local symbolTextures = {
    TRI = "Interface\\AddOns\\PugaHelper\\media\\triangle.png",
    TEE = "Interface\\AddOns\\PugaHelper\\media\\t.png",
    DIA = "Interface\\AddOns\\PugaHelper\\media\\diamond.png",
    X   = "Interface\\AddOns\\PugaHelper\\media\\x.png",
    CIR = "Interface\\AddOns\\PugaHelper\\media\\circle.png",
}

local symbolColors = {
    TRI = { 0.4,  1.0,  0.5  },
    TEE = { 0.98, 0.98, 0.99 },
    DIA = { 0.48, 0.20, 0.96 },
    X   = { 1.0,  0.3,  0.3  },
    CIR = { 1.0,  0.64, 0.01 },
}

-- positions des slots selon le mode -- heroic : 5 slots tout autour -- normal : 3 slots en bas (positions 2, 3, 4)
local slotPositionsHeroic = {
    { x =  55, y =  44 },
    { x =  55, y = -22 },
    { x =   0, y = -60 },
    { x = -55, y = -22 },
    { x = -55, y =  44 },
}

local slotPositionsNormal = {
    { x =  55, y = -22 },
    { x =   0, y = -60 },
    { x = -55, y = -22 },
}

-- état du mode
local isHeroic       = true
local maxSymbols     = 5

local currentSequence = {}
local displaySlots    = {}
local mainFrame       = nil
local needsUpdate     = false

-- gestion du timing de l'encounter -- on utilise ENCOUNTER_START/END pour ne pas être affecté par les vanish/feign death
local encounterTimers = {}  -- liste des timers actifs pour pouvoir les annuler
local inputsOpen      = false  -- est-ce qu'on accepte des inputs en ce moment

-- les 3 fenêtres de saisie (en secondes depuis le début du combat)
-- chaque fenêtre : ouverture, fermeture des inputs, reset, son
local WINDOWS = {
    { open = 6,    close = 24,   reset = 32,   sound = 36  },
    { open = 76,   close = 94,   reset = 102,  sound = 106 },
    { open = 146,  close = 164,  reset = 172,  sound = 176 },
}

local SOUND_FILE = "Interface/AddOns/PugaHelper/media/Dodge glaives.mp3"
local function playDodgeSound()
    PlaySoundFile(SOUND_FILE, "Master")
end

local function cancelAllTimers()
    for _, t in ipairs(encounterTimers) do
        if t then pcall(function() t:Cancel() end) end
    end
    wipe(encounterTimers)
end

local function hideAndReset()
    inputsOpen = false
    wipe(currentSequence)
    needsUpdate = true
    -- cache le frame principal
    if mainFrame then
        pcall(function() mainFrame:Hide() end)
    end
end

local function lockInputs()
    inputsOpen = false
    -- on laisse le diagramme visible, on bloque juste les inputs
    -- les macros vérifieront inputsOpen avant d'ajouter un symbole
end

local function openWindow()
    inputsOpen = true
    -- montre le diagramme
    if mainFrame then
        pcall(function() mainFrame:Show() end)
    end
end

local function startEncounterTimers()
    cancelAllTimers()
    -- reset au début du combat
    wipe(currentSequence)
    inputsOpen = false
    needsUpdate = true
    if mainFrame then pcall(function() mainFrame:Hide() end) end

    -- programme les 3 fenêtres
    for _, w in ipairs(WINDOWS) do
        local tOpen  = C_Timer.NewTimer(w.open,  openWindow)
        local tClose = C_Timer.NewTimer(w.close, lockInputs)
        local tReset = C_Timer.NewTimer(w.reset, hideAndReset)
        local tSound = C_Timer.NewTimer(w.sound, playDodgeSound)
        table.insert(encounterTimers, tOpen)
        table.insert(encounterTimers, tClose)
        table.insert(encounterTimers, tReset)
        table.insert(encounterTimers, tSound)
    end
end

local function stopEncounter()
    cancelAllTimers()
    hideAndReset()
end

local watchedEvents = {
    CHAT_MSG_YELL         = "YELL",
    CHAT_MSG_RAID_WARNING = "RW",
    CHAT_MSG_PING         = "PING",
}

local eventBuffer     = {}
local lastSymbolTime  = 0
local bufferTimer     = nil
local COMBO_WINDOW    = 0.5
local SYMBOL_COOLDOWN = 1.0

local slotSize    = 38
local slotBackdrop = {
    bgFile   = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Buttons\\WHITE8x8",
    edgeSize = 1,
}

-- applique la séquence sur les slots visuels
local function updateDisplay()
    if not mainFrame then return end
    for i = 1, 5 do
        local slot = displaySlots[i]
        if not slot then break end
        local symbolId = currentSequence[i]
        if symbolId and symbolTextures[symbolId] then
            local color = symbolColors[symbolId]
            slot.icon:SetTexture(symbolTextures[symbolId])
            slot.icon:SetAlpha(1)
            slot.number:SetText(i)
            slot.number:SetAlpha(1)
            slot.frame:SetBackdropColor(color[1]*0.2, color[2]*0.2, color[3]*0.2, 1)
            slot.frame:SetBackdropBorderColor(color[1]*0.9, color[2]*0.9, color[3]*0.9, 1)
        else
            slot.icon:SetAlpha(0)
            slot.number:SetAlpha(0)
            slot.frame:SetBackdropColor(0.1, 0.1, 0.15, 1)
            slot.frame:SetBackdropBorderColor(0.3, 0.3, 0.4, 1)
        end
    end
end

C_Timer.NewTicker(0.1, function()
    if needsUpdate then
        pcall(updateDisplay)
        needsUpdate = false
    end
end)

local function addSymbol(symbolId)
    if not inputsOpen then return end  -- on accepte les inputs seulement pendant la fenêtre
    if not symbolTextures[symbolId] then return end
    if #currentSequence >= maxSymbols then return end
    lastSymbolTime = GetTime()
    table.insert(currentSequence, symbolId)
    needsUpdate = true
end

local function analyzeBuffer()
    local hasYell = eventBuffer["YELL"]
    local hasPing = eventBuffer["PING"]
    local hasRW   = eventBuffer["RW"]

    local symbol = nil
    if hasYell and hasPing then
        symbol = "TRI"
    elseif hasPing and hasRW then
        symbol = "DIA"
    elseif hasPing then
        symbol = "CIR"
    elseif hasRW then
        symbol = "X"
    elseif hasYell then
        symbol = "TEE"
    end

    wipe(eventBuffer)
    bufferTimer = nil

    if symbol then addSymbol(symbol) end
end

local function onEventReceived(eventType)
    local now = GetTime()
    if now - lastSymbolTime < SYMBOL_COOLDOWN then return end
    --if IsInRaid() and not (UnitIsGroupLeader("player") or UnitIsGroupAssistant("player")) then return end
    eventBuffer[eventType] = true
    if not bufferTimer then
        bufferTimer = C_Timer.NewTimer(COMBO_WINDOW, analyzeBuffer)
    end
end

local messageListener = CreateFrame("Frame")
for eventName in pairs(watchedEvents) do
    messageListener:RegisterEvent(eventName)
end

messageListener:SetScript("OnEvent", function(self, event)
    local eventType = watchedEvents[event]
    if eventType then onEventReceived(eventType) end
end)

-- repositionne les slots selon le mode actuel
local function applyMode()
    wipe(currentSequence)
    needsUpdate = true

    local positions = isHeroic and slotPositionsHeroic or slotPositionsNormal
    maxSymbols = isHeroic and 5 or 3

    -- cache tous les slots d'abord
    for i = 1, 5 do
        if displaySlots[i] then
            displaySlots[i].frame:Hide()
        end
    end

    -- affiche et repositionne les slots actifs
    for i = 1, maxSymbols do
        local slot = displaySlots[i]
        local pos  = positions[i]
        if slot and pos then
            slot.frame:ClearAllPoints()
            slot.frame:SetPoint("CENTER", mainFrame, "CENTER", pos.x, pos.y)
            slot.frame:Show()
        end
    end
end

local function buildFrame()
    local f = CreateFrame("Frame", "PugaHelperMainFrame", UIParent)
    f:SetSize(210, 210)
    f:SetPoint("CENTER", UIParent, "CENTER", 0, 150)
    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", function(self)
        self:StopMovingOrSizing()
        -- sauvegarde la position
        local point, _, relativePoint, x, y = self:GetPoint()
        PugaHelperDB = PugaHelperDB or {}
        PugaHelperDB.point         = point
        PugaHelperDB.relativePoint = relativePoint
        PugaHelperDB.x             = x
        PugaHelperDB.y             = y
    end)
    f:SetClampedToScreen(true)
    f:SetFrameStrata("HIGH")

    -- fond circulaire gris
    local background = f:CreateTexture(nil, "BACKGROUND")
    background:SetPoint("CENTER", 0, 0)
    background:SetSize(220, 220)
    background:SetColorTexture(0.314, 0.306, 0.306, 0.92)

    local backgroundMask = f:CreateMaskTexture()
    backgroundMask:SetAllPoints(background)
    backgroundMask:SetTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
    background:AddMaskTexture(backgroundMask)

    -- image du boss au centre
    local bossImage = f:CreateTexture(nil, "ARTWORK")
    bossImage:SetSize(96, 96)
    bossImage:SetPoint("CENTER", 0, 8)
    bossImage:SetTexture("Interface\\AddOns\\PugaHelper\\media\\Lura.png")

    local bossMask = f:CreateMaskTexture()
    bossMask:SetAllPoints(bossImage)
    bossMask:SetTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
    bossImage:AddMaskTexture(bossMask)

    -- icône tank
    local tankImage = f:CreateTexture(nil, "ARTWORK")
    tankImage:SetSize(24, 24)
    tankImage:SetPoint("BOTTOM", bossImage, "TOP", 0, 2)
    tankImage:SetTexture("Interface\\AddOns\\PugaHelper\\media\\tank.png")

    -- boutons Normal / Heroic en haut
    local normalBtn = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    normalBtn:SetSize(80, 22)
    normalBtn:SetPoint("TOPLEFT", f, "TOPLEFT", 14, 30)
    normalBtn:SetText("Normal")

    local heroicBtn = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    heroicBtn:SetSize(80, 22)
    heroicBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -14, 30)
    heroicBtn:SetText("Heroic")

    normalBtn:SetScript("OnClick", function()
        if not isHeroic then return end
        isHeroic = false
        applyMode()
    end)

    heroicBtn:SetScript("OnClick", function()
        if isHeroic then return end
        isHeroic = true
        applyMode()
    end)

    -- création des 5 slots (tous créés, on en cache selon le mode)
    for i = 1, 5 do
        local pos = slotPositionsHeroic[i]

        local slotFrame = CreateFrame("Frame", nil, f, "BackdropTemplate")
        slotFrame:SetSize(slotSize, slotSize)
        slotFrame:SetPoint("CENTER", f, "CENTER", pos.x, pos.y)
        slotFrame:SetBackdrop(slotBackdrop)
        slotFrame:SetBackdropColor(0.1, 0.1, 0.15, 1)
        slotFrame:SetBackdropBorderColor(0.3, 0.3, 0.4, 1)

        local iconTexture = slotFrame:CreateTexture(nil, "ARTWORK")
        iconTexture:SetSize(slotSize - 8, slotSize - 8)
        iconTexture:SetPoint("CENTER", 0, 0)
        iconTexture:SetAlpha(0)
        iconTexture:Show()

        local numberLabel = slotFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        numberLabel:SetPoint("CENTER", 0, -28)
        numberLabel:SetText("")
        numberLabel:SetTextColor(1, 1, 1, 1)
        numberLabel:SetAlpha(0)
        numberLabel:SetScale(1.2)
        numberLabel:Show()

        displaySlots[i] = {
            frame  = slotFrame,
            icon   = iconTexture,
            number = numberLabel,
        }
    end

    mainFrame = f
    f:Show()
end

-- /pugamacro crée les macros automatiquement
SLASH_PUGAMACRO1 = "/pugamacro"
SlashCmdList["PUGAMACRO"] = function()
    local macros = {
        { name = "PugaTriangle", icon = "INV_Mask_07",                  text = "/yell .\n/ping assist" },
        { name = "PugaDiamond",  icon = "INV_Mask_10",                  text = "/rw .\n/ping assist" },
        { name = "PugaCircle",   icon = "INV_Mask_08",                  text = "/ping assist" },
        { name = "PugaX",        icon = "INV_Mask_09",                  text = "/rw ." },
        { name = "PugaT",        icon = "INV_Helm_Mask_MetalBand_A_01", text = "/yell ." },

    }
    for _, m in ipairs(macros) do
        local index = GetMacroIndexByName(m.name)
        if index == 0 then
            CreateMacro(m.name, m.icon, m.text, nil)
            print("|cff66ccffPugaHelper:|r Macro créée -> " .. m.name)
        else
            EditMacro(index, m.name, m.icon, m.text)
            print("|cff66ccffPugaHelper:|r Macro mise à jour -> " .. m.name)
        end
    end
    print("|cff00ff00PugaHelper:|r Toutes les macros sont prêtes !")
end

local loader = CreateFrame("Frame")
loader:RegisterEvent("ADDON_LOADED")
loader:RegisterEvent("PLAYER_LOGIN")
loader:RegisterEvent("PLAYER_REGEN_DISABLED")
loader:RegisterEvent("PLAYER_REGEN_ENABLED")

-- timestamp de la dernière sortie de combat
-- permet de détecter les vanish / feign death
local lastCombatEnd = 0
local VANISH_GRACE  = 10  -- secondes de grâce avant de considérer que c'est un vrai wipe

loader:SetScript("OnEvent", function(self, event, ...)
    if event == "ADDON_LOADED" then
        local name = ...
        if name == addonName then
            PugaHelperDB = PugaHelperDB or {}
            -- on construit l'UI ici, les SavedVariables sont déjà disponibles
            buildFrame()
            applyMode()
            -- restaure la position sauvegardée
            if PugaHelperDB.x then
                mainFrame:ClearAllPoints()
                mainFrame:SetPoint(PugaHelperDB.point, UIParent, PugaHelperDB.relativePoint, PugaHelperDB.x, PugaHelperDB.y)
            end
            DEFAULT_CHAT_FRAME:AddMessage("|cff66ccffPugaHelper:|r Loaded. /puga pour afficher.")
        end
    elseif event == "PLAYER_LOGIN" then
        SLASH_PUGAHELPER1 = "/puga"
        SlashCmdList["PUGAHELPER"] = function()
            if not mainFrame then return end
            if mainFrame:IsShown() then
                mainFrame:Hide()
            else
                mainFrame:Show()
            end
        end
    elseif event == "PLAYER_REGEN_DISABLED" then
        -- on entre en combat
        local now = GetTime()
        if now - lastCombatEnd < VANISH_GRACE then
            -- sortie de combat très courte = vanish/feign death, on continue sans reset
            return
        end
        startEncounterTimers()
    elseif event == "PLAYER_REGEN_ENABLED" then
        -- on sort de combat
        lastCombatEnd = GetTime()
        -- on attend VANISH_GRACE secondes avant de vraiment reset
        -- si on rentre en combat entre temps, l'event PLAYER_REGEN_DISABLED ignorera le reset
        C_Timer.NewTimer(VANISH_GRACE, function()
            -- vérifie qu'on est toujours hors combat après la grâce
            if not InCombatLockdown() then
                stopEncounter()
            end
        end)
    end
end)