local ADDON_NAME = ...
local NTP = _G.NotThatPortal or {}
_G.NotThatPortal = NTP

local DEFAULT_WARN_TEMPLATE = "Troll Portal Detected. Player: %caster%, Portal: %portal%."
local QUICK_DUPLICATE_WINDOW = 1.5

NTP.DEFAULT_WARN_TEMPLATE = DEFAULT_WARN_TEMPLATE

local DEFAULTS = {
  minimap = { hide = false },
  settings = {
    announceToGroup = true,
    echoToSelf = true,
    soloTestMode = false,
    throttleSeconds = 15,
    warnTemplate = DEFAULT_WARN_TEMPLATE,
    window = { point = "CENTER", x = 0, y = 0 },
  },
  badPortals = {
    [120146] = true,
  },
}

local Runtime = {
  seenCasts = {},
  warned = {},
}

local RawIsSecretValue = _G.issecretvalue

local function IsSecretValue(value)
  if type(RawIsSecretValue) ~= "function" then
    return false
  end

  local ok, result = pcall(RawIsSecretValue, value)
  return ok and result and true or false
end

local function SafeString(value, fallback)
  fallback = fallback or "?"

  if value == nil or IsSecretValue(value) then
    return fallback
  end

  local ok, text = pcall(tostring, value)
  if not ok or not text or IsSecretValue(text) then
    return fallback
  end

  return text
end

local function CopyDefaults(dst, src)
  for k, v in pairs(src) do
    if type(v) == "table" then
      if type(dst[k]) ~= "table" then
        dst[k] = {}
      end
      CopyDefaults(dst[k], v)
    elseif dst[k] == nil then
      dst[k] = v
    end
  end
end

local function ToNumberSafe(value)
  if value == nil or IsSecretValue(value) then
    return nil
  end

  if type(value) == "number" then
    if IsSecretValue(value) then
      return nil
    end
    return value
  end

  local ok, result = pcall(tonumber, value)
  if not ok then
    return nil
  end

  if type(result) ~= "number" then
    return nil
  end

  if IsSecretValue(result) then
    return nil
  end

  return result
end

local function IsChallengeModeRestrictionActive()
  if not C_RestrictedActions or not C_RestrictedActions.IsAddOnRestrictionActive then
    return false
  end
  if not Enum or not Enum.AddOnRestrictionType or not Enum.AddOnRestrictionType.ChallengeMode then
    return false
  end

  local ok, active = pcall(C_RestrictedActions.IsAddOnRestrictionActive, Enum.AddOnRestrictionType.ChallengeMode)
  if not ok then
    return false
  end

  return active and true or false
end

local function GetGroupChatChannel()
  if IsInGroup(LE_PARTY_CATEGORY_INSTANCE) then
    return "INSTANCE_CHAT"
  end
  if IsInRaid() then
    return "RAID"
  end
  if IsInGroup() then
    return "PARTY"
  end
  return nil
end

local function TrimMessage(s)
  s = SafeString(s, "")
  s = s:gsub("^%s+", ""):gsub("%s+$", "")
  s = s:gsub("[%z\1-\31]", "")
  if s == "" then
    s = DEFAULT_WARN_TEMPLATE
  end
  if #s > 220 then
    s = s:sub(1, 220)
  end
  return s
end

function NTP:GetPortalDisplayName(spellID)
  if IsSecretValue(spellID) then
    return "Unknown Portal"
  end

  if type(spellID) ~= "number" then
    return "Unknown Portal"
  end

  if C_Spell and C_Spell.GetSpellInfo then
    local info = C_Spell.GetSpellInfo(spellID)
    if info and info.name and info.name ~= "" then
      return info.name
    end
  end

  local entry = self.PortalByID and self.PortalByID[spellID]
  if entry and entry.name and entry.name ~= "" then
    return entry.name
  end

  return "SpellID " .. tostring(spellID)
end

function NTP:GetPortalDisplayLink(spellID)
  if IsSecretValue(spellID) then
    return "Unknown Portal"
  end

  if type(spellID) ~= "number" then
    return "Unknown Portal"
  end

  if C_Spell and C_Spell.GetSpellLink then
    local link = C_Spell.GetSpellLink(spellID)
    if link and link ~= "" then
      return link
    end
  end

  return self:GetPortalDisplayName(spellID)
end

function NTP:IsUndesirablePortal(spellID)
  if IsSecretValue(spellID) then
    return false
  end

  return self.DB and self.DB.badPortals and self.DB.badPortals[spellID] == true
end

function NTP:SetPortalUndesirable(spellID, enabled)
  if IsSecretValue(spellID) then
    return
  end

  if type(spellID) ~= "number" then
    return
  end

  self.DB.badPortals = self.DB.badPortals or {}
  self.DB.badPortals[spellID] = enabled and true or false
end

function NTP:GetWarnTemplate()
  local template = self.DB and self.DB.settings and self.DB.settings.warnTemplate
  return TrimMessage(template)
end

function NTP:FormatWarning(casterName, spellID)
  local message = self:GetWarnTemplate()
  local portalName = self:GetPortalDisplayLink(spellID)
  local cleanCaster = SafeString(casterName, "Unknown")

  message = message:gsub("%%caster%%", cleanCaster)
  message = message:gsub("%%portal%%", portalName)

  if not message:find("%[NotThatPortal%]", 1, false) then
    message = "[NotThatPortal] " .. message
  end

  return message
end

