local DR = select(2, ...)

local TradeWatcher = {
  activeTrade = false,
  tradeTarget = nil,
  pendingTradeTarget = nil,
  tradeItems = {},
}

local TRADE_ADD_DELAY = 0.15

local function getGlobal(name)
  return rawget(_G, name)
end

local isSecretValue = rawget(_G, "issecretvalue")

local function scrubSecret(value)
  if isSecretValue and isSecretValue(value) then
    return nil
  end
  return value
end

local function clearState(self)
  self.activeTrade = false
  self.tradeTarget = nil
  wipe(self.tradeItems)
end

local function captureTradeTarget()
  local TradeFrameRecipientNameText = getGlobal("TradeFrameRecipientNameText")
  local text = TradeFrameRecipientNameText and TradeFrameRecipientNameText.GetText and TradeFrameRecipientNameText:GetText()
  text = scrubSecret(text)
  if text and text ~= "" then
    -- Cross-realm targets show as "Name(*)"
    return (text:gsub("%(%*%)$", ""))
  end

  local name, realm = UnitName("NPC")
  name = scrubSecret(name)
  realm = scrubSecret(realm)
  if not name or name == "" then
    return nil
  end

  if realm and realm ~= "" then
    return string.format("%s-%s", name, realm)
  end

  return name
end

