-- Actions_Extra.lua
-- Actions additionnelles, isolées du cœur : aucun changement requis dans Core.lua.
-- Se contente d'écouter quelques events et d'appeler SRPWL.TrySpeak("<ActionKey>").

local ADDON_NAME, SRPWL = ...
if not SRPWL then return end

-- ========= Mini helpers =========
local f = CreateFrame("Frame")
local DB = _G.SleekRP_WLP_DB or {}
local function IsDebug() return DB and DB.debug == true end
local function log(...) if IsDebug() then print("|cff88fSleekRP:Extra|r", ...) end end

-- Anti-spam simple (par clé)
local _lastAt = {}
local function throttle(key, window)
  local t = GetTime()
  window = window or 2.0
  if (t - (_lastAt[key] or 0)) < window then return true end
  _lastAt[key] = t
  return false
end

-- ========= États internes =========
local IN_INSTANCE = false
local INSTANCE_TYPE = nil   -- "party","raid","arena","pvp",...
local ARENA_STARTED = false
local DUNGEON_COMBAT_PINGED = false

-- Rare tracking (par nameplates → classification)
local RARE_GUIDS = {}  -- guid => "rare"/"rareelite"/true

-- Trinkets: mise en cache des sorts "Use" équipés (slot 13/14)
local TrinketUseNames = {}  -- [lowered spell name] = true

-- Soul shards (retail) – cooldown fort (45 s)
local lastShards = nil

-- Summon_* : garde-fous anti doublons
local _summonStartAt = {}

-- Time-based (Noon/Night)
local _timeTicker = nil
local _timeFiredDay = { Noon = -1, Night = -1 } -- jour julien (date("*t").yday)
local _timeFiredMinute = { Noon = -1, Night = -1 }

-- Durability / GearBroken
local DUR_LAST_BUCKET = nil         -- nil / "B30" (20–30) / "B20" (10–20) / "B10" (0–10)
local DUR_LAST_ALERT_AT = 0         -- timestamp dernière alerte
local DUR_INTERVALS = { B30 = 600, B20 = 300, B10 = 120 } -- 10 min, 5 min, 2 min

-- ========= Détection utilitaire =========
local function UpdateInstanceInfo()
  local inInst, instType = IsInInstance()
  IN_INSTANCE = inInst or false
  INSTANCE_TYPE = instType
  if not IN_INSTANCE then
    ARENA_STARTED = false
    DUNGEON_COMBAT_PINGED = false
  end
  log("Instance:", IN_INSTANCE, "type=", tostring(instType))
end

local function RebuildTrinketUseNames()
  wipe(TrinketUseNames)
  for _, slot in ipairs({13, 14}) do
    local link = GetInventoryItemLink("player", slot)
    if link then
      local spellName = GetItemSpell(link) -- retourne le nom du sort "Utiliser : ..."
      if spellName and spellName ~= "" then
        TrinketUseNames[spellName:lower()] = true
      end
    end
  end
  log("Trinket spells cached:", next(TrinketUseNames) and "yes" or "none")
end

-- ========= Emotes additionnels (facepalm, surprised, victory, work) =========
-- On hooke DoEmote pour capter l'intention directe du joueur.
do
  local mapped = {
    SURPRISED = "Emote_Surprised",
    FACEPALM  = "Emote_Facepalm",
    VICTORY   = "Emote_Victory",
    WORK      = "Emote_Work",
  }
  if type(DoEmote) == "function" and not f._hooked_extra_emotes then
    f._hooked_extra_emotes = true
    hooksecurefunc("DoEmote", function(token)
      token = token and tostring(token):upper() or ""
      local key = mapped[token]
      if key and not throttle("EMOTE_"..token, 2.0) then
        SRPWL.TrySpeak(key)
      end
    end)
  end
end

-- ========= Summon_* (début du cast) =========
-- IDs principaux (Classic/Retail). On déclenche au START pour la sensation "immédiate".
local SUMMON_IDS = {
  [688]   = "Summon_Imp",
  [697]   = "Summon_Voidwalker",
  [712]   = "Summon_Sayaad",     -- Succubus (anciens IDs)
  [366222]= "Summon_Sayaad",     -- Retail "Summon Sayaad"
  [691]   = "Summon_Felhunter",
  [30146] = "Summon_Felguard",
}

