local DR = select(2, ...)
local constants = DR.Modules["Core.Constants"]

--- Raider window: every active loot session appears as a card with the item
--- icon/link, a countdown, and per-item roll buttons. Auto-opens when a
--- session starts.
local RaiderFrame = {
  frame = nil,
  cards = {},
  offset = 0,
  dismissed = {},
}
LibStub("AceEvent-3.0"):Embed(RaiderFrame)

local CARD_HEIGHT = 78
local CARD_SPACING = 6
local VISIBLE_CARDS = 4

-- Ordered by contention priority: BiS > 4pc > 2pc > Upgrade > OS > TM.
-- Need rolls are multiplied by roll power; OS/TM are standard flat rolls.
local CATEGORY_BUTTONS = {
  { id = constants.ROLL.CATEGORY.BIS, label = "BIS", width = 44, tip = "Best in Slot (roll 1-200, uses roll power)" },
  { id = constants.ROLL.CATEGORY.TIER_4P, label = "4p", width = 34, tip = "Completes 4-piece (roll 1-200, uses roll power)" },
  { id = constants.ROLL.CATEGORY.TIER_2P, label = "2p", width = 34, tip = "Completes 2-piece (roll 1-200, uses roll power)" },
  { id = constants.ROLL.CATEGORY.UPGRADE, label = "Up", width = 38, tip = "Major upgrade (roll 1-200, uses roll power)" },
  { id = constants.ROLL.CATEGORY.OFFSPEC, label = "OS", width = 36, tip = "Offspec / greed (roll 1-100, standard roll)" },
  { id = constants.ROLL.CATEGORY.TMOG, label = "TM", width = 36, tip = "Transmog / cosmetic (roll 1-50, standard roll)" },
  { id = constants.ROLL.CATEGORY.PASS, label = "Pass", width = 46, tip = "Pass on this item", danger = true },
}

local CATEGORY_NAMES = {
  [0] = "Pass",
  [1] = "Tmog",
  [2] = "Offspec",
  [3] = "Upgrade",
  [4] = "BIS",
  [5] = "2-piece",
  [6] = "4-piece",
}

local function Kit()
  return DR:Require("UI.Kit")
end

local function nowTs()
  return GetServerTime and GetServerTime() or time()
end

local function itemIcon(itemLink)
  if itemLink and C_Item and C_Item.GetItemInfoInstant then
    local ok, _, _, _, _, icon = pcall(C_Item.GetItemInfoInstant, itemLink)
    if ok and icon then
      return icon
    end
  end
  return 134400 -- question mark
end

local function findUnitByGuid(guid)
  if not guid then
    return nil
  end
  if UnitGUID("player") == guid then
    return "player"
  end
  if IsInRaid() then
    for i = 1, (GetNumGroupMembers() or 0) do
      local unit = "raid" .. i
      if UnitExists(unit) and UnitGUID(unit) == guid then
        return unit
      end
    end
  elseif IsInGroup() then
    for i = 1, 4 do
      local unit = "party" .. i
      if UnitExists(unit) and UnitGUID(unit) == guid then
        return unit
      end
    end
  end
  return nil
end

local function myStatusLine(session)
  local guid = UnitGUID("player")
  local submit = guid and session.submissionsByGuid and session.submissionsByGuid[guid]
  if not submit then
    return "Awaiting your roll", "dim"
  end
  if submit.category == constants.ROLL.CATEGORY.PASS then
    return "You passed", "dim"
  end
  if submit.pending or not submit.baseRoll then
    return string.format("Sent %s roll to leader", CATEGORY_NAMES[submit.category] or "?"), "accent"
  end
  -- Lead with the score that competes; power detail only for need rolls.
  local engine = DR:Require("Domain.RollEngine")
  local weight = submit.weight or 100
  local effective = engine:ComputeScoreFor(submit.category, submit.baseRoll, weight)
  if engine:IsWeightedCategory(submit.category) then
    return string.format(
      "You rolled %s: %d (raw %d at %s%% power)",
      CATEGORY_NAMES[submit.category] or "?",
      effective,
      submit.baseRoll,
      engine:FormatWeight(weight)
    ), "accent"
  end
  return string.format(
    "You rolled %s: %d",
    CATEGORY_NAMES[submit.category] or "?",
    effective
  ), "accent"
