local ADDON, ns = ...
local L = ns.L

--[[-----------------------------------------------------------------------------
BugSackBlock – Kernlogik

Was passiert hier?
- Wir hooken/patchen BugSack an genau den Stellen, wo BugSack Bugs versendet bzw. Bug-Comm empfängt.
- Ziel ist es, BugSack-„Bug-Whispers“ (Addon-Comm) kontrolliert zu blockieren, um Spam und Lag zu vermeiden.
- Optional: Auto-Reply als normaler Whisper, damit Sender weiß, warum nichts ankommt.
- Optional: Alt-Klick-Löschen (BugSack altwipe) wieder aktivieren.
- Optional: Tooltip-Erweiterung im BugSack LDB/Minimap Tooltip.
- Slashcommands: /bsb und /bugsackblock.

Wichtig (Design):
- Wir ersetzen BugSack-Funktionen nur via Wrapper und speichern die Originalfunktion.
  => Damit bleibt BugSack normal funktionsfähig, wenn die Block-Optionen deaktiviert sind.
-----------------------------------------------------------------------------]]

local AceAddon = LibStub("AceAddon-3.0")
local Addon = AceAddon:NewAddon(ADDON, "AceEvent-3.0", "AceConsole-3.0", "AceHook-3.0")

-- Standardwerte (AceDB SavedVariables)
local defaults = {
  profile = {
    -- (1) Outgoing: BugSack-Versand blockieren
    disableSend = false,

    -- (2) Incoming: Empfangsmodus für BugSack-Comm
    -- OFF: immer erlauben
    -- ALWAYS: immer blockieren
    -- COMBAT: nur im Kampf blockieren
    -- OPENWORLD: nur Open World erlauben (Instanzen blocken)
    receiveMode = "ALWAYS",

    -- (3) Optional: Auto-Reply bei geblocktem Empfang
    replyToSender = false,
    replyMessage = "Na na na na, I can't hear you!",
    replyThrottle = 5, -- Sekunden Mindestabstand pro Sender

    -- Debug-Ausgaben im Chat
    verbose = false,

    -- BugSack-Option altwipe spiegeln
    enableAltWipe = true,

    -- Tooltip erweitern
    extendTooltip = true,
  }
}

-- Reply-Throttle pro Sender: verhindert Reply-Spam, falls jemand mehrfach sendet
local lastReply = {}

-- verhindert doppeltes Patchen
local hooked = false

-- Hilfsfunktion: „Open World“ = nicht in Instanz oder Instanztyp „none“
local function InOpenWorld()
  local inInstance, instanceType = IsInInstance()
  return (not inInstance) or instanceType == "none"
end

-- Entscheidet anhand receiveMode, ob wir eingehende BugSack-Comm blocken sollen.
-- WARUM: Wenn wir vor BugSacks Deserialisierung/StoreError abbrechen,
-- vermeiden wir potenziellen Lag durch große Bugreports anderer Spieler.
function Addon:ShouldBlockReceive()
  local mode = self.db.profile.receiveMode
  if mode == "OFF" then
    return false
  elseif mode == "ALWAYS" then
    return true
  elseif mode == "COMBAT" then
    return UnitAffectingCombat("player") or InCombatLockdown()
  elseif mode == "OPENWORLD" then
    return not InOpenWorld()
  end
  return false
end

-- Optionaler Auto-Reply:
-- WARUM: Sender bekommt Feedback („Empfang deaktiviert“), statt einfach ins Leere zu senden.
-- Throttle verhindert, dass wir selbst zum Spammer werden.
function Addon:MaybeReply(sender)
  if not self.db.profile.replyToSender then return end
  if type(sender) ~= "string" or sender == "" then return end

  local now = GetTime()
  local t = lastReply[sender] or 0
  local throttle = tonumber(self.db.profile.replyThrottle) or 5
  if now - t < throttle then return end

  lastReply[sender] = now

  local msg = self.db.profile.replyMessage
  if type(msg) ~= "string" or msg == "" then
    msg = L["BlockedNotice"]
  end

  -- Normaler Whisper (kein Addon-Comm), best effort
  pcall(SendChatMessage, msg, "WHISPER", nil, sender)
end

-- Debug-Ausgaben nur bei verbose=true
local function DebugPrint(self, ...)
  if self.db and self.db.profile and self.db.profile.verbose then
    self:Print(...)
  end
end