local function captureTradeItems()
  local out = {}
  local MAX_TRADE_ITEMS = getGlobal("MAX_TRADE_ITEMS")
  local maxSlots = (MAX_TRADE_ITEMS or 7) - 1
  if maxSlots < 1 then
    maxSlots = 6
  end

  for i = 1, maxSlots do
    local link = GetTradePlayerItemLink and GetTradePlayerItemLink(i)
    if link then
      out[#out + 1] = link
    end
  end

  return out
end

--- Called right before InitiateTrade so we still know the intended target
--- even if Blizzard hides the trade frame name (secret values, race on show).
function TradeWatcher:SetPendingTradeTarget(display)
  self.pendingTradeTarget = display
end

local function findItemInBags(itemLink, usedSlots)
  if not (C_Container and C_Container.GetContainerNumSlots and C_Container.GetContainerItemInfo) then
    return nil
  end
  if not (C_Item and C_Item.GetItemInfoInstant) then
    return nil
  end

  local targetId = select(1, C_Item.GetItemInfoInstant(itemLink))
  if not targetId then
    return nil
  end

  for bag = 0, (getGlobal("NUM_BAG_SLOTS") or 4) do
    for slot = 1, (C_Container.GetContainerNumSlots(bag) or 0) do
      local key = bag .. ":" .. slot
      if not usedSlots[key] then
        local info = C_Container.GetContainerItemInfo(bag, slot)
        local link = info and scrubSecret(info.hyperlink)
        if link and not info.isLocked then
          if select(1, C_Item.GetItemInfoInstant(link)) == targetId then
            return bag, slot, key
          end
        end
      end
    end
  end
  return nil
end

--- Places every pending deferred-trade item for the current trade target into
--- the trade window (staggered - the client can't take them all at once),
--- then optionally tries to accept.
function TradeWatcher:AddPendingItemsToTrade()
  if not self.activeTrade or not self.tradeTarget then
    return
  end

  local entries = DR:Require("Services.DeferredTrade"):GetPendingForTarget(self.tradeTarget)
  if #entries == 0 then
    return
  end

  local maxSlots = (getGlobal("MAX_TRADE_ITEMS") or 7) - 1
  local usedSlots = {}
  local queued = 0

  for _, entry in ipairs(entries) do
    if queued >= maxSlots then
      break
    end
    queued = queued + 1
    local tradeSlot = queued
    DR:ScheduleTimer(function()
      if not self.activeTrade then
        return
      end
      local bag, slot, key = findItemInBags(entry.itemLink, usedSlots)
      if not bag then
        DR:Print("Could not find " .. tostring(entry.itemLink) .. " in your bags")
        return
      end
      usedSlots[key] = true
      ClearCursor()
      C_Container.PickupContainerItem(bag, slot)
      if ClickTradeButton then
        ClickTradeButton(tradeSlot)
      end
    end, TRADE_ADD_DELAY * queued)
  end

  DR:Print(string.format("Adding %d item(s) to the trade window for %s", queued, tostring(self.tradeTarget)))

  if DR.db.profile.autoAcceptTrade then
    -- Adding an item resets acceptance, so accept only after the last add
    -- settles. AcceptTrade is hardware-protected: if Blizzard blocks the
    -- call, nothing happens and the Trade button must be clicked manually.
    DR:ScheduleTimer(function()
      if self.activeTrade and AcceptTrade then
        AcceptTrade()
      end
    end, TRADE_ADD_DELAY * queued + 0.5)
  end
end

function TradeWatcher:OnTradeShow()
  self.activeTrade = true
  self.tradeTarget = captureTradeTarget() or self.pendingTradeTarget
  self.pendingTradeTarget = nil
  wipe(self.tradeItems)

  if DR.db.profile.autoTradeItems then
    self:AddPendingItemsToTrade()
  end
end

function TradeWatcher:OnTradeClosed()
  clearState(self)
end

function TradeWatcher:OnTradeAcceptUpdate(playerAccepted, targetAccepted)
  if not DR.db.profile.autoMarkTradedFromTrade or not self.activeTrade then
    return
  end

  if playerAccepted == 1 or targetAccepted == 1 then
    self.tradeItems = captureTradeItems()
  end
end

function TradeWatcher:OnUiInfoMessage(messageType, message)
  if not DR.db.profile.autoMarkTradedFromTrade then
    return
  end

  if not self.activeTrade then
    return
  end

  -- Prefer the enum; fall back to matching the localized "Trade complete."
  -- string in case the LE_ constant is removed in a future patch.
  local tradeCompleteCode = getGlobal("LE_GAME_ERR_TRADE_COMPLETE")
  local isComplete = tradeCompleteCode ~= nil and messageType == tradeCompleteCode
  if not isComplete then
    local completeText = getGlobal("ERR_TRADE_COMPLETE")
    isComplete = completeText ~= nil and message == completeText
  end
  if not isComplete then
    return
  end

  if #self.tradeItems == 0 then
    clearState(self)
    return
  end

  local deferred = DR:Require("Services.DeferredTrade")
  local audit = DR:Require("Services.Audit")

  for _, itemLink in ipairs(self.tradeItems) do
    local ok, id, matchReason = deferred:MarkTradedByItem(itemLink, self.tradeTarget, "auto_trade_complete")
    if ok then
      audit:Append("DEFERRED_TRADE_AUTO_TRADED", {
        id = id,
        itemLink = itemLink,
        target = self.tradeTarget,
        matchReason = matchReason,
      })
    else
      audit:Append("DEFERRED_TRADE_AUTO_TRADE_MISS", {
        itemLink = itemLink,
        target = self.tradeTarget,
        reason = matchReason,
      })
    end
  end

  clearState(self)
end

function TradeWatcher:OnInitialize()
  clearState(self)

  DR:RegisterEvent("TRADE_SHOW", function()
    self:OnTradeShow()
  end)
  DR:RegisterEvent("TRADE_CLOSED", function()
    self:OnTradeClosed()
  end)
  DR:RegisterEvent("TRADE_ACCEPT_UPDATE", function(_, playerAccepted, targetAccepted)
    self:OnTradeAcceptUpdate(playerAccepted, targetAccepted)
  end)
  DR:RegisterEvent("UI_INFO_MESSAGE", function(_, messageType, message)
    self:OnUiInfoMessage(messageType, message)
  end)
end

DR:RegisterModule("Modules.TradeWatcher", TradeWatcher)