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

--- Roster window: tracked players with roll power, addon version and current
--- session status. "Add Group" tracks the current raid; per-row Edit sets a
--- player's power manually; Export/Import moves the roster in and out of the
--- addon as plain text (backup / data-loss recovery).
local RosterFrame = {
  frame = nil,
  exportFrame = nil,
  rows = {},
  offset = 0,
  editingGuid = nil,
}
LibStub("AceEvent-3.0"):Embed(RosterFrame)

local ROW_HEIGHT = 20
local VISIBLE_ROWS = 12

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

local function classColoredName(display, classFile)
  if classFile and RAID_CLASS_COLORS and RAID_CLASS_COLORS[classFile] then
    local c = RAID_CLASS_COLORS[classFile]
    return string.format("|cff%02x%02x%02x%s|r", c.r * 255, c.g * 255, c.b * 255, display)
  end
  return display
end

local function sessionStatusFor(guid)
  local session = DR:Require("Services.SessionManager"):GetLatestSession()
  if not session or not guid then
    return "-"
  end
  local submit = session.submissionsByGuid and session.submissionsByGuid[guid]
  if not submit then
    if session.state == constants.SESSION.STATE.COLLECTING then
      return "|cffd08484No response|r"
    end
    return "-"
  end
  if submit.category == constants.ROLL.CATEGORY.PASS then
    return "Passed"
  end
  if submit.pending or not submit.baseRoll then
    return "Sent"
  end
  local effective = DR:Require("Domain.RollEngine"):ComputeScoreFor(
    submit.category,
    submit.baseRoll,
    submit.weight
  )
  return string.format("Rolled %d", effective)
end

local function eachGroupUnit(callback)
  if IsInRaid() then
    for i = 1, (GetNumGroupMembers() or 0) do
      local unit = "raid" .. i
      if UnitExists(unit) then
        callback(unit)
      end
    end
  elseif IsInGroup() then
    callback("player")
    for i = 1, 4 do
      local unit = "party" .. i
      if UnitExists(unit) then
        callback(unit)
      end
    end
  else
    callback("player")
  end
end

