local ADDON_NAME = ...
local AP = _G.AmmePlayed or {}
_G.AmmePlayed = AP

local MIN_REFRESH_INTERVAL = 5

local REGION_LABELS = {
  [1] = "US",
  [2] = "KR",
  [3] = "EU",
  [4] = "TW",
  [5] = "CN",
}

local function SafeNumber(v, fallback)
  v = tonumber(v)
  if not v then return fallback or 0 end
  return v
end

local function NormalizeToken(raw)
  raw = tostring(raw or ""):upper()
  raw = raw:gsub("[^A-Z0-9]", "")
  return raw
end

local RACE_TOKEN_ALIASES = {
  UNDEAD = "SCOURGE",
  ZANDALARITROLLS = "ZANDALARITROLL",
  KULTIRANHUMAN = "KULTIRAN",
  KULTIRAS = "KULTIRAN",
  MAGHAR = "MAGHARORC",
  LIGHTFORGED = "LIGHTFORGEDDRAENEI",
  EARTHENDWARF = "EARTHEN",
}

local RACE_TOKEN_TO_ID = {
  HUMAN = 1,
  ORC = 2,
  DWARF = 3,
  NIGHTELF = 4,
  SCOURGE = 5,
  TAUREN = 6,
  GNOME = 7,
  TROLL = 8,
  GOBLIN = 9,
  BLOODELF = 10,
  DRAENEI = 11,
  WORGEN = 22,
  PANDAREN = 24,
  NIGHTBORNE = 27,
  HIGHMOUNTAINTAUREN = 28,
  VOIDELF = 29,
  LIGHTFORGEDDRAENEI = 30,
  ZANDALARITROLL = 31,
  KULTIRAN = 32,
  DARKIRONDWARF = 34,
  VULPERA = 35,
  MAGHARORC = 36,
  MECHAGNOME = 37,
  DRACTHYR = 52,
  EARTHEN = 84,
}

local RACE_TOKEN_FALLBACK_NAMES = {
  HUMAN = "Human",
  ORC = "Orc",
  DWARF = "Dwarf",
  NIGHTELF = "Night Elf",
  SCOURGE = "Undead",
  TAUREN = "Tauren",
  GNOME = "Gnome",
  TROLL = "Troll",
  GOBLIN = "Goblin",
  BLOODELF = "Blood Elf",
  DRAENEI = "Draenei",
  WORGEN = "Worgen",
  PANDAREN = "Pandaren",
  NIGHTBORNE = "Nightborne",
  HIGHMOUNTAINTAUREN = "Highmountain Tauren",
  VOIDELF = "Void Elf",
  LIGHTFORGEDDRAENEI = "Lightforged Draenei",
  ZANDALARITROLL = "Zandalari Troll",
  KULTIRAN = "Kul Tiran",
  DARKIRONDWARF = "Dark Iron Dwarf",
  VULPERA = "Vulpera",
  MAGHARORC = "Mag'har Orc",
  MECHAGNOME = "Mechagnome",
  DRACTHYR = "Dracthyr",
  EARTHEN = "Earthen",
}

local function GetInterfaceNumber()
  local v = select(4, GetBuildInfo())
  return SafeNumber(v, 0)
end

local function GetEffectiveMaxLevel()
  local interface = GetInterfaceNumber()
  if interface >= 120001 then
    return 90
  end
  if interface >= 120000 then
    return 80
  end
  -- Fallback for unknown/future builds until explicit caps are added.
  return 90
end

local function EnsureDefaults()
  AmmePlayedDB = AmmePlayedDB or {}
  local db = AmmePlayedDB

  db.minimap = db.minimap or { hide = false }
  db.characters = db.characters or {}
  db.settings = db.settings or {
    selectedTab = 1,
    window = { x = 0, y = 0, point = "CENTER" },
  }
  db.refresh = db.refresh or {
    pending = false,
    lastRequest = 0,
    lastReason = "",
    lastResult = 0,
  }

  AP.DB = db
end

function AP:GetCurrentRegionLabel()
  local region = GetCurrentRegion and GetCurrentRegion()
  return REGION_LABELS[region] or tostring(region or "0")
end

function AP:GetAccountIdentifier()
  local cvarName = GetCVar and GetCVar("accountName")
  if cvarName and cvarName ~= "" then
    return cvarName
  end

  if C_BattleNet and C_BattleNet.GetAccountInfo then
    local info = C_BattleNet.GetAccountInfo()
    if info then
      if info.accountName and info.accountName ~= "" then
        return tostring(info.accountName)
      end
      if info.bnetAccountID then
        return tostring(info.bnetAccountID)
      end
    end
  end

  return "UNKNOWN"