end

local function sessionStatusLine(session)
  local S = constants.SESSION.STATE
  if session.state == S.COLLECTING then
    local remaining = math.max((session.timeoutAt or 0) - nowTs(), 0)
    return string.format("Collecting rolls - %ds left", remaining)
  end
  if session.state == S.ROLLOFF then
    return string.format("Roll-off round %d in progress", session.rollOff and session.rollOff.round or 0)
  end
  if session.state == S.RESOLVING then
    return "Resolving..."
  end
  if session.state == S.AWARDED and session.winner then
    local display = DR:Require("Services.Identity"):GetDisplayByGuid(session.winner.guid) or session.winner.guid
    return string.format("Winner: %s (%d) - awaiting trade/reward", display, session.winner.effectiveScore or 0)
  end
  if session.state == S.REWARDED and session.winner then
    local display = DR:Require("Services.Identity"):GetDisplayByGuid(session.winner.guid) or session.winner.guid
    return string.format("Rewarded: %s (%d)", display, session.winner.effectiveScore or 0)
  end
  return tostring(session.state or "?")
end

function RaiderFrame:CreateCard(index)
  local kit = Kit()
  local C = kit.COLORS
  local card = kit:CreateCard(self.frame.content, CARD_HEIGHT)
  card:SetPoint("TOPLEFT", self.frame.content, "TOPLEFT", 0, -28 - (index - 1) * (CARD_HEIGHT + CARD_SPACING))
  card:SetPoint("RIGHT", self.frame.content, "RIGHT", 0, 0)

  local icon = card:CreateTexture(nil, "ARTWORK")
  icon:SetSize(44, 44)
  icon:SetPoint("TOPLEFT", 8, -8)
  icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
  card.icon = icon

  local iconHit = CreateFrame("Frame", nil, card)
  iconHit:SetAllPoints(icon)
  iconHit:SetScript("OnEnter", function()
    if card.itemLink and card.itemLink ~= "" then
      GameTooltip:SetOwner(card, "ANCHOR_RIGHT")
      pcall(GameTooltip.SetHyperlink, GameTooltip, card.itemLink)
      GameTooltip:Show()
    end
  end)
  iconHit:SetScript("OnLeave", function()
    GameTooltip:Hide()
  end)

  -- Local dismiss: removes this item from your list only (the leader's
  -- Archive removes it for everyone).
  local clearBtn = kit:CreateButton(card, "x", 20, 18, function()
    if card.sessionId then
      RaiderFrame.dismissed[card.sessionId] = true
      RaiderFrame:Render()
    end
  end, { smallText = true })
  clearBtn:SetPoint("TOPRIGHT", card, "TOPRIGHT", -6, -6)
  clearBtn:HookScript("OnEnter", function(b)
    GameTooltip:SetOwner(b, "ANCHOR_TOP")
    GameTooltip:SetText("Clear this item from your list", 1, 1, 1)
    GameTooltip:Show()
  end)
  clearBtn:HookScript("OnLeave", function()
    GameTooltip:Hide()
  end)
  card.clearBtn = clearBtn

  -- Shown to the item holder once a winner exists and the trade is pending:
  -- opens trade with the winner and auto-places the item.
  local tradeBtn = kit:CreateButton(card, "Trade", 48, 18, function()
    if card.sessionId then
      RaiderFrame:StartTradeWithWinner(card.sessionId)
    end
  end, { smallText = true })
  tradeBtn:SetPoint("RIGHT", clearBtn, "LEFT", -4, 0)
  tradeBtn:HookScript("OnEnter", function(b)
    GameTooltip:SetOwner(b, "ANCHOR_TOP")
    GameTooltip:SetText("Open trade with the winner and add this item automatically.\nThey must be within 11 yards.", 1, 1, 1, 1, true)
    GameTooltip:Show()
  end)
  tradeBtn:HookScript("OnLeave", function()
    GameTooltip:Hide()
  end)
  card.tradeBtn = tradeBtn

  local itemText = kit:CreateLabel(card, "", "normal")
  itemText:SetPoint("TOPLEFT", icon, "TOPRIGHT", 8, -1)
  itemText:SetPoint("RIGHT", card, "RIGHT", -32, 0)
  itemText:SetJustifyH("LEFT")
  itemText:SetWordWrap(false)
  card.itemText = itemText

  local stateText = kit:CreateLabel(card, "", "small", C.textDim)
  stateText:SetPoint("TOPLEFT", itemText, "BOTTOMLEFT", 0, -3)
  stateText:SetPoint("RIGHT", card, "RIGHT", -8, 0)
  stateText:SetJustifyH("LEFT")
  card.stateText = stateText

  local mineText = kit:CreateLabel(card, "", "small", C.textDim)
  mineText:SetPoint("BOTTOMLEFT", card, "BOTTOMLEFT", 8, 8)
  card.mineText = mineText

  card.buttons = {}
  local anchor = nil
  for i = #CATEGORY_BUTTONS, 1, -1 do
    local def = CATEGORY_BUTTONS[i]
    local btn = kit:CreateButton(card, def.label, def.width, 20, function()
      RaiderFrame:SubmitRoll(card.sessionId, def.id, def.label)
    end, { smallText = true, danger = def.danger })
    if anchor then
      btn:SetPoint("RIGHT", anchor, "LEFT", -4, 0)
    else
      btn:SetPoint("BOTTOMRIGHT", card, "BOTTOMRIGHT", -8, 8)
    end
    btn:HookScript("OnEnter", function(b)
      GameTooltip:SetOwner(b, "ANCHOR_TOP")
      GameTooltip:SetText(def.tip, 1, 1, 1)
      GameTooltip:Show()
    end)
    btn:HookScript("OnLeave", function()
      GameTooltip:Hide()
    end)
    anchor = btn
    card.buttons[def.id] = btn
  end

  -- High-contrast amber button: retract your roll and pick again.
  local cancelRollBtn = kit:CreateButton(card, "Cancel Roll", 90, 20, function()
    if not card.sessionId then
      return
    end
    local ok, reason = DR:Require("Services.SessionManager"):CancelLocalRoll(card.sessionId)
    if ok then
      DR:Print("Roll cancelled - choose again")
    else
      DR:Print("Cancel failed: " .. tostring(reason))
    end
    RaiderFrame:Render()
  end, { smallText = true, accent = true })
  cancelRollBtn:SetPoint("BOTTOMRIGHT", card, "BOTTOMRIGHT", -8, 8)
  cancelRollBtn:HookScript("OnEnter", function(b)
    GameTooltip:SetOwner(b, "ANCHOR_TOP")
    GameTooltip:SetText("Cancel your roll for this item and choose again.\nOnly available while rolls are still being collected.", 1, 1, 1, 1, true)
    GameTooltip:Show()
  end)
  cancelRollBtn:HookScript("OnLeave", function()
    GameTooltip:Hide()
  end)
  card.cancelRollBtn = cancelRollBtn

  function card:SetButtonsShown(shown)
    for _, btn in pairs(self.buttons) do
      btn:SetShown(shown)
    end
  end

  function card:Update(session)
    self.sessionId = session.id
    self.itemLink = session.itemLink
    self.icon:SetTexture(itemIcon(session.itemLink))
    self.itemText:SetText(session.itemLink or "(no item)")
    self.stateText:SetText(sessionStatusLine(session))

    local mine, tone = myStatusLine(session)
    self.mineText:SetText(mine)
    local C = Kit().COLORS
    local color = tone == "accent" and C.accent or C.textDim
    self.mineText:SetTextColor(color[1], color[2], color[3], color[4] or 1)

    local guid = UnitGUID("player")
    local alreadyIn = guid and session.submissionsByGuid and session.submissionsByGuid[guid] ~= nil
    local stillCollecting = session.state == constants.SESSION.STATE.COLLECTING
      and nowTs() <= (session.timeoutAt or 0)
    local canRoll = stillCollecting and not alreadyIn
    self:SetButtonsShown(canRoll)
    -- Hide the clear button while the item is still rollable so nobody
    -- accidentally dismisses something they haven't responded to.
    self.clearBtn:SetShown(not canRoll)
    -- Retracting is only possible while the collection window is open.
    self.cancelRollBtn:SetShown(stillCollecting and alreadyIn)

    -- Trade button: only for the player actually holding the item (they have
    -- the pending deferred-trade entry) when someone else won it.
    local showTrade = false
    if session.winner and session.winner.guid ~= guid then
      showTrade = DR:Require("Services.DeferredTrade"):GetPendingBySession(session.id) ~= nil
    end
    self.tradeBtn:SetShown(showTrade)
  end

  return card
