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

--- Raid leader window: start sessions (drag an item in or link it), watch
--- every active session as a card, and resolve/roll-off/archive per item.
--- Selecting a card shows its rolls in the detail pane.
local RaidLeaderFrame = {
  frame = nil,
  cards = {},
  detailRows = {},
  offset = 0,
  detailOffset = 0,
  selectedSessionId = nil,
}
LibStub("AceEvent-3.0"):Embed(RaidLeaderFrame)

local CARD_HEIGHT = 56
local CARD_SPACING = 5
local VISIBLE_CARDS = 4
local DETAIL_ROW_HEIGHT = 19
local VISIBLE_DETAIL_ROWS = 7

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
end

local function tryInsertCursorItem(editBox)
  if not GetCursorInfo then
    return
  end
  local infoType, _, itemLink = GetCursorInfo()
  if infoType == "item" and itemLink then
    editBox:SetText(itemLink)
    ClearCursor()
  end
end

local function countSubmissions(session)
  local total, passes = 0, 0
  for _, submit in pairs(session.submissionsByGuid or {}) do
    total = total + 1
    if submit.category == constants.ROLL.CATEGORY.PASS then
      passes = passes + 1
    end
  end
  return total, passes
end

local function sessionSummary(session)
  local S = constants.SESSION.STATE
  local total, passes = countSubmissions(session)
  local counts = string.format("%d roll(s), %d pass(es)", total - passes, passes)

  if session.state == S.COLLECTING then
    local remaining = math.max((session.timeoutAt or 0) - nowTs(), 0)
    return string.format("Collecting - %ds left - %s", remaining, counts)
  end
  if session.state == S.ROLLOFF then
    return string.format("Roll-off round %d - %s", session.rollOff and session.rollOff.round or 0, counts)
  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("|cffffb84cAwarded to %s (%d) - pending reward|r", 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 to %s (%d) - power now %s%%", display, session.winner.effectiveScore or 0,
      DR:Require("Domain.RollEngine"):FormatWeight(session.winner.postPenaltyWeight or 0))
  end
  return string.format("%s - %s", tostring(session.state or "?"), counts)
end

function RaidLeaderFrame:StartSession(link)
  if not link or link == "" then
    DR:Print("Provide an item link first (drag an item into the box or shift-click a link)")
    return
  end

  local ok, sessionIdOrReason, extra = DR:Require("Services.SessionManager"):OpenSession(link)
  if ok then
    self.selectedSessionId = sessionIdOrReason
    self:Render()
  elseif sessionIdOrReason == "not_raid_leader" then
    DR:Print("Only the raid leader can start sessions")
  elseif sessionIdOrReason == "version_gate" then
    DR:Print("Version gate blocked: " .. table.concat(extra or {}, ", "))
  else
    DR:Print("Failed to open session: " .. tostring(sessionIdOrReason))
  end
end

function RaidLeaderFrame:RunSessionAction(sessionId, action)
  local manager = DR:Require("Services.SessionManager")
  local ok, reason
  if action == "resolve" then
    ok, reason = manager:ResolveSession(sessionId)
  elseif action == "rolloff" then
    ok, reason = manager:RunRollOffRound(sessionId)
  elseif action == "reward" then
    ok, reason = manager:RewardWinner(sessionId, "manual")
    if ok then
      DR:Print(string.format("Reward confirmed - winner's roll power is now %s%%",
        DR:Require("Domain.RollEngine"):FormatWeight(reason)))
    end
  elseif action == "archive" then
    ok, reason = manager:ArchiveSession(sessionId, "manual")
  end
  if not ok and reason then
    DR:Print(string.format("%s failed: %s", action, tostring(reason)))
  end
  self:Render()
end