end

function AP:GetCharacterKey()
  local region = self:GetCurrentRegionLabel()
  local realm = GetNormalizedRealmName() or GetRealmName() or "UnknownRealm"
  local name = UnitName("player") or "Unknown"
  return string.format("%s|%s|%s", region, realm, name)
end

function AP:GetClassColor(classTag)
  local c = RAID_CLASS_COLORS and RAID_CLASS_COLORS[classTag or ""]
  if c then
    return c.r, c.g, c.b
  end
  return 0.85, 0.82, 0.72
end

function AP:GetLocalizedClassName(classTag, fallback)
  classTag = tostring(classTag or ""):upper()
  local male = _G.LOCALIZED_CLASS_NAMES_MALE and _G.LOCALIZED_CLASS_NAMES_MALE[classTag]
  if male and male ~= "" then
    return male
  end
  local female = _G.LOCALIZED_CLASS_NAMES_FEMALE and _G.LOCALIZED_CLASS_NAMES_FEMALE[classTag]
  if female and female ~= "" then
    return female
  end
  return fallback or classTag or "Unknown"
end

function AP:GetLocalizedRaceName(raceTag, fallback)
  local token = NormalizeToken(raceTag)
  token = RACE_TOKEN_ALIASES[token] or token

  local raceID = RACE_TOKEN_TO_ID[token]
  if raceID and C_CreatureInfo then
    local getRaceInfo = C_CreatureInfo.GetRaceInfo or C_CreatureInfo.GetRaceInfoByID
    local info = getRaceInfo and getRaceInfo(raceID)
    if info and info.raceName and info.raceName ~= "" then
      return info.raceName
    end
  end

  local fallbackName = RACE_TOKEN_FALLBACK_NAMES[token]
  if fallbackName and fallbackName ~= "" then
    return fallbackName
  end

  return fallback or token or "Unknown"
end

function AP:GetClassIconCoords(classTag)
  local t = CLASS_ICON_TCOORDS and CLASS_ICON_TCOORDS[classTag or ""]
  if t then
    return t[1], t[2], t[3], t[4]
  end
  return 0, 1, 0, 1
end

function AP:FormatDuration(seconds)
  seconds = math.max(0, SafeNumber(seconds, 0))
  local days = math.floor(seconds / 86400)
  local hours = math.floor((seconds % 86400) / 3600)
  local mins = math.floor((seconds % 3600) / 60)
  if days > 0 then
    return string.format("%dd %dh", days, hours)
  end
  if hours > 0 then
    return string.format("%dh %dm", hours, mins)
  end
  return string.format("%dm", mins)
end

function AP:FormatDurationLong(seconds)
  seconds = math.max(0, SafeNumber(seconds, 0))
  local days = math.floor(seconds / 86400)
  local hours = math.floor((seconds % 86400) / 3600)
  local mins = math.floor((seconds % 3600) / 60)
  return string.format("%dd %dh %dm", days, hours, mins)
end

function AP:GetCharacterMaxLevelPlayed(character)
  local sum = 0
  local caps = character and character.maxLevelCaps
  if type(caps) == "table" then
    for _, v in pairs(caps) do
      sum = sum + SafeNumber(v, 0)
    end
  end
  return sum
end

function AP:CaptureCurrentCharacter(totalSeconds, levelSeconds, reason)
  local key = self:GetCharacterKey()
  local db = self.DB
  if not db then return end

  local classLocalized, classTag = UnitClass("player")
  local raceLocalized, raceTag = UnitRace("player")

  local current = db.characters[key] or {}
  current.key = key
  current.region = self:GetCurrentRegionLabel()
  current.realm = GetNormalizedRealmName() or GetRealmName() or "UnknownRealm"
  current.name = UnitName("player") or "Unknown"
  current.class = classLocalized or "Unknown"
  current.classTag = classTag or "PRIEST"
  current.race = raceLocalized or "Unknown"
  current.raceTag = raceTag or "Unknown"
  current.level = UnitLevel("player") or 0
  current.faction = UnitFactionGroup("player") or "Neutral"
  current.lastUpdated = time()
  current.lastReason = reason or "unknown"

  totalSeconds = math.max(0, SafeNumber(totalSeconds, 0))
  levelSeconds = math.max(0, SafeNumber(levelSeconds, 0))

  current.totalPlayedSeconds = math.max(totalSeconds, SafeNumber(current.totalPlayedSeconds, 0))
  current.currentLevelPlayedSeconds = levelSeconds

  current.maxLevelCaps = current.maxLevelCaps or {}

  local maxLevel = GetEffectiveMaxLevel()
  if current.level >= maxLevel then
    local capKey = tostring(maxLevel)
    local existing = SafeNumber(current.maxLevelCaps[capKey], 0)
    if levelSeconds > existing then
      current.maxLevelCaps[capKey] = levelSeconds
    end
  end

  current.maxLevelPlayedSeconds = self:GetCharacterMaxLevelPlayed(current)
  db.characters[key] = current

  if self.RefreshUI then
    self:RefreshUI()
  end