end

--- Opens a trade with the session winner; TradeWatcher then auto-places the
--- item(s) and (optionally) accepts.
function RaiderFrame:StartTradeWithWinner(sessionId)
  local session = DR:Require("Services.SessionManager"):GetSession(sessionId)
  if not session or not session.winner then
    return
  end

  local winnerGuid = session.winner.guid
  local display = DR:Require("Services.Identity"):GetDisplayByGuid(winnerGuid) or winnerGuid

  local unit = findUnitByGuid(winnerGuid)
  if not unit then
    DR:Print(display .. " is not in your group - trade manually or wait for them.")
    return
  end

  if CheckInteractDistance then
    -- The range check itself can be restricted (combat / Midnight secrets);
    -- only refuse when we get a definite "out of range".
    local ok, inRange = pcall(CheckInteractDistance, unit, 2)
    if ok and inRange == false then
      DR:Print(display .. " is out of trade range (11 yd).")
      return
    end
  end

  if not InitiateTrade then
    return
  end

  DR:Require("Modules.TradeWatcher"):SetPendingTradeTarget(display)
  DR:Print("Opening trade with " .. display)
  InitiateTrade(unit)
end

function RaiderFrame:SubmitRoll(sessionId, categoryId, label)
  if not sessionId then
    return
  end
  local manager = DR:Require("Services.SessionManager")
  local ok, value, baseRoll, weight = manager:SubmitLocalRoll(sessionId, categoryId)
  if ok then
    if categoryId == constants.ROLL.CATEGORY.PASS then
      DR:Print("You passed on this item")
    elseif type(value) == "number" then
      local engine = DR:Require("Domain.RollEngine")
      if engine:IsWeightedCategory(categoryId) then
        DR:Print(string.format("Rolled %s: %d (raw %d at %s%% power)", label, value, baseRoll or value,
          engine:FormatWeight(weight or 100)))
      else
        DR:Print(string.format("Rolled %s: %d", label, value))
      end
    else
      DR:Print(string.format("%s roll sent to leader", label))
    end
  else
    DR:Print("Submit failed: " .. tostring(value))
  end
  self:Render()