-- PatchBugSack(): setzt unsere Hooks in BugSack (Senden/Empfangen) nur einmal.
-- WARUM: Hooks mehrfach zu setzen führt zu Wrapper-Kaskaden und schwer debugbaren Effekten.
function Addon:PatchBugSack()
  if hooked then return end

  local BugSack = _G.BugSack
  if type(BugSack) ~= "table" then return end

  DebugPrint(self, "BugSack gefunden – Hooks werden gesetzt.")

  ---------------------------------------------------------------------------
  -- (1) Hook: BugSack.SendBugsToUser
  -- WARUM: Das ist der zentrale Versandpfad. Wenn disableSend aktiv ist,
  -- verhindern wir den Versand vollständig (kein Traffic, kein Spam).
  ---------------------------------------------------------------------------
  if type(BugSack.SendBugsToUser) == "function" and not BugSack.__BugSackBlock_OrigSend then
    BugSack.__BugSackBlock_OrigSend = BugSack.SendBugsToUser
    BugSack.SendBugsToUser = function(selfBS, player, session)
      if Addon.db.profile.disableSend then
        DebugPrint(Addon, "BugSackBlock: Versand blockiert.")
        return
      end
      return BugSack.__BugSackBlock_OrigSend(selfBS, player, session)
    end
  end

  ---------------------------------------------------------------------------
  -- (1) Hook: BugSackSendBugs Popup „Send“-Button deaktivieren
  -- WARUM: UX – der Spieler sieht sofort, dass Senden deaktiviert ist,
  -- statt erst durch fehlende Wirkung verwirrt zu werden.
  ---------------------------------------------------------------------------
  if type(_G.StaticPopupDialogs) == "table" and type(_G.StaticPopupDialogs.BugSackSendBugs) == "table" then
    local dlg = _G.StaticPopupDialogs.BugSackSendBugs
    if not dlg.__BugSackBlock_Patched then
      dlg.__BugSackBlock_Patched = true
      local origOnShow = dlg.OnShow
      dlg.OnShow = function(selfPopup)
        if origOnShow then pcall(origOnShow, selfPopup) end
        if Addon.db.profile.disableSend and selfPopup.GetButton1 then
          selfPopup:GetButton1():Disable()
        end
      end
    end
  end

  ---------------------------------------------------------------------------
  -- (2) Hook: BugSack.OnBugComm
  -- WARUM (kritisch): BugSack deserialisiert und speichert den Fehler. Das kann ruckeln,
  -- wenn große Fehlerdaten über Addon-Comm reinkommen. Wenn wir blocken, returnen wir
  -- vor der Verarbeitung => keine Deserialisierung, kein StoreError => weniger Lag.
  ---------------------------------------------------------------------------
  if type(BugSack.OnBugComm) == "function" and not BugSack.__BugSackBlock_OrigRecv then
    BugSack.__BugSackBlock_OrigRecv = BugSack.OnBugComm
    BugSack.OnBugComm = function(selfBS, prefix, message, dist, sender)
      if prefix == "BugSack" and Addon:ShouldBlockReceive() then
        DebugPrint(Addon, ("BugSackBlock: Empfang blockiert von %s."):format(tostring(sender)))
        Addon:MaybeReply(sender)
        return
      end
      return BugSack.__BugSackBlock_OrigRecv(selfBS, prefix, message, dist, sender)
    end
  end

  hooked = true

  -- Side Effects anwenden (nicht nur „Option speichern“, sondern BugSack spiegeln)
  self:ApplyBugSackAltWipe()
  self:PatchBugSackTooltip()

  -- WARUM: manche LDB-/Tooltip-Dinge sind erst nach PLAYER_LOGIN zuverlässig da
  self:RegisterEvent("PLAYER_LOGIN", function()
    self:PatchBugSackTooltip()
  end)
end

-- PatchBugSackTooltip(): erweitert BugSack LDB Tooltip um BugSackBlock-Status.
-- WARUM: schnelles „At a glance“ ohne Options öffnen.
function Addon:PatchBugSackTooltip()
  if not (self.db and self.db.profile and self.db.profile.extendTooltip) then
    return
  end

  local ldb = LibStub:GetLibrary("LibDataBroker-1.1", true)
  if not ldb then return end

  local obj = ldb:GetDataObjectByName("BugSack")
  if type(obj) ~= "table" or type(obj.OnTooltipShow) ~= "function" then return end
  if obj.__BugSackBlock_TooltipPatched then return end

  obj.__BugSackBlock_TooltipPatched = true
  local orig = obj.OnTooltipShow

  -- WARUM (Hook): Wir rufen zuerst BugSacks Tooltip auf und hängen danach unsere Zeilen dran.
  -- So bleibt BugSacks Tooltip vollständig, und wir ergänzen nur zusätzliche Informationen.
  obj.OnTooltipShow = function(tt, ...)
    pcall(orig, tt, ...)

    local mode = Addon.db.profile.receiveMode
    local blockedNow = Addon:ShouldBlockReceive()

    local function yesno(v)
      return v and "|cff00ff00ON|r" or "|cffff4444OFF|r"
    end

    local function modeLabel(m)
      if m == "OFF" then return "OFF"
      elseif m == "ALWAYS" then return "ALWAYS"
      elseif m == "COMBAT" then return "COMBAT"
      elseif m == "OPENWORLD" then return "OPENWORLD"
      end
      return tostring(m)
    end

    tt:AddLine(" ")
    tt:AddLine("|cff33ff99" .. (ns.L["TooltipHeader"] or "BugSackBlock") .. "|r")

    tt:AddDoubleLine(
      ns.L["TooltipSend"] or "Send",
      Addon.db.profile.disableSend and "|cffff4444BLOCKED|r" or "|cff00ff00ALLOWED|r"
    )

    local nowTxt = blockedNow and (ns.L["TooltipBlockedNow"] or "Blocked now") or (ns.L["TooltipAllowedNow"] or "Allowed now")
    tt:AddDoubleLine(
      (ns.L["TooltipReceive"] or "Receive") .. " (" .. modeLabel(mode) .. ")",
      nowTxt
    )

    tt:AddDoubleLine(
      ns.L["TooltipReply"] or "Reply",
      yesno(Addon.db.profile.replyToSender)
    )

    tt:AddDoubleLine(
      ns.L["TooltipAltWipe"] or "Alt-wipe",
      yesno(Addon.db.profile.enableAltWipe)
    )
  end