local function OnSummonStart(spellID)
  local key = SUMMON_IDS[spellID]
  if not key then return end
  local now = GetTime()
  -- petit anti-spam local par clé (cast multiples rares)
  if (_summonStartAt[key] or 0) > (now - 1.0) then return end
  _summonStartAt[key] = now
  SRPWL.TrySpeak(key)
end

-- ========= Time-based (midi & 19:00) =========
local function StartTimeTicker()
  if _timeTicker then return end
  _timeTicker = C_Timer.NewTicker(15.0, function()
    local ts = GetServerTime and GetServerTime() or time()
    local t = date("*t", ts)
    -- Midi (12:00)
    if t.hour == 12 and t.min == 0 then
      if _timeFiredDay.Noon ~= t.yday or _timeFiredMinute.Noon ~= t.min then
        _timeFiredDay.Noon = t.yday
        _timeFiredMinute.Noon = t.min
        SRPWL.TrySpeak("Time_Noon")
      end
    end
    -- 19:00 → Time_Night (clé "Night", pas "T19")
    if t.hour == 19 and t.min == 0 then
      if _timeFiredDay.Night ~= t.yday or _timeFiredMinute.Night ~= t.min then
        _timeFiredDay.Night = t.yday
        _timeFiredMinute.Night = t.min
        SRPWL.TrySpeak("Time_Night")
      end
    end
  end)
end

-- ========= Durability / GearBroken =========
local DUR_SLOTS = { 1,3,5,6,7,8,9,10,16,17,18 } -- tête, épaules, torse, ceinture, jambes, bottes, poignets, gants, armes, distance (selon version)

local function GetWorstDurability()
  local worst = 1.0
  local brokeCount = 0
  for _, slot in ipairs(DUR_SLOTS) do
    local cur, max = GetInventoryItemDurability(slot)
    if cur and max and max > 0 then
      local r = cur / max
      if r < worst then worst = r end
      if cur == 0 then brokeCount = brokeCount + 1 end
    end
  end
  return worst, brokeCount
end

local function BucketFor(ratio)
  if ratio == nil then return nil end
  if ratio <= 0.10 then return "B10"
  elseif ratio <= 0.20 then return "B20"
  elseif ratio <= 0.30 then return "B30"
  else return nil end
end

local function MaybeAlertDurability()
  local ratio = GetWorstDurability()
  local bucket = BucketFor(ratio)
  if not bucket then
    -- au-dessus de 30% : on reset l'état
    DUR_LAST_BUCKET = nil
    DUR_LAST_ALERT_AT = 0
    return
  end

  local now = GetTime()
  local needAlert = false

  if DUR_LAST_BUCKET ~= bucket then
    -- On a baissé de palier → alerte immédiate
    needAlert = true
  else
    -- Même palier : rappel si intervalle OK
    local dt = now - (DUR_LAST_ALERT_AT or 0)
    local interval = DUR_INTERVALS[bucket] or 600
    if dt >= interval then
      needAlert = true
    end
  end

  if needAlert and not throttle("GearBroken", 1.0) then
    SRPWL.TrySpeak("GearBroken")
    DUR_LAST_ALERT_AT = now
    DUR_LAST_BUCKET = bucket
  end
end

