-- Merfin Item Source Resolver (Core)
-- Provides: Merfin.GetItemDropString(itemID)
-- Requires data modules to populate:
--   Merfin.ItemSourceDB        : [itemID] = { { instance = "...", boss = "..." }, ... }
--   Merfin.ItemToSet           : [itemID] = setID
--   Merfin.SetTierBySetID      : [setID] = tierKey (string)   -- optional, for tier tokens
--   Merfin.TierTokenItems      : [tierKey] = { [equipSlotKey] = { tokenItemID1, tokenItemID2, ... } } -- optional
-- Optional:
--   Merfin.VendorPrices        : [itemID] = "priceKey:amount:priceKey:amount..."
--   Merfin.ProfessionCraft     : [itemID] = { profID, profID, ... }

_G.Merfin = _G.Merfin or {}
local Merfin = _G.Merfin

Merfin.DEBUG_ITEM_DROP = false

local function DebugPrint(...)
  if Merfin.DEBUG_ITEM_DROP then
    print(...)
  end
end

local function ColorizeTag(tag)
  if tag == "MC" then
    return "|cffff7a00MC|r"
  end

  if tag == "WB" then
    return "|cff00b7ffWB|r"
  end

  if tag == "VND" then
    return "|cff66cc33Vendor|r"
  end

  if tag == "CRF" then
    return "|cffffd100Craft|r"
  end

  return tag
end

local function NormalizeInstanceName(name)
  if not name or name == "" then
    return ""
  end

  name = name:gsub("^P%d+%s*", "")
  name = name:gsub("^Phase%s*%d+%s*", "")

  if name == "Molten Core" or name:match("^MoltenCore%d*$") then
    return "MC"
  end

  if name == "World bosses of Vanilla" or name:match("^WorldBosses%d*$") then
    return "WB"
  end

  return name
end

local function NormalizeBossName(name)
  if not name or name == "" then
    return ""
  end

  local map = {
    ["Baron Geddon"] = "Baron",
    ["Golemagg the Incinerator"] = "Golemagg",
    ["Sulfuron Harbinger"] = "Sulfuron",
    ["Majordomo Executus"] = "Majordomo",
    ["Lord Kazzak"] = "Kazzak",
  }

  if map[name] then
    return map[name]
  end

  name = name:gsub("^Lord%s+", "")
  name = name:gsub("^The%s+", "")
  name = name:gsub("%s+the%s+.*$", "")

  return name
end