end

-- ApplyBugSackAltWipe(): spiegelt enableAltWipe in BugSacks eigene Variablen.
-- WARUM: BugSack liest altwipe aus BugSackDB/BugSack.db – wir wollen nur „eine“ Option steuern.
function Addon:ApplyBugSackAltWipe()
  local enabled = self.db and self.db.profile and self.db.profile.enableAltWipe

  if type(_G.BugSackDB) == "table" then
    _G.BugSackDB.altwipe = enabled and true or false
  end

  local BugSack = _G.BugSack
  if type(BugSack) == "table" and type(BugSack.db) == "table" then
    BugSack.db.altwipe = enabled and true or false
  end
end

-- OnInitialize: DB laden, Optionen anwenden, Slashcommands registrieren und BugSack patchen.
function Addon:OnInitialize()
  self.db = LibStub("AceDB-3.0"):New("BugSackBlockDB", defaults, true)

  -- Optionale Side-Effects sofort anwenden (auch wenn BugSack bereits geladen ist)
  self:ApplyBugSackAltWipe()

  -- Slashcommands
  self:RegisterChatCommand("bsb", "SlashCommand")
  self:RegisterChatCommand("bugsackblock", "SlashCommand")

  -- Falls BugSack schon geladen ist:
  self:PatchBugSack()

  -- Falls BugSack später lädt:
  self:RegisterEvent("ADDON_LOADED", function(_, name)
    if name == "BugSack" then
      self:PatchBugSack()
    end
  end)
end

-- SlashCommand Handler für /bsb und /bugsackblock
function Addon:SlashCommand(input)
  input = (input or ""):trim()
  local cmd, rest = input:match("^(%S+)%s*(.-)$")
  cmd = cmd and cmd:lower() or ""
  rest = rest or ""

  if cmd == "" or cmd == "help" then
    self:Print(("/bsb %s:"):format(L["Commands"]))
    self:Print("/bsb status")
    self:Print("/bsb disablesend on|off")
    self:Print("/bsb receive off|always|combat|openworld")
    self:Print("/bsb reply on|off")
    self:Print("/bsb verbose on|off")
    self:Print("/bsb altwipe on|off")
    self:Print("/bsb tooltip on|off")
    return
  end

  if cmd == "status" then
    self:Print(("%s: disableSend=%s, receiveMode=%s, reply=%s"):format(
      L["Status"],
      tostring(self.db.profile.disableSend),
      tostring(self.db.profile.receiveMode),
      tostring(self.db.profile.replyToSender)
    ))
    return
  end

  if cmd == "disablesend" then
    local v = rest:lower()
    self.db.profile.disableSend = (v == "on" or v == "1" or v == "true")
    self:Print("disableSend=" .. tostring(self.db.profile.disableSend))
    return
  end

  if cmd == "receive" then
    local v = rest:lower()
    if v == "off" then self.db.profile.receiveMode = "OFF"
    elseif v == "always" then self.db.profile.receiveMode = "ALWAYS"
    elseif v == "combat" then self.db.profile.receiveMode = "COMBAT"
    elseif v == "openworld" then self.db.profile.receiveMode = "OPENWORLD"
    end
    self:Print("receiveMode=" .. tostring(self.db.profile.receiveMode))
    return
  end

  if cmd == "reply" then
    local v = rest:lower()
    self.db.profile.replyToSender = (v == "on" or v == "1" or v == "true")
    self:Print("replyToSender=" .. tostring(self.db.profile.replyToSender))
    return
  end

  if cmd == "verbose" then
    local v = rest:lower()
    self.db.profile.verbose = (v == "on" or v == "1" or v == "true")
    self:Print("verbose=" .. tostring(self.db.profile.verbose))
    return
  end

  if cmd == "altwipe" then
    local v = rest:lower()
    self.db.profile.enableAltWipe = (v == "on" or v == "1" or v == "true")
    self:ApplyBugSackAltWipe()
    self:Print("enableAltWipe=" .. tostring(self.db.profile.enableAltWipe))
    return
  end

  if cmd == "tooltip" then
    local v = rest:lower()
    self.db.profile.extendTooltip = (v == "on" or v == "1" or v == "true")
    if self.db.profile.extendTooltip then
      self:PatchBugSackTooltip()
    end
    self:Print("extendTooltip=" .. tostring(self.db.profile.extendTooltip))
    return
  end

  self:SlashCommand("help")
end