-- ========= Handlers =========
local function OnEvent(self, event, ...)
  if event == "PLAYER_LOGIN" then
    UpdateInstanceInfo()
    RebuildTrinketUseNames()
    StartTimeTicker()
    C_Timer.After(1.0, MaybeAlertDurability)

  elseif event == "PLAYER_ENTERING_WORLD" then
    UpdateInstanceInfo()
    -- Arena start (simple) : on considère le début à l'entrée en instance arena
    if IN_INSTANCE and INSTANCE_TYPE == "arena" and not ARENA_STARTED then
      ARENA_STARTED = true
      if not throttle("ArenaStart", 10.0) then
        SRPWL.TrySpeak("ArenaStart")
      end
    end
    -- Reset dungeon ping à l'entrée
    if IN_INSTANCE and INSTANCE_TYPE == "party" then
      DUNGEON_COMBAT_PINGED = false
    end
    C_Timer.After(1.0, MaybeAlertDurability)

  elseif event == "PLAYER_FLAGS_CHANGED" then
    local unit = ...
    if unit == "player" and UnitIsAFK("player") then
      if not throttle("AFK", 30.0) then
        SRPWL.TrySpeak("AFK")
      end
    end

  elseif event == "PLAYER_REGEN_DISABLED" then
    -- CombatStarted global
    if not throttle("CombatStarted", 4.0) then
      SRPWL.TrySpeak("CombatStarted")
    end
    -- OngoingDungeon : premier combat dans un donjon 5 joueurs
    if IN_INSTANCE and INSTANCE_TYPE == "party" and not DUNGEON_COMBAT_PINGED then
      DUNGEON_COMBAT_PINGED = true
      if not throttle("OngoingDungeon", 15.0) then
        SRPWL.TrySpeak("OngoingDungeon")
      end
    end

  elseif event == "UNIT_POWER_UPDATE" then
    local unit, powerType = ...
    if unit == "player" and (powerType == "SOUL_SHARDS" or powerType == "SOUL_SHARD" or powerType == "ALTERNATE") then
      local shards = UnitPower("player", Enum and Enum.PowerType and Enum.PowerType.SoulShards or 7)
      if shards ~= nil then
        if lastShards ~= nil and shards > lastShards then
          -- CD fort (45s) sur l'annonce
          if not throttle("SoulShardGained", 45.0) then
            SRPWL.TrySpeak("SoulShardGained")
          end
        end
        lastShards = shards
      end
    end

  elseif event == "UNIT_SPELLCAST_START" then
    local unit, _, spellID = ...
    if unit == "player" and spellID then
      OnSummonStart(spellID)
    end

  elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
    local unit, _, spellID = ...
    if unit ~= "player" then return end

    -- Drain Life (quelques IDs connus)
    if spellID == 234153 or spellID == 689 then
      if not throttle("DrainLifeUsed", 2.0) then
        SRPWL.TrySpeak("DrainLifeUsed")
      end
    end

    -- Infernal (Destruction) — on conserve l'annonce (déjà en place)
    if spellID == 1122 then
      if not throttle("InfernalSummoned", 5.0) then
        SRPWL.TrySpeak("InfernalSummoned")
      end
    end

    -- Détection "TrinketUsed" via nom du sort "Use" des bijoux (slots 13/14)
    local name = C_Spell and C_Spell.GetSpellName and C_Spell.GetSpellName(spellID) or (GetSpellInfo and GetSpellInfo(spellID))
    local n = type(name)=="string" and name:lower() or ""
    if n ~= "" and TrinketUseNames[n] then
      if not throttle("TrinketUsed", 2.0) then
        SRPWL.TrySpeak("TrinketUsed")
      end
    end

  elseif event == "ACTIONBAR_UPDATE_COOLDOWN" then
    -- Certains bijoux « use » ne lancent pas un sort joueur propre → fallback :
    -- On ne peut pas déterminer l’utilisation exacte ici sans scanning lourd.
    -- On se repose surtout sur UNIT_SPELLCAST_SUCCEEDED + GetItemSpell (ci-dessus).
    -- (La ligne est conservée pour de futures améliorations si besoin.)
    -- no-op

  elseif event == "PLAYER_EQUIPMENT_CHANGED" then
    local slot = ...
    if slot == 13 or slot == 14 then
      C_Timer.After(0.1, RebuildTrinketUseNames)
    end

  elseif event == "UPDATE_INVENTORY_DURABILITY" then
    MaybeAlertDurability()

  elseif event == "MERCHANT_SHOW" then
    -- Si on peut réparer, on reset l'état d'alerte après un petit délai (le temps de cliquer Réparer tout)
    if CanMerchantRepair and CanMerchantRepair() then
      C_Timer.After(1.0, function()
        DUR_LAST_BUCKET = nil
        DUR_LAST_ALERT_AT = 0
        C_Timer.After(0.5, MaybeAlertDurability)
      end)
    end

  elseif event == "NAME_PLATE_UNIT_ADDED" then
    local unit = ...
    if UnitIsEnemy("player", unit) and UnitIsPlayer(unit) ~= true then
      local c = UnitClassification(unit) -- "normal","rare","rareelite","elite","worldboss"
      if c == "rare" or c == "rareelite" then
        local guid = UnitGUID(unit)
        if guid then RARE_GUIDS[guid] = c end
      end
    end

  elseif event == "NAME_PLATE_UNIT_REMOVED" then
    local unit = ...
    local guid = UnitGUID(unit)
    if guid and not UnitExists(unit) then
      -- on ne wipe pas tout de suite; on garde la trace le temps de voir un UNIT_DIED
    end

  elseif event == "COMBAT_LOG_EVENT_UNFILTERED" then
    local _, sub, _, srcGUID, _, _, _, destGUID, _, _, _, spellID, spellName = CombatLogGetCurrentEventInfo()

    -- Interrupted (joueur source)
    if sub == "SPELL_INTERRUPT" and srcGUID == UnitGUID("player") then
      if not throttle("Interrupted", 1.5) then
        SRPWL.TrySpeak("Interrupted")
      end
    end

    -- Fallback TrinketUsed (certains bijoux utilisent des spells génériques)
    if (sub == "SPELL_CAST_SUCCESS" or sub == "SPELL_AURA_APPLIED") and srcGUID == UnitGUID("player") then
      local n = (spellName or ""):lower()
      if n ~= "" and TrinketUseNames[n] then
        if not throttle("TrinketUsed", 2.0) then
          SRPWL.TrySpeak("TrinketUsed")
        end
      end
    end

    -- RareKilled (si cible marquée rare/rareelite via nameplate)
    if sub == "UNIT_DIED" and destGUID and RARE_GUIDS[destGUID] then
      RARE_GUIDS[destGUID] = nil
      if not throttle("RareKilled", 5.0) then
        SRPWL.TrySpeak("RareKilled")
      end
    end

  -- ===== BG/Arena résultats (heuristiques légères via messages système) =====
  elseif event == "CHAT_MSG_BG_SYSTEM_ALLIANCE" or event == "CHAT_MSG_BG_SYSTEM_HORDE" or event == "CHAT_MSG_BG_SYSTEM_NEUTRAL" then
    local msg = ...
    local l = (msg or ""):lower()

    -- PvPFlagCaptured (quelques formulations usuelles EN/FR)
    if l:find("captured the flag") or l:find("a pris le drapeau") or l:find("a captur") then
      if not throttle("PvPFlagCaptured", 5.0) then
        SRPWL.TrySpeak("PvPFlagCaptured")
      end
    end

    -- BG/Arena win/loss (phrases standard)
    if l:find("the alliance wins") or l:find("l'alliance remporte la victoire") then
      if INSTANCE_TYPE == "arena" then
        if not throttle("ArenaWin", 10.0) then SRPWL.TrySpeak("ArenaWin") end
      else
        if not throttle("BattlegroundWin", 10.0) then SRPWL.TrySpeak("BattlegroundWin") end
      end
    elseif l:find("the horde wins") or l:find("la horde remporte la victoire") then
      if INSTANCE_TYPE == "arena" then
        if not throttle("ArenaWin", 10.0) then SRPWL.TrySpeak("ArenaWin") end
      else
        if not throttle("BattlegroundWin", 10.0) then SRPWL.TrySpeak("BattlegroundWin") end
      end
    elseif l:find("defeat") or l:find("d[ée]faite") then
      -- fallback très large (défaite)
      if INSTANCE_TYPE == "arena" then
        if not throttle("ArenaLoss", 10.0) then SRPWL.TrySpeak("ArenaLoss") end
      else
        if not throttle("BattlegroundLoss", 10.0) then SRPWL.TrySpeak("BattlegroundLoss") end
      end
    end
  end