end

function RaiderFrame:CreateFrame()
  if self.frame then
    return self.frame
  end

  local kit = Kit()
  local frame = kit:CreateWindow("RaiderFrame", "DetentionRolls - Loot Rolls", 600, 430, { defaultX = 300, defaultY = 40 })

  local powerText = kit:CreateLabel(frame.content, "", "normal")
  powerText:SetPoint("TOPLEFT", 0, 0)
  frame.powerText = powerText

  local emptyText = kit:CreateLabel(frame.content, "No active loot sessions", "normal", kit.COLORS.textDim)
  emptyText:SetPoint("CENTER", frame.content, "CENTER", 0, 0)
  frame.emptyText = emptyText

  local scrollHint = kit:CreateLabel(frame.content, "", "small", kit.COLORS.textDim)
  scrollHint:SetPoint("TOPRIGHT", 0, 0)
  frame.scrollHint = scrollHint

  kit:AttachWheelScroll(frame, function(delta)
    self.offset = math.max(self.offset + delta, 0)
    self:Render()
  end)

  frame.updateAccumulator = 0
  frame:SetScript("OnUpdate", function(f, elapsed)
    f.updateAccumulator = f.updateAccumulator + elapsed
    if f.updateAccumulator < 0.5 then
      return
    end
    f.updateAccumulator = 0
    self:Render()
  end)

  self.frame = frame
  return frame