end

function AP:GetAllCharacters()
  local out = {}
  local db = self.DB
  if not db or not db.characters then
    return out
  end

  for key, data in pairs(db.characters) do
    data.key = key
    data.totalPlayedSeconds = SafeNumber(data.totalPlayedSeconds, 0)
    data.maxLevelPlayedSeconds = self:GetCharacterMaxLevelPlayed(data)
    table.insert(out, data)
  end

  table.sort(out, function(a, b)
    if a.totalPlayedSeconds == b.totalPlayedSeconds then
      return (a.name or "") < (b.name or "")
    end
    return a.totalPlayedSeconds > b.totalPlayedSeconds
  end)

  return out
end

local function ShallowCopyCharacterData(src)
  if type(src) ~= "table" then return nil end
  local out = {}
  for k, v in pairs(src) do
    if k == "maxLevelCaps" and type(v) == "table" then
      local caps = {}
      for ck, cv in pairs(v) do
        caps[ck] = cv
      end
      out[k] = caps
    else
      out[k] = v
    end
  end
  return out
end

function AP:DeleteCharacterData(key)
  local db = self.DB
  if not db or not db.characters or not key then
    return false
  end

  local currentKey = self:GetCharacterKey()
  local wasCurrent = (key == currentKey)
  local currentSnapshot = wasCurrent and ShallowCopyCharacterData(db.characters[currentKey]) or nil

  db.characters[key] = nil

  if wasCurrent then
    if currentSnapshot then
      db.characters[currentKey] = currentSnapshot
    else
      self:CaptureCurrentCharacter(0, 0, "character-data-reseed")
    end
  end

  if self.RefreshUI then
    self:RefreshUI()
  end
  return true
end

function AP:ClearAllCharacters()
  local db = self.DB
  if not db then return false end
  db.characters = db.characters or {}

  local currentKey = self:GetCharacterKey()
  local currentSnapshot = ShallowCopyCharacterData(db.characters[currentKey])

  db.characters = {}
  if currentSnapshot then
    db.characters[currentKey] = currentSnapshot
  else
    self:CaptureCurrentCharacter(0, 0, "character-data-clear-reseed")
  end

  if self.RefreshUI then
    self:RefreshUI()
  end
  return true
end

function AP:GetSummaryStats()
  local chars = self:GetAllCharacters()
  local stats = {
    characters = chars,
    characterCount = #chars,
    totalPlayedSeconds = 0,
    totalMaxLevelSeconds = 0,
    classTotals = {},
    raceTotals = {},
    favoriteClassTag = nil,
    favoriteClassName = nil,
    favoriteRace = nil,
    favoriteClassSeconds = 0,
    favoriteRaceSeconds = 0,
    topCharacter = chars[1],
  }

  for _, c in ipairs(chars) do
    stats.totalPlayedSeconds = stats.totalPlayedSeconds + SafeNumber(c.totalPlayedSeconds, 0)
    stats.totalMaxLevelSeconds = stats.totalMaxLevelSeconds + SafeNumber(c.maxLevelPlayedSeconds, 0)

    local classTag = c.classTag or "UNKNOWN"
    local className = self:GetLocalizedClassName(classTag, c.class or classTag)
    local raceName = c.race or "Unknown"

    stats.classTotals[classTag] = stats.classTotals[classTag] or { seconds = 0, name = className }
    stats.classTotals[classTag].seconds = stats.classTotals[classTag].seconds + SafeNumber(c.totalPlayedSeconds, 0)

    stats.raceTotals[raceName] = stats.raceTotals[raceName] or 0
    stats.raceTotals[raceName] = stats.raceTotals[raceName] + SafeNumber(c.totalPlayedSeconds, 0)
  end

  for classTag, info in pairs(stats.classTotals) do
    if info.seconds > stats.favoriteClassSeconds then
      stats.favoriteClassSeconds = info.seconds
      stats.favoriteClassTag = classTag
      stats.favoriteClassName = info.name
    end
  end

  for raceName, seconds in pairs(stats.raceTotals) do
    if seconds > stats.favoriteRaceSeconds then
      stats.favoriteRaceSeconds = seconds
      stats.favoriteRace = raceName
    end
  end

  stats.hasEnoughForNarrative = (stats.characterCount >= 3 and stats.totalPlayedSeconds >= (40 * 3600))
  return stats