end

-- ========= Inscriptions =========
f:RegisterEvent("PLAYER_LOGIN")
f:RegisterEvent("PLAYER_ENTERING_WORLD")

-- AFK
f:RegisterEvent("PLAYER_FLAGS_CHANGED")

-- Combat start (global) + ping donjon
f:RegisterEvent("PLAYER_REGEN_DISABLED")

-- Soul shards (retail)
f:RegisterEvent("UNIT_POWER_UPDATE")

-- Summon_* dès le début de cast
f:RegisterEvent("UNIT_SPELLCAST_START")

-- Sorts joueur (Drain Life, Infernal, Trinkets) + fallback cooldowns
f:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
f:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN")
f:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")

-- Durability / réparation
f:RegisterEvent("UPDATE_INVENTORY_DURABILITY")
f:RegisterEvent("MERCHANT_SHOW")

-- RareKilled via nameplates + CLEU
f:RegisterEvent("NAME_PLATE_UNIT_ADDED")
f:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
f:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")

-- BG/Arena messages (victoires/drapeaux)
f:RegisterEvent("CHAT_MSG_BG_SYSTEM_ALLIANCE")
f:RegisterEvent("CHAT_MSG_BG_SYSTEM_HORDE")
f:RegisterEvent("CHAT_MSG_BG_SYSTEM_NEUTRAL")

f:SetScript("OnEvent", OnEvent)