end

function RaiderFrame:Render()
  local frame = self.frame
  if not frame or not frame:IsShown() then
    return
  end

  local guid = UnitGUID("player")
  local weight = guid and DR:Require("Services.Ledger"):GetWeight(guid) or 100
  frame.powerText:SetText(string.format("Roll power: |cffffb84c%s%%|r",
    DR:Require("Domain.RollEngine"):FormatWeight(weight)))

  local sessions = {}
  for _, session in ipairs(DR:Require("Services.SessionManager"):GetActiveSessions()) do
    if not self.dismissed[session.id] then
      sessions[#sessions + 1] = session
    end
  end
  local total = #sessions
  local maxOffset = math.max(total - VISIBLE_CARDS, 0)
  if self.offset > maxOffset then
    self.offset = maxOffset
  end

  frame.emptyText:SetShown(total == 0)
  if total > VISIBLE_CARDS then
    frame.scrollHint:SetText(string.format("%d-%d of %d (scroll)", self.offset + 1, math.min(self.offset + VISIBLE_CARDS, total), total))
  else
    frame.scrollHint:SetText("")
  end

  for i = 1, VISIBLE_CARDS do
    local session = sessions[self.offset + i]
    local card = self.cards[i]
    if session then
      if not card then
        card = self:CreateCard(i)
        self.cards[i] = card
      end
      card:Update(session)
      card:Show()
    elseif card then
      card:Hide()
    end
  end
end

function RaiderFrame:Toggle()
  local frame = self:CreateFrame()
  if frame:IsShown() then
    frame:Hide()
  else
    frame:Show()
    self:Render()
  end
end

function RaiderFrame:ShowAndRender()
  local frame = self:CreateFrame()
  if not frame:IsShown() then
    frame:Show()
  end
  self:Render()
end

function RaiderFrame:OnInitialize()
  self:CreateFrame()
  DR:RegisterChatCommand("drollme", function()
    self:Toggle()
  end)

  self:RegisterMessage("DR_SESSION_OPENED", function()
    self:ShowAndRender()
  end)
  self:RegisterMessage("DR_SUBMISSION_ACCEPTED", function()
    self:Render()
  end)
  self:RegisterMessage("DR_SUBMISSION_CANCELLED", function()
    self:Render()
  end)
  self:RegisterMessage("DR_ROLLOFF_STARTED", function()
    self:Render()
  end)
  self:RegisterMessage("DR_ROLLOFF_RESULT", function()
    self:Render()
  end)
  self:RegisterMessage("DR_SESSION_RESOLVED", function()
    self:Render()
  end)
  self:RegisterMessage("DR_SESSION_REWARDED", function()
    self:Render()
  end)
  self:RegisterMessage("DR_SESSION_ARCHIVED", function()
    self:Render()
  end)
  self:RegisterMessage("DR_STATE_SNAPSHOT", function()
    self:Render()
  end)
end

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