local API = rawget(_G, "LIDCOUNCIL_API")
local INTERNAL_API = type(API) == "table" and type(API.__internal) == "table" and API.__internal or nil
if type(INTERNAL_API) ~= "table" then
  error("LidCouncil missing internal API registry.")
end

local shared = INTERNAL_API.LIDCOUNCIL_LootRuntimeUiShared_Impl
if type(shared) ~= "table" then
  error("LidCouncil missing loot runtime UI shared helpers.")
end

local GetActiveLootSession = shared.GetActiveLootSession
local GetLootProfileAccessInfo = shared.GetLootProfileAccessInfo
local GetLootProfileAccessInfoForRole = shared.GetLootProfileAccessInfoForRole
local ResolveLootPointValue = shared.ResolveLootPointValue
local ResolveLootAwardOutcome = shared.ResolveLootAwardOutcome
local ApplyFrameSkin = shared.ApplyFrameSkin
local SendSessionComm = shared.SendSessionComm
local ResolveOptionalFunction = shared.ResolveOptionalFunction
local NOTE_ICON_TEXTURE = shared.NOTE_ICON_TEXTURE
local RuntimeState = shared.RuntimeState
local UIState = shared.UIState
local TrimText = shared.TrimText
local IsRealSimulationItemKey = shared.IsRealSimulationItemKey
local NormalizeSelectionNote = shared.NormalizeSelectionNote
local RefreshEditor = shared.RefreshEditor
local WrapColor = shared.WrapColor
local BuildMemberDisplayText = shared.BuildMemberDisplayText
local GetSelfKey = shared.GetSelfKey
local IsSelfHost = shared.IsSelfHost
local GetBroadcastDistribution = shared.GetBroadcastDistribution
local EnsurePreviewBoardContext = shared.EnsurePreviewBoardContext
local ResolveLootLedgerTotals = shared.ResolveLootLedgerTotals
local ClearRuntimeState = shared.ClearRuntimeState
local EnsureRuntimeSession = shared.EnsureRuntimeSession
local EnsureSelectedMember = shared.EnsureSelectedMember
local CycleSelectedMember = shared.CycleSelectedMember
local BuildAwardNote = shared.BuildAwardNote
local RemoveAwardFromEntry = shared.RemoveAwardFromEntry
local ApplyAwardMessage = shared.ApplyAwardMessage
local RefreshBoardAndEditor = shared.RefreshBoardAndEditor
local GetLiveLootBoardSummary = shared.GetLiveLootBoardSummary
local BuildPreviewBoardState = shared.BuildPreviewBoardState
local GetSelectionPriority = shared.GetSelectionPriority
local FormatSelectionLabel = shared.FormatSelectionLabel
local EnsurePreviewSelectedMember = shared.EnsurePreviewSelectedMember
local ResolveRollOptions = shared.ResolveRollOptions
local RecordChoice = shared.RecordChoice
local RecordLootVoteStat = shared.RecordLootVoteStat
local ResolveSessionUnit = shared.ResolveSessionUnit
local ResolveOptionIconTexture = shared.ResolveOptionIconTexture
local PersistRuntimeBoardHistory = shared.PersistRuntimeBoardHistory

local function GetSessionCommProfileName(activeSession, fallbackProfileName)
  return TrimText(type(activeSession) == "table" and activeSession.remoteProfileName) ~= ""
    and TrimText(activeSession.remoteProfileName)
    or TrimText(fallbackProfileName)
end

local OPTIONAL_BOARD_COLUMN_DEFAULTS = {
  items = false,
  offspec = false,
  transmog = false,
  disenchant = false,
  total = true,
  bis = true,
}
local OPTIONAL_BOARD_COLUMN_KEYS = {
  items = true,
  offspec = true,
  transmog = true,
  disenchant = true,
  total = true,
  bis = true,
}

local function GetRootUiSettings()
  local rootProvider = rawget(_G, "LIDCOUNCIL_RootDB")
  local root = type(rootProvider) == "function" and rootProvider() or nil
  if type(root) ~= "table" then
    return nil
  end
  root.ui = type(root.ui) == "table" and root.ui or {}
  return root.ui
end