function RaidLeaderFrame:CreateCard(index)
  local kit = Kit()
  local C = kit.COLORS
  local card = kit:CreateCard(self.frame.listArea, CARD_HEIGHT)
  card:SetPoint("TOPLEFT", self.frame.listArea, "TOPLEFT", 0, -(index - 1) * (CARD_HEIGHT + CARD_SPACING))
  card:SetPoint("RIGHT", self.frame.listArea, "RIGHT", 0, 0)
  card:EnableMouse(true)
  card:SetScript("OnMouseDown", function()
    if card.sessionId then
      self.selectedSessionId = card.sessionId
      self:Render()
    end
  end)

  local icon = card:CreateTexture(nil, "ARTWORK")
  icon:SetSize(36, 36)
  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 itemText = kit:CreateLabel(card, "", "small")
  itemText:SetPoint("TOPLEFT", icon, "TOPRIGHT", 8, -2)
  itemText:SetPoint("RIGHT", card, "RIGHT", -250, 0)
  itemText:SetJustifyH("LEFT")
  itemText:SetWordWrap(false)
  card.itemText = itemText

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

  local archiveBtn = kit:CreateButton(card, "Archive", 56, 20, function()
    self:RunSessionAction(card.sessionId, "archive")
  end, { smallText = true, danger = true })
  archiveBtn:SetPoint("RIGHT", card, "RIGHT", -8, 0)

  local rewardBtn = kit:CreateButton(card, "Reward", 56, 20, function()
    self:RunSessionAction(card.sessionId, "reward")
  end, { smallText = true })
  rewardBtn:SetPoint("RIGHT", archiveBtn, "LEFT", -4, 0)
  rewardBtn:HookScript("OnEnter", function(b)
    GameTooltip:SetOwner(b, "ANCHOR_TOP")
    GameTooltip:SetText("Confirm the winner: applies their roll-power penalty.\nAlso happens automatically when the item is traded to them.", 1, 1, 1, 1, true)
    GameTooltip:Show()
  end)
  rewardBtn:HookScript("OnLeave", function()
    GameTooltip:Hide()
  end)

  local rollOffBtn = kit:CreateButton(card, "Roll-Off", 56, 20, function()
    self:RunSessionAction(card.sessionId, "rolloff")
  end, { smallText = true })
  rollOffBtn:SetPoint("RIGHT", rewardBtn, "LEFT", -4, 0)

  local resolveBtn = kit:CreateButton(card, "Resolve", 56, 20, function()
    self:RunSessionAction(card.sessionId, "resolve")
  end, { smallText = true })
  resolveBtn:SetPoint("RIGHT", rollOffBtn, "LEFT", -4, 0)

  card.resolveBtn = resolveBtn
  card.rollOffBtn = rollOffBtn
  card.rewardBtn = rewardBtn

  function card:Update(session, selected)
    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(sessionSummary(session))
    self:SetSelected(selected)

    local S = constants.SESSION.STATE
    self.resolveBtn:SetEnabledState(session.state == S.COLLECTING)
    self.rollOffBtn:SetEnabledState(session.state == S.ROLLOFF)
    self.rewardBtn:SetEnabledState(session.state == S.AWARDED)
  end

  return card
end

local function formatRollLine(row, identity, rollEngine, isWinner)
  local display = identity:GetDisplayByGuid(row.guid) or row.guid
  local text
  if row.submit.category == constants.ROLL.CATEGORY.PASS then
    text = string.format("|cff9a9baa%s: Pass|r", display)
  elseif row.effective and rollEngine:IsWeightedCategory(row.submit.category) then
    text = string.format(
      "%s: %s |cffffb84c%d|r  (raw %d at %s%% power)",
      display,
      CATEGORY_NAMES[row.submit.category] or "?",
      row.effective,
      tonumber(row.submit.baseRoll) or 0,
      rollEngine:FormatWeight(tonumber(row.submit.weight) or 100)
    )
  elseif row.effective then
    text = string.format(
      "%s: %s |cffffb84c%d|r  (standard roll)",
      display,
      CATEGORY_NAMES[row.submit.category] or "?",
      row.effective
    )
  else
    text = string.format("%s: %s ...", display, CATEGORY_NAMES[row.submit.category] or "?")
  end

  if isWinner then
    text = "|cffffb84c* WINNER|r  " .. text .. (row.powerNote or "")
  end
  return text
end

function RaidLeaderFrame:CreateDetailRow(index)
  local kit = Kit()
  local row = CreateFrame("Frame", nil, self.frame.detailArea)
  row:SetHeight(DETAIL_ROW_HEIGHT)
  row:SetPoint("TOPLEFT", self.frame.detailArea, "TOPLEFT", 0, -(index - 1) * DETAIL_ROW_HEIGHT)
  row:SetPoint("RIGHT", self.frame.detailArea, "RIGHT", 0, 0)

  local text = kit:CreateLabel(row, "", "small")
  text:SetPoint("LEFT", 0, 0)
  text:SetPoint("RIGHT", row, "RIGHT", -58, 0)
  text:SetJustifyH("LEFT")
  text:SetWordWrap(false)
  row.text = text

  local awardBtn = kit:CreateButton(row, "Award", 50, 16, function()
    if not row.guid or not row.sessionId then
      return
    end
    local ok, result = DR:Require("Services.SessionManager"):SetWinner(row.sessionId, row.guid, "manual_winner_change")
    if ok then
      local display = DR:Require("Services.Identity"):GetDisplayByGuid(row.guid) or row.guid
      DR:Print("Winner changed to " .. display .. " - use the Trade button to hand the item over")
    else
      DR:Print("Winner change failed: " .. tostring(result))
    end
    RaidLeaderFrame:Render()
  end, { smallText = true })
  awardBtn:SetPoint("RIGHT", row, "RIGHT", -2, 0)
  awardBtn:HookScript("OnEnter", function(b)
    GameTooltip:SetOwner(b, "ANCHOR_TOP")
    GameTooltip:SetText("Make this player the winner instead\n(e.g. the original winner no longer wants the item).", 1, 1, 1, 1, true)
    GameTooltip:Show()
  end)
  awardBtn:HookScript("OnLeave", function()
    GameTooltip:Hide()
  end)
  row.awardBtn = awardBtn

  return row