--- Group members first, then tracked players who aren't in the group
--- (offline / other raid nights), dimmed.
function RosterFrame:BuildEntries()
  local identity = DR:Require("Services.Identity")
  local ledger = DR:Require("Services.Ledger")
  local versionCheck = DR:Require("Modules.VersionCheck")
  local myVersion = DR.version
  local entries = {}
  local seen = {}

  eachGroupUnit(function(unit)
    local guid, display = identity:NormalizeUnit(unit)
    if guid and not seen[guid] then
      seen[guid] = true
      local _, classFile = UnitClass(unit)
      local versionText
      if guid == UnitGUID("player") then
        versionText = "v" .. tostring(myVersion)
      else
        local peer = versionCheck.peers and versionCheck.peers[guid]
        if not peer then
          versionText = "|cffd08484no addon|r"
        elseif peer.version ~= myVersion then
          versionText = "|cffd0a060v" .. tostring(peer.version) .. "|r"
        else
          versionText = "v" .. tostring(peer.version)
        end
      end
      entries[#entries + 1] = {
        inGroup = true,
        guid = guid,
        display = display or guid,
        classFile = classFile,
        weight = ledger:GetWeight(guid),
        versionText = versionText,
        status = sessionStatusFor(guid),
        tracked = ledger:IsTracked(guid),
        bench = ledger:IsBenched(guid),
      }
    end
  end)

  local offline = {}
  for _, row in ipairs(ledger:GetTrackedWeights()) do
    if not seen[row.guid] then
      offline[#offline + 1] = {
        inGroup = false,
        guid = row.guid,
        display = row.display,
        weight = row.weight,
        versionText = "-",
        status = row.bench and "|cff6bcc7aBench (+boss credit)|r" or "|cff9a9baanot in group|r",
        tracked = true,
        bench = row.bench,
      }
    end
  end
  table.sort(offline, function(a, b)
    return tostring(a.display) < tostring(b.display)
  end)
  for _, entry in ipairs(offline) do
    entries[#entries + 1] = entry
  end

  return entries
end

function RosterFrame:BeginEdit(entry)
  local frame = self.frame
  self.editingGuid = entry.guid
  frame.editLabel:SetText(string.format("Set power for %s:", entry.display))
  frame.editBox:SetText(tostring(entry.weight or 100))
  frame.editBar:Show()
  frame.editBox:SetFocus()
  frame.editBox:HighlightText()
end

function RosterFrame:CommitEdit()
  local frame = self.frame
  local guid = self.editingGuid
  if not guid then
    frame.editBar:Hide()
    return
  end

  local value = tonumber(frame.editBox:GetText())
  if not value then
    DR:Print("Enter a number for roll power")
    return
  end

  local ledger = DR:Require("Services.Ledger")
  ledger:Track(guid)
  local applied = ledger:SetWeight(guid, value, "manual_edit")
  DR:Print(string.format(
    "Roll power for %s set to %s%%",
    DR:Require("Services.Identity"):GetDisplayByGuid(guid) or guid,
    DR:Require("Domain.RollEngine"):FormatWeight(applied or value)
  ))

  self.editingGuid = nil
  frame.editBar:Hide()
  self:Render()
end

function RosterFrame:CancelEdit()
  self.editingGuid = nil
  if self.frame then
    self.frame.editBar:Hide()
  end
end

function RosterFrame:CreateRow(index)
  local kit = Kit()
  local C = kit.COLORS
  local row = CreateFrame("Frame", nil, self.frame.content)
  row:SetHeight(ROW_HEIGHT)
  row:SetPoint("TOPLEFT", self.frame.content, "TOPLEFT", 0, -56 - (index - 1) * ROW_HEIGHT)
  row:SetPoint("RIGHT", self.frame.content, "RIGHT", 0, 0)

  local name = kit:CreateLabel(row, "", "small")
  name:SetPoint("LEFT", 4, 0)
  name:SetWidth(150)
  name:SetJustifyH("LEFT")
  name:SetWordWrap(false)
  row.name = name

  local power = kit:CreateLabel(row, "", "small")
  power:SetPoint("LEFT", 158, 0)
  power:SetWidth(46)
  power:SetJustifyH("LEFT")
  row.power = power

  local version = kit:CreateLabel(row, "", "small", C.textDim)
  version:SetPoint("LEFT", 208, 0)
  version:SetWidth(72)
  version:SetJustifyH("LEFT")
  row.version = version

  local status = kit:CreateLabel(row, "", "small", C.textDim)
  status:SetPoint("LEFT", 284, 0)
  status:SetPoint("RIGHT", row, "RIGHT", -92, 0)
  status:SetJustifyH("LEFT")
  status:SetWordWrap(false)
  row.status = status

  local removeBtn = kit:CreateButton(row, "x", 18, 16, function()
    if row.entry then
      DR:Require("Services.Ledger"):Untrack(row.entry.guid)
      RosterFrame:Render()
    end
  end, { smallText = true, danger = true })
  removeBtn:SetPoint("RIGHT", row, "RIGHT", -2, 0)

  local benchBtn = kit:CreateButton(row, "B", 20, 16, function()
    if row.entry then
      local ledger = DR:Require("Services.Ledger")
      local benched = ledger:SetBench(row.entry.guid, not ledger:IsBenched(row.entry.guid))
      DR:Print(string.format(
        "%s %s the bench",
        row.entry.display,
        benched and "added to" or "removed from"
      ))
      RosterFrame:Render()
    end
  end, { smallText = true })
  benchBtn:SetPoint("RIGHT", row, "RIGHT", -66, 0)
  benchBtn:HookScript("OnEnter", function(b)
    GameTooltip:SetOwner(b, "ANCHOR_TOP")
    GameTooltip:SetText("Toggle Bench: standby players earn the higher boss credit while out of the raid.\nAbsent players who are not benched earn nothing.", 1, 1, 1, 1, true)
    GameTooltip:Show()
  end)
  benchBtn:HookScript("OnLeave", function()
    GameTooltip:Hide()
  end)
  row.benchBtn = benchBtn
  removeBtn:HookScript("OnEnter", function(b)
    GameTooltip:SetOwner(b, "ANCHOR_TOP")
    GameTooltip:SetText("Stop tracking this player", 1, 1, 1)
    GameTooltip:Show()
  end)
  removeBtn:HookScript("OnLeave", function()
    GameTooltip:Hide()
  end)
  row.removeBtn = removeBtn

  local editBtn = kit:CreateButton(row, "Edit", 36, 16, function()
    if row.entry then
      RosterFrame:BeginEdit(row.entry)
    end
  end, { smallText = true })
  editBtn:SetPoint("RIGHT", removeBtn, "LEFT", -4, 0)
  editBtn:HookScript("OnEnter", function(b)
    GameTooltip:SetOwner(b, "ANCHOR_TOP")
    GameTooltip:SetText("Manually set this player's roll power.\nOnly the raid leader's ledger is authoritative.", 1, 1, 1, 1, true)
    GameTooltip:Show()
  end)
  editBtn:HookScript("OnLeave", function()
    GameTooltip:Hide()
  end)
  row.editBtn = editBtn

  function row:Update(entry)
    self.entry = entry
    local benchTag = entry.bench and " |cff6bcc7a[B]|r" or ""
    self.name:SetText(classColoredName(entry.display, entry.classFile) .. benchTag)
    local weight = entry.weight or 100
    local color = weight >= 100 and "|cff6bcc7a" or weight >= 60 and "|cffffb84c" or "|cffd08484"
    self.power:SetText(string.format("%s%s%%|r", color, DR:Require("Domain.RollEngine"):FormatWeight(weight)))
    self.version:SetText(entry.versionText or "-")
    self.status:SetText(entry.status or "-")
    self:SetAlpha(entry.inGroup and 1 or 0.55)
    -- Untracking only makes sense for stored entries outside the group.
    self.removeBtn:SetShown(not entry.inGroup and entry.tracked)
    if entry.bench then
      self.benchBtn.label:SetTextColor(0.42, 0.80, 0.48, 1)
    else
      local kit = DR:Require("UI.Kit")
      local c = kit.COLORS.text
      self.benchBtn.label:SetTextColor(c[1], c[2], c[3], c[4] or 1)
    end
  end

  return row
end

function RosterFrame:CreateExportFrame()
  if self.exportFrame then
    return self.exportFrame
  end

  local kit = Kit()
  local C = kit.COLORS
  local frame = kit:CreateWindow("RosterExport", "DetentionRolls - Roster Export / Import", 480, 340, { strata = "DIALOG" })

  local hint = kit:CreateLabel(frame.content,
    "Export: copy this text somewhere safe (Ctrl+C).\nImport: paste a saved export below, then press Import.", "small", C.textDim)
  hint:SetPoint("TOPLEFT", 0, 0)
  hint:SetJustifyH("LEFT")

  local textArea = kit:CreateTextArea(frame.content, 456, 190)
  textArea:SetPoint("TOPLEFT", 0, -34)
  frame.textArea = textArea

  local exportBtn = kit:CreateButton(frame.content, "Generate Export", 120, 24, function()
    local text = DR:Require("Services.Ledger"):ExportRoster()
    textArea.edit:SetText(text)
    textArea.edit:SetFocus()
    textArea.edit:HighlightText()
  end, { smallText = true })
  exportBtn:SetPoint("TOPLEFT", textArea, "BOTTOMLEFT", 0, -8)

  local importBtn = kit:CreateButton(frame.content, "Import", 80, 24, function()
    local ok, result = DR:Require("Services.Ledger"):ImportRoster(textArea.edit:GetText())
    if ok then
      DR:Print(string.format("Roster import: %d player(s) restored", result))
      self:Render()
    else
      DR:Print("Roster import failed: " .. tostring(result))
    end
  end, { smallText = true })
  importBtn:SetPoint("LEFT", exportBtn, "RIGHT", 8, 0)

  self.exportFrame = frame
  return frame
end

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

  local kit = Kit()
  local C = kit.COLORS
  local frame = kit:CreateWindow("RosterFrame", "DetentionRolls - Roster", 520, 440, { defaultX = 0, defaultY = 60 })

  -- toolbar
  local addGroupBtn = kit:CreateButton(frame.content, "Add Group", 84, 24, function()
    local count = DR:Require("Services.Ledger"):TrackGroupMembers()
    DR:Print(string.format("Roster: %d group member(s) added/refreshed", count))
    self:Render()
  end, { smallText = true })
  addGroupBtn:SetPoint("TOPLEFT", 0, 0)
  addGroupBtn:HookScript("OnEnter", function(b)
    GameTooltip:SetOwner(b, "ANCHOR_TOP")
    GameTooltip:SetText("Track everyone currently in your group.\nOnly tracked players are stored and exported.", 1, 1, 1, 1, true)
    GameTooltip:Show()
  end)
  addGroupBtn:HookScript("OnLeave", function()
    GameTooltip:Hide()
  end)

  local exportBtn = kit:CreateButton(frame.content, "Export / Import", 110, 24, function()
    local win = self:CreateExportFrame()
    if win:IsShown() then
      win:Hide()
    else
      win:Show()
    end
  end, { smallText = true })
  exportBtn:SetPoint("LEFT", addGroupBtn, "RIGHT", 6, 0)

  -- Leader: broadcast the roster to the raid. Raider: ask the leader for it.
  local syncBtn = kit:CreateButton(frame.content, "Sync", 110, 24, function()
    local ledger = DR:Require("Services.Ledger")
    if not IsInGroup() then
      DR:Print("Roster sync needs a group")
      return
    end
    if UnitIsGroupLeader("player") then
      local ok, result = ledger:SendRosterSync()
      if not ok then
        DR:Print("Roster send failed: " .. tostring(result))
      end
    else
      local ok = ledger:RequestRosterSync()
      if ok then
        DR:Print("Roster sync requested from the raid leader")
      else
        DR:Print("Could not send the sync request")
      end
    end
  end, { smallText = true })
  syncBtn:SetPoint("LEFT", exportBtn, "RIGHT", 6, 0)
  syncBtn:HookScript("OnEnter", function(b)
    GameTooltip:SetOwner(b, "ANCHOR_TOP")
    if UnitIsGroupLeader("player") then
      GameTooltip:SetText("Broadcast your roster (roll power) to everyone in the raid.", 1, 1, 1, 1, true)
    else
      GameTooltip:SetText("Ask the raid leader to send their authoritative roster.", 1, 1, 1, 1, true)
    end
    GameTooltip:Show()
  end)
  syncBtn:HookScript("OnLeave", function()
    GameTooltip:Hide()
  end)
  frame.syncBtn = syncBtn

  -- column headers
  local headName = kit:CreateLabel(frame.content, "Player", "small", C.accent)
  headName:SetPoint("TOPLEFT", 4, -32)
  local headPower = kit:CreateLabel(frame.content, "Power", "small", C.accent)
  headPower:SetPoint("TOPLEFT", 158, -32)
  local headVersion = kit:CreateLabel(frame.content, "Addon", "small", C.accent)
  headVersion:SetPoint("TOPLEFT", 208, -32)
  local headStatus = kit:CreateLabel(frame.content, "Session", "small", C.accent)
  headStatus:SetPoint("TOPLEFT", 284, -32)

  local divider = kit:CreateDivider(frame.content)
  divider:SetPoint("TOPLEFT", 0, -50)
  divider:SetPoint("TOPRIGHT", 0, -50)

  -- power editor bar (hidden until a row's Edit is clicked)
  local editBar = CreateFrame("Frame", nil, frame.content)
  editBar:SetHeight(26)
  editBar:SetPoint("BOTTOMLEFT", 0, 0)
  editBar:SetPoint("BOTTOMRIGHT", 0, 0)
  editBar:Hide()
  frame.editBar = editBar

  local editLabel = kit:CreateLabel(editBar, "", "small")
  editLabel:SetPoint("LEFT", 0, 0)
  frame.editLabel = editLabel

  local editBox = kit:CreateEditBox(editBar, 60, 22)
  editBox:SetPoint("LEFT", editLabel, "RIGHT", 8, 0)
  -- Not SetNumeric: weights can be fractional (e.g. 112.5).
  editBox:SetMaxLetters(5)
  editBox:SetScript("OnEnterPressed", function()
    self:CommitEdit()
  end)
  frame.editBox = editBox

  local setBtn = kit:CreateButton(editBar, "Set", 44, 22, function()
    self:CommitEdit()
  end, { smallText = true })
  setBtn:SetPoint("LEFT", editBox, "RIGHT", 6, 0)

  local cancelBtn = kit:CreateButton(editBar, "Cancel", 54, 22, function()
    self:CancelEdit()
  end, { smallText = true })
  cancelBtn:SetPoint("LEFT", setBtn, "RIGHT", 6, 0)

  -- Proxy rolling for players without the addon: the leader makes their
  -- roll choices in a dedicated window.
  local rollForBtn = kit:CreateButton(editBar, "Roll For", 66, 22, function()
    if self.editingGuid then
      DR:Require("Modules.ProxyRoll"):Open(self.editingGuid)
    end
  end, { smallText = true, accent = true })
  rollForBtn:SetPoint("LEFT", cancelBtn, "RIGHT", 6, 0)
  rollForBtn:HookScript("OnEnter", function(b)
    GameTooltip:SetOwner(b, "ANCHOR_TOP")
    GameTooltip:SetText("Open the proxy roll window: make roll choices on this player's behalf.\nFor raiders who don't run the addon. Leader only.", 1, 1, 1, 1, true)
    GameTooltip:Show()
  end)
  rollForBtn:HookScript("OnLeave", function()
    GameTooltip:Hide()
  end)

  local scrollHint = kit:CreateLabel(frame.content, "", "small", C.textDim)
  scrollHint:SetPoint("BOTTOMRIGHT", 0, 4)
  frame.scrollHint = scrollHint

  local emptyText = kit:CreateLabel(frame.content, "No tracked players - press Add Group", "normal", C.textDim)
  emptyText:SetPoint("CENTER", 0, 0)
  frame.emptyText = emptyText

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

  self.frame = frame
  return frame
end

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

  if frame.syncBtn then
    frame.syncBtn.label:SetText(UnitIsGroupLeader("player") and "Send to Raid" or "Sync from RL")
  end

  local entries = self:BuildEntries()
  local total = #entries
  local maxOffset = math.max(total - VISIBLE_ROWS, 0)
  if self.offset > maxOffset then
    self.offset = maxOffset
  end

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

  for i = 1, VISIBLE_ROWS do
    local entry = entries[self.offset + i]
    local row = self.rows[i]
    if entry then
      if not row then
        row = self:CreateRow(i)
        self.rows[i] = row
      end
      row:Update(entry)
      row:Show()
    elseif row then
      row:Hide()
    end
  end
end

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

function RosterFrame:OnInitialize()
  self:CreateFrame()
  DR:RegisterChatCommand("drollroster", function()
    self:Toggle()
  end)

  self:RegisterMessage("DR_SUBMISSION_ACCEPTED", function()
    self:Render()
  end)
  self:RegisterMessage("DR_SUBMISSION_CANCELLED", function()
    self:Render()
  end)
  self:RegisterMessage("DR_SESSION_OPENED", 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_ROSTER_SYNCED", function()
    self:Render()
  end)
  self:RegisterEvent("GROUP_ROSTER_UPDATE", function()
    self:Render()
  end)
end

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