local function FormatSourcesGrouped(sources, maxEntries)
  if not sources or #sources == 0 then
    return ""
  end

  local grouped = {}
  local order = {}

  local seenPair = {}
  local totalUniquePairs = 0

  for i = 1, #sources do
    local s = sources[i]
    local inst = NormalizeInstanceName(s.instance)
    local boss = NormalizeBossName(s.boss)

    if inst ~= "" and boss ~= "" then
      local key = inst .. "||" .. boss
      if not seenPair[key] then
        seenPair[key] = true
        totalUniquePairs = totalUniquePairs + 1

        local bucket = grouped[inst]
        if not bucket then
          bucket = { bosses = {}, seenBoss = {} }
          grouped[inst] = bucket
          order[#order + 1] = inst
        end

        if not bucket.seenBoss[boss] then
          bucket.seenBoss[boss] = true
          bucket.bosses[#bucket.bosses + 1] = boss
        end
      end
    end
  end

  if #order == 0 then
    return ""
  end

  local parts = {}
  local usedPairs = 0

  for i = 1, #order do
    local inst = order[i]
    local bucket = grouped[inst]
    local bosses = bucket and bucket.bosses or {}

    if #bosses > 0 then
      local take = #bosses
      if maxEntries then
        local remaining = maxEntries - usedPairs
        if remaining <= 0 then
          break
        end
        if take > remaining then
          take = remaining
        end
      end

      local bossText
      if take == #bosses then
        bossText = table.concat(bosses, ", ")
      else
        local tmp = {}
        for j = 1, take do
          tmp[#tmp + 1] = bosses[j]
        end
        bossText = table.concat(tmp, ", ")
      end

      local instOut = inst
      if instOut == "MC" or instOut == "WB" then
        instOut = ColorizeTag(instOut)
      end

      parts[#parts + 1] = string.format("%s: %s", instOut, bossText)
      usedPairs = usedPairs + take

      if maxEntries and usedPairs >= maxEntries then
        break
      end
    end
  end

  if #parts == 0 then
    return ""
  end

  if maxEntries and totalUniquePairs > usedPairs then
    parts[#parts + 1] = string.format("+%d more", totalUniquePairs - usedPairs)
  end

  return table.concat(parts, " | ")
end

local function GetEquipSlotKey(itemID)
  if not itemID or itemID == 0 then
    return nil
  end

  local equipLoc
  if GetItemInfoInstant then
    local _, _, _, loc = GetItemInfoInstant(itemID)
    equipLoc = loc
  end

  if not equipLoc or equipLoc == "" then
    return nil
  end

  -- Map WoW equipLoc to our tier token slot keys
  local map = Merfin.EquipLocToTierSlot
    or {
      INVTYPE_HEAD = "HEAD",
      INVTYPE_SHOULDER = "SHOULDER",
      INVTYPE_CHEST = "CHEST",
      INVTYPE_ROBE = "CHEST",
      INVTYPE_HAND = "HANDS",
      INVTYPE_LEGS = "LEGS",
    }

  return map[equipLoc] or equipLoc
end

local function ParseVendorPriceString(priceStr)
  if not priceStr or priceStr == "" then
    return nil
  end

  local out = {}
  for key, amount in priceStr:gmatch("([^:]+):(%d+)") do
    out[#out + 1] = { key = key, amount = tonumber(amount) or 0 }
  end

  return out
end

local function FormatVendorSource(itemID)
  local prices = Merfin.VendorPrices
  if not prices then
    return ""
  end

  local priceStr = prices[itemID]
  if not priceStr then
    return ""
  end

  local parsed = ParseVendorPriceString(priceStr)
  if not parsed or #parsed == 0 then
    return ""
  end

  local keyInfo = Merfin.VendorPriceKeyInfo or {}
  local parts = {}

  for i = 1, #parsed do
    local p = parsed[i]
    local info = keyInfo[p.key]
    local name = (info and info.name) or p.key
    local abbr = (info and info.abbr) or name
    local color = (info and info.color) or nil

    local piece
    if p.key == "money" then
      if GetCoinTextureString then
        piece = GetCoinTextureString(p.amount)
      else
        local gold = math.floor(p.amount / 10000)
        local silver = math.floor((p.amount % 10000) / 100)
        local copper = p.amount % 100
        piece = string.format("%dg %ds %dc", gold, silver, copper)
      end
    else
      piece = string.format("%s x%d", abbr, p.amount)
    end

    if color then
      piece = string.format("|cff%s%s|r", color, piece)
    end

    parts[#parts + 1] = piece
  end

  local tag = ColorizeTag("VND")
  return string.format("%s: %s", tag, table.concat(parts, ", "))
end

local function FormatProfessionSource(itemID)
  local craft = Merfin.ProfessionCraft
  if not craft then
    return ""
  end

  local profIDs = craft[itemID]
  if not profIDs or #profIDs == 0 then
    return ""
  end

  local profInfo = Merfin.ProfessionInfo or {}
  local parts = {}

  for i = 1, #profIDs do
    local id = profIDs[i]
    local info = profInfo[id]
    local abbr = (info and info.abbr) or tostring(id)
    local color = (info and info.color) or nil
    local piece = abbr
    if color then
      piece = string.format("|cff%s%s|r", color, piece)
    end
    parts[#parts + 1] = piece
  end

  local tag = ColorizeTag("CRF")
  return string.format("%s: %s", tag, table.concat(parts, ", "))
end

local function ResolveTierTokenSources(itemID)
  local itemToSet = Merfin.ItemToSet
  local setTier = Merfin.SetTierBySetID
  local tierTokens = Merfin.TierTokenItems
  local sourceDB = Merfin.ItemSourceDB

  if not itemToSet or not setTier or not tierTokens or not sourceDB then
    return nil
  end

  local setID = itemToSet[itemID]
  if not setID then
    return nil
  end

  local tierKey = setTier[setID]
  if not tierKey then
    return nil
  end
  DebugPrint("tier key itemID", tierKey)
  local slotKey = GetEquipSlotKey(itemID)
  DebugPrint("slotKey:", slotKey)
  if not slotKey then
    return nil
  end

  local tokenByItem = Merfin.TierTokenByItemID
  local tokenListByItem = tokenByItem and tokenByItem[itemID]
  if tokenListByItem then
    local tokenList = type(tokenListByItem) == "table" and tokenListByItem or { tokenListByItem }

    local out = {}
    for i = 1, #tokenList do
      local tokenID = tokenList[i]
      local src = sourceDB[tokenID]
      if src and #src > 0 then
        for j = 1, #src do
          out[#out + 1] = src[j]
        end
      end
    end

    if #out > 0 then
      return out
    end
  end

  local tokenList = tierTokens[tierKey] and tierTokens[tierKey][slotKey]
  DebugPrint("tokenlist", tierTokens[tierKey])
  if not tokenList or #tokenList == 0 then
    local overrideByTier = Merfin.TierSourceOverrideByTier and Merfin.TierSourceOverrideByTier[tierKey]
    local override = overrideByTier and overrideByTier[slotKey]
    if override and #override > 0 then
      return override
    end
    return nil
  end

  local out = {}
  for i = 1, #tokenList do
    local tokenID = tokenList[i]
    local src = sourceDB[tokenID]
    if src and #src > 0 then
      for j = 1, #src do
        out[#out + 1] = src[j]
      end
    end
  end

  return out
end

local function GetDarkmoonSuffix(itemID)
  local name = GetItemInfo(itemID)
  if not name then
    return nil
  end
  name = name:gsub("：", ":")
  return name:match("^.*:%s*(.+)$") or name
end

local DARKMOON_IDS = {
  [42989] = true,
  [42988] = true,
  [42990] = true,
  [42987] = true,
  [44253] = true,
  [44254] = true,
  [44255] = true,
}

function Merfin.GetItemDropString(itemID)
  DebugPrint("---- GetItemDropString ----")
  DebugPrint("itemID:", itemID)

  if type(itemID) ~= "number" then
    DebugPrint("Abort: itemID is not a number")
    return ""
  end

  local sourceDB = Merfin.ItemSourceDB or {}

  local direct = sourceDB[itemID]
  DebugPrint("Direct sources:", direct and #direct or nil)

  local sources = direct

  if not sources or #sources == 0 then
    local tierSources = ResolveTierTokenSources(itemID)
    DebugPrint("Tier token sources:", tierSources and #tierSources or nil)
    if tierSources and #tierSources > 0 then
      sources = tierSources
    end
  end

  local outParts = {}

  local lootText = ""
  if sources and #sources > 0 then
    lootText = FormatSourcesGrouped(sources, 50)
    if lootText ~= "" then
      outParts[#outParts + 1] = lootText
    end
  end

  if DARKMOON_IDS[itemID] then
    local suffix = GetDarkmoonSuffix(itemID)
    if suffix then
      return ("Darkmoon Faire (%s)"):format(suffix)
    end
    return "Darkmoon Faire"
  end

  local faction = Merfin.FactionSourceDB and Merfin.FactionSourceDB[itemID]
  if faction then
    local factionName = GetFactionInfoByID(faction.factionID)
    local repName = Merfin.REPUTATION_NAMES[faction.reputation] or "Unknown"
    if factionName then
      return string.format("%s (%s)", factionName, repName)
    end
  end

  local vendorText = FormatVendorSource(itemID)
  DebugPrint("Vendor:", vendorText ~= "" and vendorText or nil)
  if vendorText ~= "" then
    outParts[#outParts + 1] = vendorText
  end

  local profText = FormatProfessionSource(itemID)
  DebugPrint("Profession:", profText ~= "" and profText or nil)
  if profText ~= "" then
    outParts[#outParts + 1] = profText
  end

  if #outParts == 0 then
    DebugPrint("Abort: no sources found")
    return ""
  end

  return table.concat(outParts, " | ")
end