end

function AP:GetGroupedData(groupBy, metric)
  local chars = self:GetAllCharacters()
  local groupsByKey = {}

  for _, c in ipairs(chars) do
    local value = SafeNumber(c.totalPlayedSeconds, 0)
    if metric == "max" then
      value = SafeNumber(c.maxLevelPlayedSeconds, 0)
    end

    if value > 0 then
      local key, label
      if groupBy == "race" then
        key = NormalizeToken(c.raceTag or c.race)
        if key == "" then
          key = "UNKNOWN"
        end
        key = RACE_TOKEN_ALIASES[key] or key
        label = self:GetLocalizedRaceName(key, c.race or c.raceTag or "Unknown")
      else
        key = c.classTag or "UNKNOWN"
        label = self:GetLocalizedClassName(key, c.class or key)
      end

      local g = groupsByKey[key]
      if not g then
        g = { key = key, label = label, totalSeconds = 0, segments = {} }
        groupsByKey[key] = g
      end

      local segment = {
        name = c.name,
        realm = c.realm,
        level = c.level,
        class = self:GetLocalizedClassName(c.classTag or "UNKNOWN", c.class),
        classTag = c.classTag,
        race = c.race,
        raceTag = c.raceTag,
        totalPlayedSeconds = c.totalPlayedSeconds,
        maxLevelPlayedSeconds = c.maxLevelPlayedSeconds,
        value = value,
      }

      g.totalSeconds = g.totalSeconds + value
      table.insert(g.segments, segment)
    end
  end

  local groups = {}
  for _, g in pairs(groupsByKey) do
    table.sort(g.segments, function(a, b)
      if a.value == b.value then
        return (a.name or "") < (b.name or "")
      end
      return a.value > b.value
    end)
    table.insert(groups, g)
  end

  table.sort(groups, function(a, b)
    if a.totalSeconds == b.totalSeconds then
      return (a.label or "") < (b.label or "")
    end
    return a.totalSeconds > b.totalSeconds
  end)

  return groups
end

function AP:RequestPlayedTime(reason, force)
  if not RequestTimePlayed then
    return false
  end

  local db = self.DB
  if not db then return false end

  local now = GetTime()
  local refresh = db.refresh
  if not force then
    if refresh.pending then
      return false
    end
    if (now - SafeNumber(refresh.lastRequest, 0)) < MIN_REFRESH_INTERVAL then
      return false
    end
  end

  refresh.pending = true
  refresh.lastRequest = now
  refresh.lastReason = reason or "manual"

  RequestTimePlayed()
  return true
end

local EventFrame = CreateFrame("Frame")
EventFrame:RegisterEvent("ADDON_LOADED")
EventFrame:RegisterEvent("PLAYER_LOGIN")
EventFrame:RegisterEvent("TIME_PLAYED_MSG")

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 AP.CreateMinimapButton then
      AP:CreateMinimapButton()
    end

    C_Timer.After(2, function()
      AP:RequestPlayedTime("login", true)
    end)
    return
  end

  if event == "TIME_PLAYED_MSG" then
    local totalSeconds, levelSeconds = ...
    if AP.DB and AP.DB.refresh then
      AP.DB.refresh.pending = false
      AP.DB.refresh.lastResult = GetTime()
    end

    AP:CaptureCurrentCharacter(totalSeconds, levelSeconds, AP.DB and AP.DB.refresh and AP.DB.refresh.lastReason or "played")
    return
  end
end)

SLASH_AMMEPLAYED1 = "/ammeplayed"
SLASH_AMMEPLAYED2 = "/aplayed"
SlashCmdList["AMMEPLAYED"] = function(msg)
  msg = (msg or ""):lower():gsub("^%s+", ""):gsub("%s+$", "")

  if msg == "refresh" then
    local ok = AP:RequestPlayedTime("slash-refresh", true)
    if ok then
      print("|cffd9b96eAmmePlayed:|r Requested /played update for current character.")
    else
      print("|cffd9b96eAmmePlayed:|r Please wait a few seconds before requesting again.")
    end
    return
  end

  if msg == "open" or msg == "show" then
    if AP.ToggleMainWindow then
      if not AP.MainFrame or not AP.MainFrame:IsShown() then
        AP:ToggleMainWindow()
      end
    end
    return
  end

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

  if msg == "reset window" then
    if AP.ResetWindowPosition then
      AP:ResetWindowPosition()
      print("|cffd9b96eAmmePlayed:|r Window position reset to center.")
    end
    return
  end

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