end

function RaidLeaderFrame:RenderDetails(session)
  local frame = self.frame

  if not session then
    frame.detailEmpty:SetText("Select a session to see its rolls")
    frame.detailEmpty:Show()
    frame.detailHint:SetText("")
    for _, row in ipairs(self.detailRows) do
      row:Hide()
    end
    return
  end

  local identity = DR:Require("Services.Identity")
  local rollEngine = DR:Require("Domain.RollEngine")
  local manager = DR:Require("Services.SessionManager")

  local rows = {}
  for guid, submit in pairs(session.submissionsByGuid or {}) do
    local effective = nil
    if submit.baseRoll and submit.category ~= constants.ROLL.CATEGORY.PASS then
      effective = rollEngine:ComputeScoreFor(submit.category, submit.baseRoll, submit.weight)
    end
    rows[#rows + 1] = { guid = guid, submit = submit, effective = effective }
  end

  -- Highest first (contention order: category priority, then weighted score);
  -- passes at the bottom. Once a winner exists, they pin to the top.
  local winnerGuid = session.winner and session.winner.guid
  table.sort(rows, function(a, b)
    if winnerGuid then
      if (a.guid == winnerGuid) ~= (b.guid == winnerGuid) then
        return a.guid == winnerGuid
      end
    end
    local pa = rollEngine:GetCategoryPriority(a.submit.category)
    local pb = rollEngine:GetCategoryPriority(b.submit.category)
    if pa ~= pb then
      return pa > pb
    end
    if (a.effective or -1) ~= (b.effective or -1) then
      return (a.effective or -1) > (b.effective or -1)
    end
    return a.guid < b.guid
  end)

  if winnerGuid then
    for _, row in ipairs(rows) do
      if row.guid == winnerGuid then
        row.powerNote = session.winner.postPenaltyWeight
          and string.format("  |cff9a9baa(power now %s%%)|r", rollEngine:FormatWeight(session.winner.postPenaltyWeight))
          or "  |cff9a9baa(pending reward)|r"
      end
    end
  end

  frame.detailEmpty:SetShown(#rows == 0)
  if #rows == 0 then
    frame.detailEmpty:SetText("No rolls yet")
  end

  local maxOffset = math.max(#rows - VISIBLE_DETAIL_ROWS, 0)
  if self.detailOffset > maxOffset then
    self.detailOffset = maxOffset
  end
  if #rows > VISIBLE_DETAIL_ROWS then
    frame.detailHint:SetText(string.format("%d-%d of %d (scroll)", self.detailOffset + 1, math.min(self.detailOffset + VISIBLE_DETAIL_ROWS, #rows), #rows))
  else
    frame.detailHint:SetText("")
  end

  -- The Award button appears while the item is awarded but not yet rewarded.
  local canReaward = manager:IsRaidLeaderAuthority()
    and session.state == constants.SESSION.STATE.AWARDED

  for i = 1, VISIBLE_DETAIL_ROWS do
    local data = rows[self.detailOffset + i]
    local row = self.detailRows[i]
    if data then
      if not row then
        row = self:CreateDetailRow(i)
        self.detailRows[i] = row
      end
      row.guid = data.guid
      row.sessionId = session.id
      local isWinner = winnerGuid ~= nil and data.guid == winnerGuid
      row.text:SetText(formatRollLine(data, identity, rollEngine, isWinner))
      row.awardBtn:SetShown(
        canReaward
          and not isWinner
          and data.submit.category ~= constants.ROLL.CATEGORY.PASS
          and data.submit.baseRoll ~= nil
      )
      row:Show()
    elseif row then
      row:Hide()
    end
  end
end

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

  local kit = Kit()
  local C = kit.COLORS
  local frame = kit:CreateWindow("RaidLeaderFrame", "DetentionRolls - Raid Leader", 640, 540, { defaultX = -320, defaultY = 40 })

  -- item input row
  local itemInput = kit:CreateEditBox(frame.content, 380, 26)
  itemInput:SetPoint("TOPLEFT", 0, 0)
  itemInput:SetScript("OnReceiveDrag", function(box)
    tryInsertCursorItem(box)
  end)
  itemInput:HookScript("OnMouseDown", function(box)
    tryInsertCursorItem(box)
  end)
  itemInput:SetScript("OnEnterPressed", function(box)
    self:StartSession(box:GetText())
    box:ClearFocus()
  end)
  frame.itemInput = itemInput

  local startBtn = kit:CreateButton(frame.content, "Start Session", 100, 26, function()
    self:StartSession(itemInput:GetText())
  end)
  startBtn:SetPoint("LEFT", itemInput, "RIGHT", 6, 0)

  local rosterBtn = kit:CreateButton(frame.content, "Roster", 60, 26, function()
    DR:Require("Modules.RosterFrame"):Toggle()
  end, { smallText = true })
  rosterBtn:SetPoint("LEFT", startBtn, "RIGHT", 6, 0)

  local tradesBtn = kit:CreateButton(frame.content, "Trades", 60, 26, function()
    DR:Require("Modules.DeferredTradeFrame"):Toggle()
  end, { smallText = true })
  tradesBtn:SetPoint("LEFT", rosterBtn, "RIGHT", 6, 0)

  local hint = kit:CreateLabel(frame.content, "", "small", C.textDim)
  hint:SetPoint("TOPLEFT", itemInput, "BOTTOMLEFT", 0, -4)
  frame.hint = hint

  -- session list
  local listArea = CreateFrame("Frame", nil, frame.content)
  listArea:SetPoint("TOPLEFT", 0, -56)
  listArea:SetPoint("RIGHT", 0, 0)
  listArea:SetHeight(VISIBLE_CARDS * (CARD_HEIGHT + CARD_SPACING))
  frame.listArea = listArea

  local emptyText = kit:CreateLabel(listArea, "No sessions - start one above", "normal", C.textDim)
  emptyText:SetPoint("CENTER", 0, 0)
  frame.emptyText = emptyText

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

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

  -- detail pane
  local divider = kit:CreateDivider(frame.content)
  divider:SetPoint("TOPLEFT", listArea, "BOTTOMLEFT", 0, -4)
  divider:SetPoint("TOPRIGHT", listArea, "BOTTOMRIGHT", 0, -4)

  local detailTitle = kit:CreateLabel(frame.content, "Rolls", "normal", C.accent)
  detailTitle:SetPoint("TOPLEFT", listArea, "BOTTOMLEFT", 0, -12)

  local detailHint = kit:CreateLabel(frame.content, "", "small", C.textDim)
  detailHint:SetPoint("LEFT", detailTitle, "RIGHT", 12, 0)
  frame.detailHint = detailHint

  local detailArea = CreateFrame("Frame", nil, frame.content)
  detailArea:SetPoint("TOPLEFT", detailTitle, "BOTTOMLEFT", 0, -6)
  detailArea:SetPoint("BOTTOMRIGHT", frame.content, "BOTTOMRIGHT", 0, 0)
  frame.detailArea = detailArea

  local detailEmpty = kit:CreateLabel(detailArea, "Select a session to see its rolls", "small", C.textDim)
  detailEmpty:SetPoint("TOPLEFT", 0, 0)
  frame.detailEmpty = detailEmpty

  -- Rolls list scrolls independently of the session cards.
  kit:AttachWheelScroll(detailArea, function(delta)
    self.detailOffset = math.max(self.detailOffset + delta, 0)
    self:Render()
  end)

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

  self.frame = frame
  return frame
end

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

  if DR:Require("Modules.RaidActivation"):IsActive() then
    frame.hint:SetText("|cff6bcc7aRaid mode ACTIVE|r - loot auto-adds sessions; drag an item in for manual ones")
  else
    frame.hint:SetText("|cffd08484Raid mode inactive|r - /droll raid on (boss regain and loot automation are off)")
  end

  local manager = DR:Require("Services.SessionManager")
  local sessions = manager:GetActiveSessions()
  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

  if not self.selectedSessionId or not manager:GetSession(self.selectedSessionId) then
    self.selectedSessionId = sessions[#sessions] and sessions[#sessions].id or nil
  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, session.id == self.selectedSessionId)
      card:Show()
    elseif card then
      card:Hide()
    end
  end

  self:RenderDetails(self.selectedSessionId and manager:GetSession(self.selectedSessionId) or nil)
end

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

function RaidLeaderFrame:OnInitialize()
  self:CreateFrame()
  DR:RegisterChatCommand("drollrl", function()
    self:Toggle()
  end)

  self:RegisterMessage("DR_SESSION_OPENED", function()
    if UnitIsGroupLeader("player") and self.frame and not self.frame:IsShown() then
      self.frame:Show()
    end
    self:Render()
  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)
  self:RegisterMessage("DR_RAID_ACTIVE_CHANGED", function()
    self:Render()
  end)
end

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