local function ShouldSkipQuickDuplicate(casterKey, spellID)
  local now = GetTime()
  local key = SafeString(casterKey, "?") .. ":" .. SafeString(spellID, "?")
  local previous = Runtime.seenCasts[key]

  Runtime.seenCasts[key] = now

  if previous and (now - previous) <= QUICK_DUPLICATE_WINDOW then
    return true
  end

  return false
end

function NTP:IsWarningThrottled(casterKey, spellID)
  local throttleSeconds = ToNumberSafe(self.DB and self.DB.settings and self.DB.settings.throttleSeconds) or 15
  throttleSeconds = math.max(1, math.min(300, math.floor(throttleSeconds + 0.5)))

  local now = GetTime()
  local key = SafeString(casterKey, "?") .. ":" .. SafeString(spellID, "?")
  local previous = Runtime.warned[key]

  if previous and (now - previous) < throttleSeconds then
    return true
  end

  Runtime.warned[key] = now
  return false
end

function NTP:SendPortalWarning(casterName, casterKey, spellID)
  if not self.DB or not self.DB.settings then
    return
  end

  if self:IsWarningThrottled(casterKey, spellID) then
    return
  end

  local message = self:FormatWarning(casterName, spellID)
  local channel = GetGroupChatChannel()

  if channel and self.DB.settings.announceToGroup then
    SendChatMessage(message, channel)

    if self.DB.settings.echoToSelf then
      print("|cffd9b96eNotThatPortal:|r " .. message)
    end
    return
  end

  if (not channel) and self.DB.settings.soloTestMode then
    print("|cffd9b96eNotThatPortal [Solo Test]:|r " .. message)
  end
end

function NTP:HandlePortalCast(casterName, casterKey, spellID)
  if IsChallengeModeRestrictionActive() then
    return
  end

  if IsSecretValue(spellID) then
    return
  end

  spellID = ToNumberSafe(spellID)
  if type(spellID) ~= "number" or IsSecretValue(spellID) then
    return
  end

  if not self.PortalByID then
    return
  end

  if not self.PortalByID[spellID] then
    return
  end

  if not self:IsUndesirablePortal(spellID) then
    return
  end

  local resolvedCasterName = SafeString(casterName, "Unknown")
  local resolvedCasterKey = SafeString(casterKey, resolvedCasterName)

  if ShouldSkipQuickDuplicate(resolvedCasterKey, spellID) then
    return
  end

  self:SendPortalWarning(resolvedCasterName, resolvedCasterKey, spellID)
end

function NTP:OnUnitSpellcastSucceeded(unit, _, spellID)
  if not unit or IsSecretValue(unit) then
    return
  end

  if type(unit) ~= "string" then
    return
  end

  if unit ~= "player" and not unit:match("^party%d+$") and not unit:match("^raid%d+$") then
    return
  end

  local casterName = UnitName(unit)
  local casterGUID = UnitGUID(unit)
  if IsSecretValue(casterName) then
    casterName = nil
  end
  if IsSecretValue(casterGUID) then
    casterGUID = nil
  end

  if not casterName then
    return
  end

  self:HandlePortalCast(casterName, casterGUID or unit, spellID)
end

local function EnsureDefaults()
  NotThatPortalDB = NotThatPortalDB or {}
  CopyDefaults(NotThatPortalDB, DEFAULTS)

  NTP.DB = NotThatPortalDB
  NTP.DB.minimap = NTP.DB.minimap or { hide = false }
  NTP.DB.settings = NTP.DB.settings or {}
  NTP.DB.settings.window = NTP.DB.settings.window or { point = "CENTER", x = 0, y = 0 }
  NTP.DB.badPortals = NTP.DB.badPortals or {}

  NTP.DB.settings.warnTemplate = TrimMessage(NTP.DB.settings.warnTemplate)
  NTP.DB.settings.throttleSeconds = math.max(1, math.min(300, math.floor((ToNumberSafe(NTP.DB.settings.throttleSeconds) or 15) + 0.5)))
end

local EventFrame = CreateFrame("Frame")
NTP.EventFrame = EventFrame

EventFrame:RegisterEvent("ADDON_LOADED")
EventFrame:RegisterEvent("PLAYER_LOGIN")
EventFrame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")

EventFrame:SetScript("OnEvent", function(_, event, ...)
  if event == "ADDON_LOADED" then
    local addonName = ...
    if addonName ~= ADDON_NAME then
      return
    end

    EnsureDefaults()
    return
  end

  if event == "PLAYER_LOGIN" then
    if NTP.CreateMinimapButton then
      NTP:CreateMinimapButton()
    end
    return
  end

  if event == "UNIT_SPELLCAST_SUCCEEDED" then
    NTP:OnUnitSpellcastSucceeded(...)
    return
  end
end)

SLASH_NOTTHATPORTAL1 = "/notthatportal"
SLASH_NOTTHATPORTAL2 = "/ntp"
SlashCmdList.NOTTHATPORTAL = function(msg)
  msg = tostring(msg or ""):lower():gsub("^%s+", ""):gsub("%s+$", "")

  if msg == "minimap show" or msg == "show minimap" then
    NTP:SetMinimapHidden(false)
    print("|cffd9b96eNotThatPortal:|r Minimap button shown.")
    return
  end

  if msg == "minimap hide" or msg == "hide minimap" then
    NTP:SetMinimapHidden(true)
    print("|cffd9b96eNotThatPortal:|r Minimap button hidden.")
    return
  end

  if msg == "reset" then
    if NTP.ResetWindowPosition then
      NTP:ResetWindowPosition()
      print("|cffd9b96eNotThatPortal:|r Window position reset.")
    end
    return
  end

  if NTP.ToggleMainWindow then
    NTP:ToggleMainWindow()
  end
end