local function EnsureBoardColumnVisibility()
  local ui = GetRootUiSettings()
  local columns = ui and ui.liveBoardColumns or UIState.boardColumnVisibility
  columns = type(columns) == "table" and columns or {}
  for key, defaultValue in pairs(OPTIONAL_BOARD_COLUMN_DEFAULTS) do
    if columns[key] == nil then
      columns[key] = defaultValue and true or false
    else
      columns[key] = columns[key] and true or false
    end
  end
  local removeKeys = {}
  for key in pairs(columns) do
    if not OPTIONAL_BOARD_COLUMN_KEYS[key] then
      removeKeys[#removeKeys + 1] = key
    end
  end
  for _, key in ipairs(removeKeys) do
    columns[key] = nil
  end
  if ui then
    ui.liveBoardColumns = columns
  end
  UIState.boardColumnVisibility = columns
  return columns
end

local function RefreshBoardColumnToggles(frame)
  if not frame then
    return
  end
  local columns = EnsureBoardColumnVisibility()
  for _, check in ipairs(frame.memberColumnToggles or {}) do
    local key = TrimText(check.__columnKey)
    if check.SetChecked then
      check:SetChecked(columns[key] == true)
    end
  end
end

local function ApplyBoardColumnVisibility(frame)
  if not frame then
    return
  end
  local columns = EnsureBoardColumnVisibility()
  frame.__memberColumnVisibility = columns
  local sortKey = TrimText(frame.__memberSortKey)
  if OPTIONAL_BOARD_COLUMN_KEYS[sortKey] and columns[sortKey] ~= true then
    frame.__memberSortKey = "roll"
    frame.__memberSortDescending = false
  end
  RefreshBoardColumnToggles(frame)
end

local function SetBoardColumnVisibility(frame, key, visible)
  local resolvedKey = TrimText(key)
  if not OPTIONAL_BOARD_COLUMN_KEYS[resolvedKey] then
    return
  end
  local columns = EnsureBoardColumnVisibility()
  columns[resolvedKey] = visible and true or false
  local ui = GetRootUiSettings()
  if ui then
    ui.liveBoardColumns = columns
  end
  UIState.boardColumnVisibility = columns
  ApplyBoardColumnVisibility(frame)
end
local CyclePreviewSelectedMember = shared.CyclePreviewSelectedMember
local ApplySimulationAward = shared.ApplySimulationAward
local GetBoardPreviewState = shared.GetBoardPreviewState
local SetBoardPreviewState = shared.SetBoardPreviewState
local PersistBoardState = shared.PersistBoardState
local GetEntryVoteTargetForVoter = shared.GetEntryVoteTargetForVoter
local SetEntryVoteTarget = shared.SetEntryVoteTarget
local GetEntryVoteVoterKeys = shared.GetEntryVoteVoterKeys
local GetEntryVoteCount = shared.GetEntryVoteCount
local RaisePopupFrameAboveEditor
local ApplyPopupBoardResizeLimits
local CapturePopupBoardGeometry
local ApplyPopupBoardGeometry
local EnforcePopupBoardMinimumSize
local StartPopupBoardResize
local StopPopupBoardResize
local BindPopupBoardDragScripts
local ApplyBoardSectionSkin
local ApplyBoardButtonStyle
local ApplyPopupBoardLayout
local ApplyEmbeddedBoardLayout
local SetBoardCollapsed
local LayoutBoardFrame
local SetBoardSelectedMember
local ShowSelectionNotePopup
local HideBoardItemTooltip
local ShowBoardItemTooltip
local ApplyBoardMemberRowLayout
local RefreshBoardHeaderSortState
local BuildBoardHeaderButton
local ResolveEntryVotedKey
local ToggleBoardVoteForMember
local EnsureBoardMemberRows
local UpdateBoardMemberRows
local ResolveBoardMemberTotalPoints
local ResolveBoardProjectedPoints
local BuildAwardStatusDetail
local SetBoardItemButtonState
local BuildBoardItemButtonTitle
local SelectNextUnawardedBoardItem
local RenderBoardItemButtons
local RenderPreviewBoard
local SetBoardAwardStatus
local CountPendingBoardSelections
local CreateBoardFrameScaffold
local OpenChangeRollPopup
local OpenTradeHelperPopup

local function CanSelfAssignLiveBoard(activeSession, profileName)
  if not activeSession or not IsSelfHost(activeSession) then
    return false
  end
  local resolvedProfileName = TrimText(profileName ~= "" and profileName or (type(activeSession) == "table" and activeSession.profileName) or RuntimeState.profileName)
  if type(GetLootProfileAccessInfo) == "function" then
    local access = GetLootProfileAccessInfo(resolvedProfileName)
    if resolvedProfileName == "Simulation" then
      return type(access) == "table" and access.canAssignLoot and true or false
    end
    if type(access) == "table" and access.canTransferMaster then
      return access.canAssignLoot and true or false
    end
  end
  if type(GetLootProfileAccessInfoForRole) == "function" then
    local hostAccess = GetLootProfileAccessInfoForRole(resolvedProfileName, "SESSION_HOST")
    return type(hostAccess) == "table" and hostAccess.canAssignLoot and true or false
  end
  return false
end

local function GetSimulationPreviewAccess(preview)
  if not preview or not preview.isSimulation or type(GetLootProfileAccessInfo) ~= "function" then
    return nil
  end
  local profileName = TrimText(preview.profileName)
  if profileName == "" then
    profileName = "Simulation"
  end
  local rootProvider = rawget(_G, "LIDCOUNCIL_RootDB")
  local root = type(rootProvider) == "function" and rootProvider() or nil
  if type(root) == "table" and (type(root.profiles) ~= "table" or type(root.profiles[profileName]) ~= "table") then
    return nil
  end
  local access = GetLootProfileAccessInfo(profileName)
  if type(access) ~= "table" then
    return nil
  end
  return access
end

local function CanSelfVoteSimulationPreview(preview)
  local access = GetSimulationPreviewAccess(preview)
  return type(access) == "table" and access.canOpenLiveBoard and access.canVote and true or false
end

local function CanSelfAssignSimulationPreview(preview)
  local access = GetSimulationPreviewAccess(preview)
  if type(access) == "table" then
    return access.canAssignLoot and true or false
  end
  return false
end

local DEFAULT_POPUP_BOARD_WIDTH = 780
local DEFAULT_POPUP_BOARD_HEIGHT = 560
local MIN_POPUP_BOARD_WIDTH = 780
local MIN_POPUP_BOARD_HEIGHT = 500

local CreateLootBoardLayoutHelpers = INTERNAL_API.LIDCOUNCIL_CreateLootBoardLayoutHelpers_Impl
if type(CreateLootBoardLayoutHelpers) ~= "function" then
  error("LidCouncil missing loot board layout helpers.")
end

local boardLayoutHelpers = CreateLootBoardLayoutHelpers({
  ApplyFrameSkin = ApplyFrameSkin,
  WrapColor = WrapColor,
  TrimText = TrimText,
  UIState = UIState,
  GetLiveLootBoardSummary = GetLiveLootBoardSummary,
  GetApplyBoardMemberRowLayout = function()
    return ApplyBoardMemberRowLayout
  end,
  DEFAULT_POPUP_BOARD_WIDTH = DEFAULT_POPUP_BOARD_WIDTH,
  DEFAULT_POPUP_BOARD_HEIGHT = DEFAULT_POPUP_BOARD_HEIGHT,
  MIN_POPUP_BOARD_WIDTH = MIN_POPUP_BOARD_WIDTH,
  MIN_POPUP_BOARD_HEIGHT = MIN_POPUP_BOARD_HEIGHT,
})

RaisePopupFrameAboveEditor = boardLayoutHelpers.RaisePopupFrameAboveEditor
ApplyPopupBoardResizeLimits = boardLayoutHelpers.ApplyPopupBoardResizeLimits
CapturePopupBoardGeometry = boardLayoutHelpers.CapturePopupBoardGeometry
ApplyPopupBoardGeometry = boardLayoutHelpers.ApplyPopupBoardGeometry
EnforcePopupBoardMinimumSize = boardLayoutHelpers.EnforcePopupBoardMinimumSize
StartPopupBoardResize = boardLayoutHelpers.StartPopupBoardResize
StopPopupBoardResize = boardLayoutHelpers.StopPopupBoardResize
BindPopupBoardDragScripts = boardLayoutHelpers.BindPopupBoardDragScripts
ApplyBoardSectionSkin = boardLayoutHelpers.ApplyBoardSectionSkin
ApplyBoardButtonStyle = boardLayoutHelpers.ApplyBoardButtonStyle
ApplyPopupBoardLayout = boardLayoutHelpers.ApplyPopupBoardLayout
ApplyEmbeddedBoardLayout = boardLayoutHelpers.ApplyEmbeddedBoardLayout
SetBoardCollapsed = boardLayoutHelpers.SetBoardCollapsed
LayoutBoardFrame = boardLayoutHelpers.LayoutBoardFrame

local CreateLootBoardActionHelpers = INTERNAL_API.LIDCOUNCIL_CreateLootBoardActionHelpers_Impl
if type(CreateLootBoardActionHelpers) ~= "function" then
  error("LidCouncil missing loot board action helpers.")
end

local boardActionHelpers = CreateLootBoardActionHelpers({
  ApplyFrameSkin = ApplyFrameSkin,
  ApplyBoardButtonStyle = ApplyBoardButtonStyle,
  BuildMemberDisplayText = BuildMemberDisplayText,
  GetActiveLootSession = GetActiveLootSession,
  IsSelfHost = IsSelfHost,
  CanSelfAssignLiveBoard = CanSelfAssignLiveBoard,
  EnsureRuntimeSession = EnsureRuntimeSession,
  ResolveRollOptions = ResolveRollOptions,
  RecordChoice = RecordChoice,
  SendSessionComm = SendSessionComm,
  GetBroadcastDistribution = GetBroadcastDistribution,
  GetSessionCommProfileName = GetSessionCommProfileName,
  ResolveSessionUnit = ResolveSessionUnit,
  RefreshBoardAndEditor = RefreshBoardAndEditor,
  PersistRuntimeBoardHistory = PersistRuntimeBoardHistory,
  RuntimeState = RuntimeState,
  TrimText = TrimText,
  ResolveOptionIconTexture = ResolveOptionIconTexture,
  GetSelfKey = GetSelfKey,
})

OpenChangeRollPopup = boardActionHelpers.OpenChangeRollPopup
OpenTradeHelperPopup = boardActionHelpers.OpenTradeHelperPopup

local CreateLootBoardOverlayHelpers = INTERNAL_API.LIDCOUNCIL_CreateLootBoardOverlayHelpers_Impl
if type(CreateLootBoardOverlayHelpers) ~= "function" then
  error("LidCouncil missing loot board overlay helpers.")
end

local boardOverlayHelpers = CreateLootBoardOverlayHelpers({
  UIState = UIState,
  ApplyFrameSkin = ApplyFrameSkin,
  RaisePopupFrameAboveEditor = RaisePopupFrameAboveEditor,
  ApplyBoardButtonStyle = ApplyBoardButtonStyle,
  NormalizeSelectionNote = NormalizeSelectionNote,
  WrapColor = WrapColor,
  TrimText = TrimText,
})

ShowSelectionNotePopup = boardOverlayHelpers.ShowSelectionNotePopup
HideBoardItemTooltip = boardOverlayHelpers.HideBoardItemTooltip
ShowBoardItemTooltip = boardOverlayHelpers.ShowBoardItemTooltip

local CreateLootBoardScaffoldHelpers = INTERNAL_API.LIDCOUNCIL_CreateLootBoardScaffoldHelpers_Impl
if type(CreateLootBoardScaffoldHelpers) ~= "function" then
  error("LidCouncil missing loot board scaffold helpers.")
end

local boardScaffoldHelpers = CreateLootBoardScaffoldHelpers({
  ApplyFrameSkin = ApplyFrameSkin,
  ResolveOptionalFunction = ResolveOptionalFunction,
  BindPopupBoardDragScripts = BindPopupBoardDragScripts,
  ApplyPopupBoardResizeLimits = ApplyPopupBoardResizeLimits,
  RaisePopupFrameAboveEditor = RaisePopupFrameAboveEditor,
  ApplyBoardButtonStyle = ApplyBoardButtonStyle,
  ApplyBoardSectionSkin = ApplyBoardSectionSkin,
  StartPopupBoardResize = StartPopupBoardResize,
  StopPopupBoardResize = StopPopupBoardResize,
  ShowBoardItemTooltip = ShowBoardItemTooltip,
  HideBoardItemTooltip = HideBoardItemTooltip,
  TrimText = TrimText,
  DEFAULT_POPUP_BOARD_WIDTH = DEFAULT_POPUP_BOARD_WIDTH,
  DEFAULT_POPUP_BOARD_HEIGHT = DEFAULT_POPUP_BOARD_HEIGHT,
})

CreateBoardFrameScaffold = boardScaffoldHelpers.CreateBoardFrameScaffold

local CreateLootBoardRowHelpers = INTERNAL_API.LIDCOUNCIL_CreateLootBoardRowHelpers_Impl
if type(CreateLootBoardRowHelpers) ~= "function" then
  error("LidCouncil missing loot board row helpers.")
end

local boardRowHelpers = CreateLootBoardRowHelpers({
  TrimText = TrimText,
  GetEntryVoteTargetForVoter = GetEntryVoteTargetForVoter,
  GetEntryVoteVoterKeys = GetEntryVoteVoterKeys,
  GetEntryVoteCount = GetEntryVoteCount,
  GetSelfKey = GetSelfKey,
  GetBoardPreviewState = GetBoardPreviewState,
  PersistBoardState = PersistBoardState,
  GetActiveLootSession = GetActiveLootSession,
  EnsureRuntimeSession = EnsureRuntimeSession,
  RuntimeState = RuntimeState,
  SetEntryVoteTarget = SetEntryVoteTarget,
  GetBroadcastDistribution = GetBroadcastDistribution,
  SendSessionComm = SendSessionComm,
  NormalizeSelectionNote = NormalizeSelectionNote,
  BuildMemberDisplayText = BuildMemberDisplayText,
  FormatSelectionLabel = FormatSelectionLabel,
  GetLootProfileAccessInfo = GetLootProfileAccessInfo,
  ResolveRollOptions = ResolveRollOptions,
  RecordLootVoteStat = RecordLootVoteStat,
  IsSelfHost = IsSelfHost,
  NOTE_ICON_TEXTURE = NOTE_ICON_TEXTURE,
  GetShowSelectionNotePopup = function()
    return ShowSelectionNotePopup
  end,
  GetSetBoardSelectedMember = function()
    return SetBoardSelectedMember
  end,
  ApplyBoardButtonStyle = ApplyBoardButtonStyle,
})

RefreshBoardHeaderSortState = boardRowHelpers.RefreshBoardHeaderSortState
BuildBoardHeaderButton = boardRowHelpers.BuildBoardHeaderButton
ResolveEntryVotedKey = boardRowHelpers.ResolveEntryVotedKey
ToggleBoardVoteForMember = boardRowHelpers.ToggleBoardVoteForMember
ApplyBoardMemberRowLayout = boardRowHelpers.ApplyBoardMemberRowLayout
EnsureBoardMemberRows = boardRowHelpers.EnsureBoardMemberRows
ResolveBoardMemberTotalPoints = boardRowHelpers.ResolveBoardMemberTotalPoints
UpdateBoardMemberRows = boardRowHelpers.UpdateBoardMemberRows

SetBoardSelectedMember = function(frame, memberKey)
  if not frame then
    return
  end

  local preview = frame.__previewState
  if type(preview) == "table" and preview.isSimulation then
    preview.selectedMemberKey = memberKey
  else
    RuntimeState.selectedMemberKey = memberKey
  end

  if frame.RefreshBoard then
    frame:RefreshBoard()
  end
end

local function UpdateAssignDropdown(frame, rows, selectedRow, emptyText)
  if not frame or not frame.assignDropdown then
    return
  end

  local fallbackText = tostring(emptyText or "Select session member")
  frame.__assignRows = rows or {}
  frame.__assignSelectedKey = selectedRow and selectedRow.key or nil
  frame.assignDropdown:SetSelectedValue(frame.__assignSelectedKey)
  frame.assignDropdown:SetText(selectedRow and BuildMemberDisplayText(selectedRow, { iconSize = 14 }) or fallbackText)
  frame.assignDropdown:SetMenuBuilder(function()
    local entries = {}
    for _, row in ipairs(frame.__assignRows or {}) do
      entries[#entries + 1] = {
        text = BuildMemberDisplayText(row, { iconSize = 14 }) .. " - " .. FormatSelectionLabel(row.selection),
        checked = frame.__assignSelectedKey == row.key,
        func = function()
          SetBoardSelectedMember(frame, row.key)
        end,
      }
    end
    if #entries == 0 then
      entries[1] = {
        text = fallbackText,
        disabled = true,
        notCheckable = true,
      }
    end
    return {
      width = 320,
      entries = entries,
    }
  end)
end

SetBoardAwardStatus = function(frame, tone, headline, detail)
  if not frame or not frame.awardStatus then
    return
  end

  local titleText = TrimText(headline)
  local detailText = TrimText(detail)
  local statusBox = frame.awardStatusBox

  if titleText == "" and detailText == "" then
    frame.awardStatus:SetText("")
    frame.__awardStatusHeight = nil
    if statusBox then
      statusBox:Hide()
    end
    if type(LayoutBoardFrame) == "function" then
      LayoutBoardFrame(frame)
    end
    return
  end

  local titleColor = { 0.82, 0.82, 0.86 }
  local bgColor = { 0.07, 0.08, 0.11, 0.94 }
  if tone == "success" then
    titleColor = { 0.22, 0.88, 0.32 }
    bgColor = { 0.05, 0.12, 0.07, 0.94 }
  elseif tone == "ready" then
    titleColor = { 0.42, 0.92, 0.56 }
    bgColor = { 0.06, 0.14, 0.08, 0.94 }
  elseif tone == "warning" then
    titleColor = { 1.00, 0.72, 0.22 }
    bgColor = { 0.15, 0.10, 0.04, 0.94 }
  elseif tone == "projected" then
    titleColor = { 0.95, 0.82, 0.22 }
    bgColor = { 0.13, 0.10, 0.04, 0.94 }
  elseif tone == "waiting" then
    titleColor = { 1.00, 0.72, 0.22 }
    bgColor = { 0.15, 0.10, 0.04, 0.94 }
  end

  local parts = {}
  if titleText ~= "" then
    parts[#parts + 1] = WrapColor(titleText, titleColor)
  end
  if detailText ~= "" then
    parts[#parts + 1] = detailText
  end
  frame.awardStatus:SetText(table.concat(parts, "\n"))

  local function ResolveStatusHeight()
    if frame.awardStatus.GetStringHeight then
      return math.max(30, math.ceil((tonumber(frame.awardStatus:GetStringHeight()) or 0) + 14))
    end
    return 30
  end

  local statusHeight = ResolveStatusHeight()
  local heightChanged = math.abs((tonumber(frame.__awardStatusHeight) or 0) - statusHeight) > 0.5
  frame.__awardStatusHeight = statusHeight

  if statusBox then
    if statusBox.SetBackdropColor then
      statusBox:SetBackdropColor(bgColor[1], bgColor[2], bgColor[3], bgColor[4])
    end
    statusBox:Show()
  end

  if heightChanged and type(LayoutBoardFrame) == "function" then
    LayoutBoardFrame(frame)
    local postLayoutHeight = ResolveStatusHeight()
    if math.abs((tonumber(frame.__awardStatusHeight) or 0) - postLayoutHeight) > 0.5 then
      frame.__awardStatusHeight = postLayoutHeight
      LayoutBoardFrame(frame)
    end
  end
end

CountPendingBoardSelections = function(rows)
  local pendingCount = 0
  local resolvedCount = 0
  for _, row in ipairs(rows or {}) do
    local selectionKey = string.lower(TrimText(row and row.selection and (row.selection.optionKey or row.selection.label) or "pending"))
    if selectionKey == "" or selectionKey == "pending" then
      pendingCount = pendingCount + 1
    else
      resolvedCount = resolvedCount + 1
    end
  end
  return pendingCount, resolvedCount
end

local CreateLootBoardRenderHelpers = INTERNAL_API.LIDCOUNCIL_CreateLootBoardRenderHelpers_Impl
if type(CreateLootBoardRenderHelpers) ~= "function" then
  error("LidCouncil missing loot board render helpers.")
end

local boardRenderHelpers = CreateLootBoardRenderHelpers({
  TrimText = TrimText,
  ResolveLootAwardOutcome = ResolveLootAwardOutcome,
  ResolveLootPointValue = ResolveLootPointValue,
  GetBoardPreviewState = GetBoardPreviewState,
  EnsurePreviewBoardContext = EnsurePreviewBoardContext,
  ResolveLootLedgerTotals = ResolveLootLedgerTotals,
  EnsurePreviewSelectedMember = EnsurePreviewSelectedMember,
  FormatSelectionLabel = FormatSelectionLabel,
  BuildMemberDisplayText = BuildMemberDisplayText,
  IsRealSimulationItemKey = IsRealSimulationItemKey,
  UpdateBoardMemberRows = UpdateBoardMemberRows,
  ResolveEntryVotedKey = ResolveEntryVotedKey,
  CountPendingBoardSelections = CountPendingBoardSelections,
  SetBoardAwardStatus = SetBoardAwardStatus,
  UpdateAssignDropdown = UpdateAssignDropdown,
  GetLootProfileAccessInfo = GetLootProfileAccessInfo,
  RefreshBoardHeaderSortState = RefreshBoardHeaderSortState,
  RuntimeState = RuntimeState,
})

ResolveBoardProjectedPoints = boardRenderHelpers.ResolveBoardProjectedPoints
BuildAwardStatusDetail = boardRenderHelpers.BuildAwardStatusDetail
SetBoardItemButtonState = boardRenderHelpers.SetBoardItemButtonState
BuildBoardItemButtonTitle = boardRenderHelpers.BuildBoardItemButtonTitle
SelectNextUnawardedBoardItem = boardRenderHelpers.SelectNextUnawardedBoardItem
RenderBoardItemButtons = boardRenderHelpers.RenderBoardItemButtons
RenderPreviewBoard = boardRenderHelpers.RenderPreviewBoard

local function SetLiveBoardDetailTooltipState(frame, entry, itemKey)
  local button = frame and frame.detailTooltipButton or nil
  if not button then
    return
  end
  local hasEntry = type(entry) == "table"
  button.__itemTooltipLink = hasEntry and entry.link or nil
  button.__itemTooltipItemID = hasEntry and (entry.itemID or entry.link) or nil
  button.__itemTooltipName = hasEntry and (entry.name or itemKey) or itemKey
  local hasTooltip = hasEntry
    and (TrimText(button.__itemTooltipLink) ~= "" or button.__itemTooltipItemID ~= nil or TrimText(button.__itemTooltipName) ~= "")
  if button.EnableMouse then
    button:EnableMouse(hasTooltip and true or false)
  end
end

local function CreateBoardFrame()
  if UIState.boardFrame then
    return UIState.boardFrame
  end

  local frame
  frame = CreateBoardFrameScaffold({
    BuildBoardHeaderButton = BuildBoardHeaderButton,
    OnClose = function(currentFrame)
      if UIState.choicePopup and not GetActiveLootSession() then
        UIState.choicePopup:Hide()
      end
      currentFrame:Hide()
      RefreshEditor()
    end,
    OnCollapse = function(currentFrame)
      local nextCollapsed = not UIState.collapsed
      SetBoardCollapsed(currentFrame, nextCollapsed)
      if not nextCollapsed and currentFrame.RefreshBoard then
        currentFrame:RefreshBoard()
      end
    end,
    OnSendBoardSync = function(currentFrame)
      local sendBoardSync = ResolveOptionalFunction("LIDCOUNCIL_SendActiveLiveBoardSyncFlow")
      if type(sendBoardSync) == "function" then
        sendBoardSync()
      end
      if currentFrame and currentFrame.RefreshBoard then
        currentFrame:RefreshBoard()
      end
    end,
    OnTradeHelper = function(currentFrame)
      if type(OpenTradeHelperPopup) == "function" then
        OpenTradeHelperPopup(currentFrame)
      end
    end,
    OnToggleMemberColumn = function(currentFrame, columnKey, visible)
      SetBoardColumnVisibility(currentFrame, columnKey, visible)
      if currentFrame and currentFrame.RefreshBoard then
        currentFrame:RefreshBoard()
      end
    end,
    OnItemEnter = function(_, button)
      if button.__highlight then
        button.__highlight:Show()
        button.__highlight:SetVertexColor(0.90, 0.74, 0.18, button.__itemSelected and 0.20 or 0.12)
      end
      ShowBoardItemTooltip(button)
    end,
    OnItemLeave = function(_, button)
      if button.__highlight and not button.__itemSelected then
        button.__highlight:Hide()
      elseif button.__highlight then
        button.__highlight:Show()
        button.__highlight:SetVertexColor(0.90, 0.74, 0.18, 0.20)
      end
      HideBoardItemTooltip()
    end,
    OnItemClick = function(currentFrame, button)
      local preview = currentFrame.__previewState
      if preview then
        preview.selectedItemKey = button.__itemKey
        if currentFrame.RefreshBoard then
          currentFrame:RefreshBoard()
        end
        if preview.isSimulation then
          local openSimulationChoicePopup = INTERNAL_API.LIDCOUNCIL_OpenSimulationChoicePopup_Impl
          if type(openSimulationChoicePopup) == "function" then
            local entry = preview.items and preview.items[button.__itemKey] or nil
            local selfSelection = entry and entry.selections and entry.selections[preview.selfKey or ""] or nil
            local pending = string.lower(TrimText(selfSelection and (selfSelection.optionKey or selfSelection.label) or "pending")) == "pending"
            if pending and not (entry and entry.awardedAt) then
              openSimulationChoicePopup(preview, button.__itemKey)
            end
          end
        end
      else
        RuntimeState.selectedItemKey = button.__itemKey
        if currentFrame.RefreshBoard then
          currentFrame:RefreshBoard()
        end
      end
    end,
    OnResizeStart = function(currentFrame, resizeHandle)
      StartPopupBoardResize(currentFrame, resizeHandle)
    end,
    OnResizeStop = function(currentFrame, resizeHandle)
      StopPopupBoardResize(currentFrame, resizeHandle)
    end,
    OnPrevAssign = function(currentFrame)
      local preview = currentFrame.__previewState
      if preview and preview.isSimulation then
        if not CanSelfAssignSimulationPreview(preview) then
          return
        end
        local entry = preview.items and preview.items[preview.selectedItemKey] or nil
        if entry then
          CyclePreviewSelectedMember(preview, entry, -1)
          currentFrame:RefreshBoard()
        end
        return
      end

      local activeSession, profileName = GetActiveLootSession()
      local entry = RuntimeState.items[RuntimeState.selectedItemKey]
      if activeSession and entry then
        EnsureRuntimeSession(activeSession, profileName)
        CycleSelectedMember(activeSession, entry, -1)
        currentFrame:RefreshBoard()
      end
    end,
    OnNextAssign = function(currentFrame)
      local preview = currentFrame.__previewState
      if preview and preview.isSimulation then
        if not CanSelfAssignSimulationPreview(preview) then
          return
        end
        local entry = preview.items and preview.items[preview.selectedItemKey] or nil
        if entry then
          CyclePreviewSelectedMember(preview, entry, 1)
          currentFrame:RefreshBoard()
        end
        return
      end

      local activeSession, profileName = GetActiveLootSession()
      local entry = RuntimeState.items[RuntimeState.selectedItemKey]
      if activeSession and entry then
        EnsureRuntimeSession(activeSession, profileName)
        CycleSelectedMember(activeSession, entry, 1)
        currentFrame:RefreshBoard()
      end
    end,
    OnAssign = function(currentFrame)
      local preview = currentFrame.__previewState
      if preview and preview.isSimulation then
        if not CanSelfAssignSimulationPreview(preview) then
          return
        end
        local entry = preview.items and preview.items[preview.selectedItemKey] or nil
        if not entry or entry.awardedAt then
          return
        end
        local _, selectedMember = EnsurePreviewSelectedMember(preview, entry)
        if not selectedMember or TrimText(selectedMember.key) == "" then
          return
        end
        if ApplySimulationAward(preview, entry, selectedMember.key) then
          preview.selectedItemKey = SelectNextUnawardedBoardItem(preview.itemOrder, preview.items, preview.selectedItemKey)
          PersistBoardState(currentFrame)
          currentFrame:RefreshBoard()
          RefreshEditor()
        end
        return
      end

      local activeSession, profileName = GetActiveLootSession()
      if not activeSession or not CanSelfAssignLiveBoard(activeSession, profileName) then
        return
      end
      if not EnsureRuntimeSession(activeSession, profileName) then
        return
      end

      local entry = RuntimeState.items[RuntimeState.selectedItemKey]
      if not entry or entry.awardedAt then
        return
      end

      local _, selectedMember = EnsureSelectedMember(activeSession, entry)
      if not selectedMember or TrimText(selectedMember.key) == "" then
        return
      end

      local itemName = entry.name or entry.link or entry.key
      local itemLevel = tonumber(entry.itemLevel)
      local points = ResolveBoardProjectedPoints(profileName, activeSession, entry, selectedMember.key)
      entry.projectedPoints = points
      local note = BuildAwardNote(entry, activeSession)
      local awardChangedAt = (type(GetServerTime) == "function" and GetServerTime()) or (time and time() or 0)
      local ok = ApplyAwardMessage(entry.key, selectedMember.key, itemName, itemLevel, points, note, activeSession, profileName, awardChangedAt)
      if not ok then
        return
      end

      local distribution = GetBroadcastDistribution()
      if distribution and type(SendSessionComm) == "function" then
        SendSessionComm(
          "AWARD",
          distribution,
          nil,
          activeSession.id,
          GetSessionCommProfileName(activeSession, profileName),
          entry.key,
          selectedMember.key,
          itemName,
          itemLevel or "",
          points,
          note,
          awardChangedAt
        )
      end
      RuntimeState.selectedItemKey = SelectNextUnawardedBoardItem(RuntimeState.itemOrder, RuntimeState.items, RuntimeState.selectedItemKey)
      RefreshBoardAndEditor({
        reason = "award",
        itemKey = entry.key,
      })
    end,
    OnVote = function(currentFrame)
      local preview = GetBoardPreviewState(currentFrame)
      if preview and preview.isSimulation then
        if not CanSelfVoteSimulationPreview(preview) then
          return
        end
        local entry = preview.items and preview.items[preview.selectedItemKey] or nil
        local selectedMember = nil
        if entry then
          local _, resolvedSelectedMember = EnsurePreviewSelectedMember(preview, entry)
          selectedMember = resolvedSelectedMember
        end
        if not entry or not selectedMember or TrimText(selectedMember.key) == "" or entry.awardedAt then
          return
        end
        ToggleBoardVoteForMember(currentFrame, selectedMember.key)
        return
      end

      local activeSession, profileName = GetActiveLootSession()
      if not activeSession or not EnsureRuntimeSession(activeSession, profileName) then
        return
      end
      local entry = RuntimeState.items[RuntimeState.selectedItemKey]
      local selectedMember = nil
      if entry then
        local _, resolvedSelectedMember = EnsureSelectedMember(activeSession, entry)
        selectedMember = resolvedSelectedMember
      end
      if not entry or not selectedMember or TrimText(selectedMember.key) == "" or entry.awardedAt then
        return
      end
      ToggleBoardVoteForMember(currentFrame, selectedMember.key)
    end,
    OnChangeRoll = function(currentFrame)
      local activeSession, profileName = GetActiveLootSession()
      if not activeSession or not CanSelfAssignLiveBoard(activeSession, profileName) then
        return
      end
      if not EnsureRuntimeSession(activeSession, profileName) then
        return
      end
      local entry = RuntimeState.items[RuntimeState.selectedItemKey]
      if not entry or entry.awardedAt then
        return
      end
      local _, selectedMember = EnsureSelectedMember(activeSession, entry)
      if not selectedMember or TrimText(selectedMember.key) == "" then
        return
      end
      if type(OpenChangeRollPopup) == "function" then
        OpenChangeRollPopup(currentFrame, entry, selectedMember.key)
      end
    end,
    OnUnassign = function(currentFrame)
      local preview = GetBoardPreviewState(currentFrame)
      if preview and preview.isSimulation then
        if not CanSelfAssignSimulationPreview(preview) then
          return
        end
        local entry = preview.items and preview.items[preview.selectedItemKey] or nil
        if not entry or not entry.awardedAt then
          return
        end
        local ok = RemoveAwardFromEntry(preview.profileName, entry)
        if not ok then
          return
        end
      PersistBoardState(currentFrame)
      currentFrame:RefreshBoard()
      RefreshEditor()
      return
      end

      local activeSession, profileName = GetActiveLootSession()
      if not activeSession or not EnsureRuntimeSession(activeSession, profileName) then
        return
      end
      if not CanSelfAssignLiveBoard(activeSession, profileName) then
        return
      end
      local entry = RuntimeState.items[RuntimeState.selectedItemKey]
      if not entry or not entry.awardedAt then
        return
      end
      local awardChangedAt = (type(GetServerTime) == "function" and GetServerTime()) or (time and time() or 0)
      local ok = RemoveAwardFromEntry(profileName, entry, awardChangedAt)
      if not ok then
        return
      end
      local distribution = GetBroadcastDistribution()
      if distribution and type(SendSessionComm) == "function" then
        SendSessionComm(
          "UNASSIGN",
          distribution,
          nil,
          activeSession.id,
          GetSessionCommProfileName(activeSession, profileName),
          entry.key,
          awardChangedAt
        )
      end
      PersistBoardState(currentFrame)
      RefreshBoardAndEditor({
        reason = "unassign",
        itemKey = entry.key,
      })
    end,
  })
  ApplyBoardColumnVisibility(frame)
  RefreshBoardHeaderSortState(frame)

  function frame:RefreshBoard()
    ApplyBoardColumnVisibility(self)
    LayoutBoardFrame(self)
    RefreshBoardHeaderSortState(self)
    if self.boardSyncBtn then
      self.boardSyncBtn:Disable()
    end
    if self.tradeHelperBtn then
      self.tradeHelperBtn:Disable()
    end
    if RenderPreviewBoard(self) then
      if UIState.collapsed then
        SetBoardCollapsed(self, true)
      end
      return
    end
    local activeSession, profileName = GetActiveLootSession()
    if not activeSession or not EnsureRuntimeSession(activeSession, profileName) then
      self.note:SetText("No active LidCouncil session is currently running.")
      for _, button in ipairs(self.itemButtons or {}) do
        button:Hide()
      end
      self.__assignedHeaderOffsetY = nil
      if self.assignedHeader then
        self.assignedHeader:Hide()
      end
      self.__tradedHeaderOffsetY = nil
      if self.tradedHeader then
        self.tradedHeader:Hide()
      end
      self.detailIcon:SetTexture(nil)
      SetLiveBoardDetailTooltipState(self, nil, "")
      self.detailTitle:SetText("")
      self.detailMeta:SetText("")
      self.assignTarget:SetText("")
      SetBoardAwardStatus(self)
      UpdateBoardMemberRows(self, {}, nil, nil, nil, nil)
      UpdateAssignDropdown(self, {}, nil, "No active session")
      self.prevAssignBtn:Hide()
      self.prevAssignBtn:Disable()
      self.nextAssignBtn:Hide()
      self.nextAssignBtn:Disable()
      self.assignBtn:Disable()
      self.voteBtn:Disable()
      self.unassignBtn:Disable()
      if UIState.collapsed then
        SetBoardCollapsed(self, true)
      end
      return
    end

    if not CanSelfAssignLiveBoard(activeSession, profileName) then
      self.note:SetText("Council members can review the Live Board and cast votes here. Only the session host can assign or unassign items.")
    else
      self.note:SetText("Live loot selection and assignment board for the session host. Awarding an item here applies points into the Loot List for the whole session.")
    end
    if self.boardSyncBtn then
      if CanSelfAssignLiveBoard(activeSession, profileName) then
        self.boardSyncBtn:Enable()
      else
        self.boardSyncBtn:Disable()
      end
    end
    if self.tradeHelperBtn then
      if CanSelfAssignLiveBoard(activeSession, profileName) then
        self.tradeHelperBtn:Enable()
      else
        self.tradeHelperBtn:Disable()
      end
    end

    RenderBoardItemButtons(self, RuntimeState.itemOrder, RuntimeState.items, RuntimeState.selectedItemKey)

    local selected = RuntimeState.items[RuntimeState.selectedItemKey]
    if not selected and #RuntimeState.itemOrder > 0 then
      RuntimeState.selectedItemKey = RuntimeState.itemOrder[#RuntimeState.itemOrder]
      selected = RuntimeState.items[RuntimeState.selectedItemKey]
    end
    if not selected then
      self.detailIcon:SetTexture(nil)
      SetLiveBoardDetailTooltipState(self, nil, "")
      self.detailTitle:SetText("No tracked loot items yet.")
      self.detailMeta:SetText("")
      self.assignTarget:SetText("")
      SetBoardAwardStatus(self)
      UpdateBoardMemberRows(self, {}, nil, nil, nil, nil)
      UpdateAssignDropdown(self, {}, nil, "No tracked items")
      self.prevAssignBtn:Hide()
      self.prevAssignBtn:Disable()
      self.nextAssignBtn:Hide()
      self.nextAssignBtn:Disable()
      self.assignBtn:Disable()
      self.voteBtn:Disable()
      self.unassignBtn:Disable()
      if self.changeRollBtn then
        self.changeRollBtn:Disable()
      end
      if UIState.collapsed then
        SetBoardCollapsed(self, true)
      end
      return
    end

    self.detailIcon:SetTexture(selected.icon)
    SetLiveBoardDetailTooltipState(self, selected, RuntimeState.selectedItemKey)
    local isRealSimulationItem = IsRealSimulationItemKey(selected.key)
    self.detailTitle:SetText(selected.link or selected.name or selected.key)
    local detailMetaLabel = isRealSimulationItem and "Real Simulation" or ("Item Key: " .. tostring(selected.key))
    if isRealSimulationItem then
      local sourceParts = {}
      if TrimText(selected.instanceName) ~= "" then
        sourceParts[#sourceParts + 1] = tostring(selected.instanceName)
      end
      if TrimText(selected.encounterName) ~= "" then
        sourceParts[#sourceParts + 1] = tostring(selected.encounterName)
      end
      if #sourceParts > 0 then
        detailMetaLabel = detailMetaLabel .. " | " .. table.concat(sourceParts, " / ")
      end
    end
    self.detailMeta:SetText(
      string.format(
        "%s%s",
        detailMetaLabel,
        selected.itemLevel and (" | Item Level: " .. tostring(selected.itemLevel)) or ""
      )
    )

    local memberRows, selectedMember = EnsureSelectedMember(activeSession, selected)
    local totalsByCharacter = ResolveLootLedgerTotals(profileName)

    if selectedMember then
      local selection = selectedMember.selection or (selected.selections and selected.selections[selectedMember.key] or nil)
      local selectionLabel = FormatSelectionLabel(selection)
      self.assignTarget:SetText("|cffffd100Current Target|r\n" .. BuildMemberDisplayText(selectedMember, { iconSize = 16 }) .. " | " .. tostring(selectionLabel))
    else
      self.assignTarget:SetText("|cffffd100Current Target|r\n(none)")
    end
    local pendingRollCount, resolvedRollCount = CountPendingBoardSelections(memberRows)

    if selected.awardedAt and selected.awardedTo then
      SetBoardAwardStatus(
        self,
        "success",
        "Award Complete",
        BuildAwardStatusDetail(profileName, selected)
      )
    else
      local projectedPoints = ResolveBoardProjectedPoints(profileName, activeSession, selected, selectedMember and selectedMember.key or nil)
      selected.projectedPoints = projectedPoints
      if pendingRollCount > 0 then
        SetBoardAwardStatus(
          self,
          "warning",
          "Await Rolls",
          isRealSimulationItem
            and string.format(
              "%d participant(s) still pending. You can still assign now.\nNo Loot List points will be written for this real simulation.",
              pendingRollCount
            )
            or string.format(
              "%d participant(s) still pending. You can still assign now.\nProjected award: %d point(s).",
              pendingRollCount,
              projectedPoints
            )
        )
      else
        SetBoardAwardStatus(
          self,
          "ready",
          "All Participants Selected",
          isRealSimulationItem
            and "No Loot List points will be written for this real simulation."
            or string.format("Projected award: %d point(s).", projectedPoints)
        )
      end
    end

    UpdateBoardMemberRows(self, memberRows, RuntimeState.selectedMemberKey, totalsByCharacter, ResolveEntryVotedKey(selected), selected.awardedTo, selected)
    UpdateAssignDropdown(self, memberRows, selectedMember, pendingRollCount > 0 and "Await rolls" or "Select session member")
    if not CanSelfAssignLiveBoard(activeSession, profileName) or selected.awardedAt or not selectedMember then
      self.assignBtn:Disable()
    else
      self.assignBtn:Enable()
    end
    if #memberRows > 1 and CanSelfAssignLiveBoard(activeSession, profileName) then
      self.prevAssignBtn:Hide()
      self.prevAssignBtn:Disable()
      self.nextAssignBtn:Hide()
      self.nextAssignBtn:Disable()
    else
      self.prevAssignBtn:Hide()
      self.prevAssignBtn:Disable()
      self.nextAssignBtn:Hide()
      self.nextAssignBtn:Disable()
    end
    if self.voteBtn then
      self.voteBtn:Hide()
    end
    if selected.awardedAt and CanSelfAssignLiveBoard(activeSession, profileName) then
      self.unassignBtn:Enable()
    else
      self.unassignBtn:Disable()
    end
    if self.changeRollBtn then
      if CanSelfAssignLiveBoard(activeSession, profileName) and selectedMember and not selected.awardedAt then
        self.changeRollBtn:Enable()
      else
        self.changeRollBtn:Disable()
      end
    end
    if UIState.collapsed then
      SetBoardCollapsed(self, true)
    end
    if selected.awardedAt then
      self.assignBtn:SetText("Item Awarded")
    else
      self.assignBtn:SetText("Assign Item")
    end
  end

  frame:HookScript("OnSizeChanged", function(self)
    if self.__resizing then
      return
    end
    if EnforcePopupBoardMinimumSize(self) then
      return
    end
    if not self.__embeddedParent and not UIState.collapsed then
      CapturePopupBoardGeometry(self)
    end
    if self.__embeddedParent or not UIState.collapsed then
      LayoutBoardFrame(self)
    end
  end)
  frame:HookScript("OnHide", function(self)
    if self.__resizing then
      StopPopupBoardResize(self, self.resizeHandle)
    end
  end)

  LayoutBoardFrame(frame)
  UIState.boardFrame = frame
  return frame
end

local function OpenLiveLootBoard()
  local frame = CreateBoardFrame()
  ApplyPopupBoardLayout(frame)
  frame.__previewState = GetBoardPreviewState(frame)
  frame:Show()
  frame:RefreshBoard()
  SetBoardCollapsed(frame, UIState.collapsed)
end

local function CloseLiveLootBoard()
  if UIState.boardFrame then
    UIState.boardFrame:Hide()
  end
end

local function AttachLiveLootBoard(parent)
  local frame = CreateBoardFrame()
  frame.__previewState = GetBoardPreviewState(frame)
  ApplyEmbeddedBoardLayout(frame, parent)
  frame:Show()
  frame:RefreshBoard()
  SetBoardCollapsed(frame, false)
end

local function PopoutLiveLootBoard()
  local frame = CreateBoardFrame()
  ApplyPopupBoardLayout(frame)
  frame.__previewState = GetBoardPreviewState(frame)
  frame:Show()
  frame:RefreshBoard()
  SetBoardCollapsed(frame, UIState.collapsed)
end

local function PreviewLiveLootBoard(profileName)
  local frame = CreateBoardFrame()
  ApplyPopupBoardLayout(frame)
  SetBoardPreviewState(frame, BuildPreviewBoardState(profileName))
  PersistBoardState(frame)
  frame:Show()
  frame:RefreshBoard()
  SetBoardCollapsed(frame, false)
end

local function RefreshLiveLootBoard()
  local frame = UIState.boardFrame
  if frame and frame.RefreshBoard then
    frame.__previewState = GetBoardPreviewState(frame)
    frame:RefreshBoard()
    return true
  end
  return false
end

INTERNAL_API.LIDCOUNCIL_CreateBoardFrame_Impl = CreateBoardFrame
INTERNAL_API.LIDCOUNCIL_ApplyPopupBoardLayout_Impl = ApplyPopupBoardLayout
INTERNAL_API.LIDCOUNCIL_ApplyEmbeddedBoardLayout_Impl = ApplyEmbeddedBoardLayout
INTERNAL_API.LIDCOUNCIL_SetBoardCollapsed_Impl = SetBoardCollapsed
INTERNAL_API.LIDCOUNCIL_OpenLiveLootBoard_Impl = OpenLiveLootBoard
INTERNAL_API.LIDCOUNCIL_CloseLiveLootBoard_Impl = CloseLiveLootBoard
INTERNAL_API.LIDCOUNCIL_AttachLiveLootBoard_Impl = AttachLiveLootBoard
INTERNAL_API.LIDCOUNCIL_PopoutLiveLootBoard_Impl = PopoutLiveLootBoard
INTERNAL_API.LIDCOUNCIL_PreviewLiveLootBoard_Impl = PreviewLiveLootBoard
INTERNAL_API.LIDCOUNCIL_RefreshLiveLootBoard_Impl = RefreshLiveLootBoard
