local ADDON_NAME, namespace = ...
local GuildPing = namespace.GuildPing
local UI = GuildPing.UIFactory

if not UI then
	return
end

local CreateWindow = UI.CreateWindow
local MakeDraggable = UI.MakeDraggable
local SetTitle = UI.SetTitle
local CreateButton = UI.CreateButton
local CreateLabel = UI.CreateLabel
local CreateInput = UI.CreateInput
local ROW_BACKDROP = UI.RowBackdrop
local GUILD_GREEN = UI.GuildGreen
local MUTED_COLOR = UI.MutedColor
local WARNING_GOLD = UI.WarningGold
local UPDATE_RED = UI.UpdateRed
local Unpack = unpack or table.unpack

local RESIGN_POPUP_KEY = "GUILD_PING_POKER_RESIGN"
local END_PRACTICE_POPUP_KEY = "GUILD_PING_POKER_END_PRACTICE"
local ABANDON_POPUP_KEY = "GUILD_PING_POKER_ABANDON"
local STACK_PRESETS = { 500, 1000, 2000 }
local FELT_GREEN = { 0.071, 0.239, 0.192, 0.98 }
local DEEP_FELT = { 0.035, 0.130, 0.105, 1 }
local OLD_BRASS = { 0.843, 0.659, 0.294, 1 }
local CARD_IVORY = { 0.957, 0.906, 0.773, 1 }
local OXBLOOD = { 0.478, 0.149, 0.212, 1 }
local CARD_INK = { 0.090, 0.105, 0.125, 1 }
local SUIT_RED = { 0.67, 0.10, 0.14, 1 }
local LOGO_TEXTURE = "Interface\\AddOns\\" .. ADDON_NAME .. "\\Media\\GuildPing-Logo.png"
local SUIT_TEXTURE = "Interface\\AddOns\\" .. ADDON_NAME .. "\\Media\\PokerSuits.png"
local SUIT_COORDS = {
	c = { 0, 0.25 },
	d = { 0.25, 0.50 },
	h = { 0.50, 0.75 },
	s = { 0.75, 1 },
}
local DISPLAY_RANKS = { T = "10" }
local HISTORY_SUITS = { c = "♣", d = "♦", h = "♥", s = "♠" }

local function FormatNumber(value)
	local formatted = tostring(math.floor(tonumber(value) or 0))
	while true do
		local changed
		formatted, changed = formatted:gsub("^(-?%d+)(%d%d%d)", "%1,%2")
		if changed == 0 then return formatted end
	end
end

local function FormatInviteAge(receivedAt)
	local currentTime = GetTime and GetTime() or 0
	local seconds = math.max(0, math.floor(currentTime - (receivedAt or currentTime)))
	if seconds < 60 then return "just now" end
	return string.format("%dm ago", math.floor(seconds / 60))
end

local function SeatName(game, seat, localSeat)
	if game.practice then
		return seat == GuildPing.POKER_CHALLENGER and "Player 1" or "Player 2"
	end
	if seat == localSeat then return "You" end
	return GuildPing:GetPokerSeatPlayer(game, seat) or "Opponent"
end

local function CreateInviteRow(parent, width)
	local row = CreateFrame("Frame", nil, parent, "BackdropTemplate")
	row:SetSize(width, 54)
	row:SetBackdrop(ROW_BACKDROP)
	row:SetBackdropColor(0.047, 0.055, 0.078, 0.92)
	row:SetBackdropBorderColor(0.180, 0.204, 0.275, 0.95)

	row.senderText = CreateLabel(row, "", "GameFontNormal")
	row.senderText:SetPoint("LEFT", 10, 7)
	row.senderText:SetPoint("RIGHT", -190, 7)
	row.senderText:SetWordWrap(false)
	row.detailText = CreateLabel(row, "", "GameFontHighlightSmall")
	row.detailText:SetPoint("LEFT", 10, -10)
	row.detailText:SetPoint("RIGHT", -190, -10)
	row.detailText:SetWordWrap(false)
	row.detailText:SetTextColor(MUTED_COLOR:GetRGB())

	row.declineButton = CreateButton(row, "Decline", 78, 24)
	row.declineButton:SetPoint("RIGHT", -8, 0)
	row.declineButton:SetScript("OnClick", function()
		if row.inviteID then GuildPing:DeclinePokerChallenge(row.inviteID) end
	end)
	row.acceptButton = CreateButton(row, "Take Seat", 92, 24, "primary")
	row.acceptButton:SetPoint("RIGHT", row.declineButton, "LEFT", -6, 0)
	row.acceptButton:SetScript("OnClick", function()
		if not row.inviteID then return end
		local game, errorMessage = GuildPing:AcceptPokerChallenge(row.inviteID)
		if game then GuildPing:ShowPokerTable() else GuildPing:Print(errorMessage) end
	end)
	row:Hide()
	return row
end

local function PopulateInviteRow(row, invite, canAccept)
	if not invite then
		row.inviteID = nil
		row:Hide()
		return
	end
	row.inviteID = invite.id
	row.senderText:SetText(invite.sender .. " opened a heads-up table")
	row.detailText:SetText(string.format(
		"%s chips each  •  blinds 10/20  •  %s",
		FormatNumber(invite.startingStack),
		FormatInviteAge(invite.receivedAt)
	))
	row.acceptButton:SetEnabled(canAccept)
	row:Show()
end

local function RefreshStackButtons(lobby)
	for value, button in pairs(lobby.stackButtons) do
		button:SetVariant(value == lobby.startingStack and "primary" or "secondary")
	end
end

function GuildPing:CreatePokerLobby()
	if self.pokerLobby then return self.pokerLobby end
	local lobby = CreateWindow("GuildPingPokerLobbyFrame")
	lobby:SetSize(640, 650)
	lobby:SetPoint("CENTER")
	lobby:SetFrameStrata("DIALOG")
	MakeDraggable(lobby, lobby.header)
	SetTitle(lobby, "Guild Ping — Texas Hold 'Em")

	lobby.heading = CreateLabel(lobby, "Take a seat", "GameFontNormalLarge")
	lobby.heading:SetPoint("TOPLEFT", 24, -49)
	lobby.description = CreateLabel(
		lobby,
		"Play a complete heads-up match with play chips. The dealer button rotates each hand; the challenger hosts the casual table over private addon whispers.",
		"GameFontHighlight"
	)
	lobby.description:SetPoint("TOPLEFT", 24, -79)
	lobby.description:SetPoint("TOPRIGHT", -24, -79)
	lobby.description:SetWordWrap(true)

	lobby.challengeLabel = CreateLabel(lobby, "Guild member", "GameFontNormal")
	lobby.challengeLabel:SetPoint("TOPLEFT", 24, -137)
	lobby.challengeInput = CreateInput(lobby, 390, "Character or Character-Realm", 48)
	lobby.challengeInput:SetPoint("TOPLEFT", 26, -159)
	lobby.challengeButton = CreateButton(lobby, "Open Table", 164, 27, "primary")
	lobby.challengeButton:SetPoint("LEFT", lobby.challengeInput, "RIGHT", 12, 0)

	lobby.stackLabel = CreateLabel(lobby, "Starting play chips  •  blinds stay 10/20", "GameFontNormal")
	lobby.stackLabel:SetPoint("TOPLEFT", 24, -205)
	lobby.startingStack = GuildPing.POKER_DEFAULT_STACK or 1000
	lobby.stackButtons = {}
	for index, value in ipairs(STACK_PRESETS) do
		local button = CreateButton(lobby, FormatNumber(value), 116, 25)
		button:SetPoint("TOPLEFT", 26 + ((index - 1) * 124), -227)
		button:SetScript("OnClick", function()
			lobby.startingStack = value
			RefreshStackButtons(lobby)
		end)
		lobby.stackButtons[value] = button
	end
	RefreshStackButtons(lobby)

	lobby.challengeButton:SetScript("OnClick", function()
		local challenge, errorMessage = GuildPing:ChallengePokerPlayer(
			lobby.challengeInput:GetText(), lobby.startingStack
		)
		if challenge then
			lobby.challengeInput:ClearFocus()
			if challenge.practice then
				lobby.statusText:SetText("Local practice table opened.")
				GuildPing:ShowPokerTable()
			else
				lobby.statusText:SetText("Table invitation sent to " .. challenge.target .. ".")
			end
			lobby.statusText:SetTextColor(GUILD_GREEN:GetRGB())
		else
			lobby.statusText:SetText(errorMessage)
			lobby.statusText:SetTextColor(UPDATE_RED:GetRGB())
		end
		GuildPing:RefreshPokerLobby()
	end)
	lobby.challengeInput:SetScript("OnEnterPressed", function(self)
		self:ClearFocus()
		lobby.challengeButton:Click()
	end)

	lobby.statusText = CreateLabel(lobby, "", "GameFontHighlightSmall")
	lobby.statusText:SetPoint("TOPLEFT", 26, -266)
	lobby.statusText:SetPoint("RIGHT", -26, 0)
	lobby.statusText:SetWordWrap(false)
	lobby.statusText:SetTextColor(MUTED_COLOR:GetRGB())

	local activeDivider = lobby:CreateTexture(nil, "ARTWORK")
	activeDivider:SetColorTexture(0.235, 0.267, 0.345, 0.9)
	activeDivider:SetPoint("TOPLEFT", 24, -292)
	activeDivider:SetPoint("TOPRIGHT", -24, -292)
	activeDivider:SetHeight(1)
	lobby.activeHeader = CreateLabel(lobby, "Current table", "GameFontNormalLarge")
	lobby.activeHeader:SetPoint("TOPLEFT", 24, -310)
	lobby.activeText = CreateLabel(lobby, "", "GameFontHighlight")
	lobby.activeText:SetPoint("TOPLEFT", 26, -341)
	lobby.activeText:SetPoint("RIGHT", -180, 0)
	lobby.activeText:SetWordWrap(false)
	lobby.resumeButton = CreateButton(lobby, "Open Table", 128, 25, "primary")
	lobby.resumeButton:SetPoint("TOPRIGHT", -24, -331)
	lobby.resumeButton:SetScript("OnClick", function() GuildPing:ShowPokerTable() end)
	lobby.resumeButton:Hide()

	lobby.inviteHeader = CreateLabel(lobby, "Incoming table invitations", "GameFontNormalLarge")
	lobby.inviteHeader:SetPoint("TOPLEFT", 24, -386)
	lobby.inviteCount = CreateLabel(lobby, "", "GameFontHighlightSmall")
	lobby.inviteCount:SetPoint("LEFT", lobby.inviteHeader, "RIGHT", 10, -1)
	lobby.inviteCount:SetTextColor(MUTED_COLOR:GetRGB())
	lobby.inviteRows = {}
	for index = 1, 3 do
		local row = CreateInviteRow(lobby, 592)
		row:SetPoint("TOPLEFT", 24, -416 - ((index - 1) * 59))
		lobby.inviteRows[index] = row
	end
	lobby.emptyInvites = CreateLabel(lobby, "No incoming Hold 'Em invitations.", "GameFontDisable")
	lobby.emptyInvites:SetPoint("TOPLEFT", 28, -426)

	lobby.allGamesButton = CreateButton(lobby, "All Games", 104, 25)
	lobby.allGamesButton:SetPoint("BOTTOMLEFT", 24, 14)
	lobby.allGamesButton:SetScript("OnClick", function()
		if GuildPing.ShowGames then GuildPing:ShowGames() end
	end)
	lobby.closeButton = CreateButton(lobby, "Close", 90, 25)
	lobby.closeButton:SetPoint("BOTTOMRIGHT", -24, 14)
	lobby.closeButton:SetScript("OnClick", function() lobby:Hide() end)
	lobby:SetScript("OnShow", function() GuildPing:RefreshPokerLobby() end)
	lobby:Hide()
	self.pokerLobby = lobby
	return lobby
end

function GuildPing:RefreshPokerLobby()
	local lobby = self.pokerLobby
	if not lobby then return end
	self:CleanupPokerInvites()
	local game = self.activePokerGame
	local active = game and game.status == "ACTIVE"
	lobby.challengeButton:SetEnabled(self.pokerInitialized and not active)
	local currentStatus = lobby.statusText:GetText() or ""
	if self.outgoingPokerChallenge then
		lobby.statusText:SetText("Waiting for " .. self.outgoingPokerChallenge.target .. " to take the seat.")
		lobby.statusText:SetTextColor(WARNING_GOLD:GetRGB())
	elseif currentStatus == "" or currentStatus:find("^Waiting for ") then
		lobby.statusText:SetText("Invitations expire after five minutes. Play chips have no outside value.")
		lobby.statusText:SetTextColor(MUTED_COLOR:GetRGB())
	end

	if game then
		local opponent = game.practice and "Practice" or "vs " .. (self:GetPokerOpponent(game) or "Unknown")
		local stateText = game.status == "FINISHED" and "Match finished"
			or game.handStatus == "WAITING" and "Waiting for deal" or "In progress"
		lobby.activeText:SetText(string.format(
			"%s  •  Hand %d  •  %s / %s chips  •  %s",
			opponent,
			game.handNumber or 0,
			FormatNumber(game.stacks[GuildPing.POKER_CHALLENGER]),
			FormatNumber(game.stacks[GuildPing.POKER_OPPONENT]),
			stateText
		))
		lobby.resumeButton:Show()
		lobby.resumeButton:SetEnabled(true)
	else
		lobby.activeText:SetText("No Hold 'Em table in this session.")
		lobby.resumeButton:Hide()
		lobby.resumeButton:SetEnabled(false)
	end

	local invites = self:GetPokerInvites()
	lobby.inviteCount:SetText(string.format("(%d)", #invites))
	lobby.emptyInvites:SetShown(#invites == 0)
	for rowIndex, row in ipairs(lobby.inviteRows) do
		PopulateInviteRow(row, invites[#invites - rowIndex + 1], not active)
	end
end

local function EnsurePokerPopups()
	if not StaticPopupDialogs[RESIGN_POPUP_KEY] then
		StaticPopupDialogs[RESIGN_POPUP_KEY] = {
			text = "Leave this Hold 'Em match? Your opponent will win the match.",
			button1 = "Leave Match",
			button2 = CANCEL,
			timeout = 0,
			whileDead = true,
			hideOnEscape = true,
			preferredIndex = 3,
			OnAccept = function()
				local resigned, errorMessage = GuildPing:ResignPokerGame()
				if not resigned then GuildPing:Print(errorMessage) end
			end,
		}
	end
	if not StaticPopupDialogs[END_PRACTICE_POPUP_KEY] then
		StaticPopupDialogs[END_PRACTICE_POPUP_KEY] = {
			text = "Close this local Hold 'Em practice table?",
			button1 = "End Practice",
			button2 = CANCEL,
			timeout = 0,
			whileDead = true,
			hideOnEscape = true,
			preferredIndex = 3,
			OnAccept = function()
				local ended, errorMessage = GuildPing:ResignPokerGame()
				if not ended then GuildPing:Print(errorMessage) end
			end,
		}
	end
	if not StaticPopupDialogs[ABANDON_POPUP_KEY] then
		StaticPopupDialogs[ABANDON_POPUP_KEY] = {
			text = "Abandon this saved Hold 'Em match? It will be removed from this character.",
			button1 = "Abandon Game",
			button2 = CANCEL,
			timeout = 0,
			whileDead = true,
			hideOnEscape = true,
			preferredIndex = 3,
			OnAccept = function()
				local abandoned, errorMessage = GuildPing:AbandonPokerGame()
				if abandoned then GuildPing:ShowPokerLobby() else GuildPing:Print(errorMessage) end
			end,
		}
	end
end

local function FormatHistoryCard(token)
	if type(token) ~= "string" or #token < 2 then return "?" end
	local rank = token:sub(1, 1)
	return (DISPLAY_RANKS[rank] or rank) .. (HISTORY_SUITS[token:sub(2, 2)] or "?")
end

local function FormatHistoryCards(cards)
	local values = {}
	for _, card in ipairs(cards or {}) do table.insert(values, FormatHistoryCard(card)) end
	return #values > 0 and table.concat(values, "  ") or "—"
end

local function CreatePokerCard(parent)
	local card = CreateFrame("Frame", nil, parent, "BackdropTemplate")
	card:SetSize(58, 82)
	card:SetBackdrop(ROW_BACKDROP)
	card.rankText = CreateLabel(card, "", "GameFontNormalLarge")
	card.rankText:SetPoint("TOPLEFT", 7, -5)
	card.suitIcon = card:CreateTexture(nil, "ARTWORK")
	card.suitIcon:SetTexture(SUIT_TEXTURE)
	card.suitIcon:SetSize(28, 28)
	card.suitIcon:SetPoint("CENTER", 5, -7)
	card.backLogo = card:CreateTexture(nil, "ARTWORK")
	card.backLogo:SetTexture(LOGO_TEXTURE)
	card.backLogo:SetSize(34, 34)
	card.backLogo:SetPoint("CENTER")
	return card
end

local function SetPokerCard(card, token, hidden)
	if hidden then
		card:SetBackdropColor(Unpack(OXBLOOD))
		card:SetBackdropBorderColor(Unpack(OLD_BRASS))
		card.rankText:SetText("")
		card.suitIcon:Hide()
		card.backLogo:Show()
		card:Show()
	elseif token then
		local suit = token:sub(2, 2)
		local color = (suit == "d" or suit == "h") and SUIT_RED or CARD_INK
		card:SetBackdropColor(Unpack(CARD_IVORY))
		card:SetBackdropBorderColor(Unpack(OLD_BRASS))
		local rank = token:sub(1, 1)
		card.rankText:SetText(DISPLAY_RANKS[rank] or rank)
		card.rankText:SetTextColor(Unpack(color))
		local coordinates = SUIT_COORDS[suit]
		card.suitIcon:SetTexCoord(coordinates[1], coordinates[2], 0, 1)
		card.suitIcon:Show()
		card.backLogo:Hide()
		card:Show()
	else
		card:SetBackdropColor(0.035, 0.130, 0.105, 0.45)
		card:SetBackdropBorderColor(0.843, 0.659, 0.294, 0.28)
		card.rankText:SetText("")
		card.suitIcon:Hide()
		card.backLogo:Hide()
		card:Show()
	end
end

local function CreateDealerPuck(parent)
	local puck = CreateFrame("Frame", nil, parent, "BackdropTemplate")
	puck:SetSize(29, 29)
	puck:SetBackdrop(ROW_BACKDROP)
	puck:SetBackdropColor(Unpack(CARD_IVORY))
	puck:SetBackdropBorderColor(Unpack(OLD_BRASS))
	puck.text = CreateLabel(puck, "D", "GameFontNormal")
	puck.text:SetPoint("CENTER")
	puck.text:SetTextColor(Unpack(CARD_INK))
	return puck
end

local function CreateSeatPlate(parent)
	local plate = CreateFrame("Frame", nil, parent, "BackdropTemplate")
	plate:SetSize(226, 58)
	plate:SetBackdrop(ROW_BACKDROP)
	plate:SetBackdropColor(Unpack(DEEP_FELT))
	plate:SetBackdropBorderColor(0.843, 0.659, 0.294, 0.52)
	plate.nameText = CreateLabel(plate, "", "GameFontNormal")
	plate.nameText:SetPoint("TOPLEFT", 12, -9)
	plate.stackText = CreateLabel(plate, "", "GameFontHighlight")
	plate.stackText:SetPoint("BOTTOMLEFT", 12, 9)
	plate.stackText:SetTextColor(Unpack(OLD_BRASS))
	return plate
end

local function ActionDescription(game, localSeat)
	if not game.lastAction or not game.lastActor then return "Blinds are posted. The button acts first before the flop." end
	local actor = SeatName(game, game.lastActor, localSeat)
	if game.lastAction == "FOLD" then return actor .. " folded."
	elseif game.lastAction == "CHECK" then return actor .. " checked."
	elseif game.lastAction == "CALL" then return actor .. " called " .. FormatNumber(game.lastAmount) .. "."
	elseif game.lastAction == "RAISE" then
		return actor .. " made it " .. FormatNumber(game.lastAmount) .. "."
	end
	return ""
end

local function RefreshCustomRaiseControl(frame, game, controllableSeat, legal, actionable)
	local input = frame.customRaiseInput
	if not input then return end
	if frame.customRaiseSequence ~= game.sequence or frame.customRaiseSeat ~= controllableSeat then
		frame.customRaiseSequence = game.sequence
		frame.customRaiseSeat = controllableSeat
		frame.updatingCustomRaise = true
		local suggestedAmount = legal.canRaise
			and GuildPing:GetPokerPresetRaiseTo(game, controllableSeat, "MIN") or nil
		input:SetText(suggestedAmount and tostring(suggestedAmount) or "")
		frame.updatingCustomRaise = false
	end

	local amount = math.floor(tonumber(input:GetText()) or 0)
	local validAmount = legal.canRaise
		and amount > (game.currentBet or 0)
		and amount <= legal.maxRaiseTo
		and (amount >= legal.minRaiseTo or amount == legal.maxRaiseTo)
	input:SetEnabled(actionable and legal.canRaise)
	frame.customRaiseButton:SetEnabled(actionable and validAmount)

	if not legal.canRaise then
		frame.customRaiseHint:SetText("Custom raise unavailable")
	elseif legal.maxRaiseTo < legal.minRaiseTo then
		frame.customRaiseHint:SetText("Only all-in to " .. FormatNumber(legal.maxRaiseTo) .. " is available")
	elseif amount > legal.maxRaiseTo then
		frame.customRaiseHint:SetText("Maximum " .. FormatNumber(legal.maxRaiseTo))
	elseif amount > 0 and amount < legal.minRaiseTo then
		frame.customRaiseHint:SetText("Minimum " .. FormatNumber(legal.minRaiseTo))
	else
		frame.customRaiseHint:SetText(string.format(
			"Min %s  •  Max %s",
			FormatNumber(legal.minRaiseTo),
			FormatNumber(legal.maxRaiseTo)
		))
	end
end

function GuildPing:CreatePokerTable()
	if self.pokerTableFrame then return self.pokerTableFrame end
	EnsurePokerPopups()
	local frame = CreateWindow("GuildPingPokerTableFrame")
	frame:SetSize(820, 690)
	frame:SetPoint("CENTER")
	frame:SetFrameStrata("DIALOG")
	MakeDraggable(frame, frame.header)
	SetTitle(frame, "Guild Ping — Texas Hold 'Em")

	frame.handText = CreateLabel(frame, "", "GameFontNormal")
	frame.handText:SetPoint("TOPLEFT", 24, -50)
	frame.blindText = CreateLabel(frame, "Blinds 10 / 20  •  play chips", "GameFontHighlightSmall")
	frame.blindText:SetPoint("TOPRIGHT", -24, -52)
	frame.blindText:SetTextColor(MUTED_COLOR:GetRGB())

	frame.felt = CreateFrame("Frame", nil, frame, "BackdropTemplate")
	frame.felt:SetPoint("TOPLEFT", 24, -76)
	frame.felt:SetPoint("TOPRIGHT", -24, -76)
	frame.felt:SetHeight(438)
	frame.felt:SetBackdrop(ROW_BACKDROP)
	frame.felt:SetBackdropColor(Unpack(FELT_GREEN))
	frame.felt:SetBackdropBorderColor(Unpack(OLD_BRASS))
	frame.felt.innerRail = CreateFrame("Frame", nil, frame.felt, "BackdropTemplate")
	frame.felt.innerRail:SetPoint("TOPLEFT", 7, -7)
	frame.felt.innerRail:SetPoint("BOTTOMRIGHT", -7, 7)
	frame.felt.innerRail:SetBackdrop(ROW_BACKDROP)
	frame.felt.innerRail:SetBackdropColor(0, 0, 0, 0)
	frame.felt.innerRail:SetBackdropBorderColor(0.843, 0.659, 0.294, 0.27)

	frame.topSeat = CreateSeatPlate(frame.felt)
	frame.topSeat:SetPoint("TOP", 0, -17)
	frame.bottomSeat = CreateSeatPlate(frame.felt)
	frame.bottomSeat:SetPoint("BOTTOM", 0, 17)
	frame.dealerPuck = CreateDealerPuck(frame.felt)

	frame.topCards = { CreatePokerCard(frame.felt), CreatePokerCard(frame.felt) }
	frame.topCards[1]:SetPoint("TOPLEFT", 46, -18)
	frame.topCards[2]:SetPoint("LEFT", frame.topCards[1], "RIGHT", 7, 0)
	frame.bottomCards = { CreatePokerCard(frame.felt), CreatePokerCard(frame.felt) }
	frame.bottomCards[1]:SetPoint("BOTTOMRIGHT", -111, 18)
	frame.bottomCards[2]:SetPoint("LEFT", frame.bottomCards[1], "RIGHT", 7, 0)

	frame.potText = CreateLabel(frame.felt, "Pot 0", "GameFontNormalLarge")
	frame.potText:SetPoint("TOP", 0, -112)
	frame.potText:SetTextColor(Unpack(OLD_BRASS))
	frame.streetText = CreateLabel(frame.felt, "", "GameFontNormalSmall")
	frame.streetText:SetPoint("TOP", frame.potText, "BOTTOM", 0, -4)
	frame.streetText:SetTextColor(0.82, 0.90, 0.84, 1)

	frame.boardCards = {}
	for index = 1, 5 do
		local card = CreatePokerCard(frame.felt)
		card:SetPoint("CENTER", frame.felt, "CENTER", (index - 3) * 67, 0)
		frame.boardCards[index] = card
	end
	frame.lastActionText = CreateLabel(frame.felt, "", "GameFontHighlightSmall")
	frame.lastActionText:SetPoint("BOTTOM", 0, 106)
	frame.lastActionText:SetTextColor(0.84, 0.89, 0.85, 1)

	frame.statusText = CreateLabel(frame, "", "GameFontNormal")
	frame.statusText:SetPoint("TOPLEFT", 26, -529)
	frame.statusText:SetPoint("TOPRIGHT", -26, -529)
	frame.statusText:SetJustifyH("CENTER")
	frame.statusText:SetWordWrap(false)

	frame.foldButton = CreateButton(frame, "Fold", 76, 27, "danger")
	frame.foldButton:SetPoint("TOPLEFT", 35, -559)
	frame.foldButton:SetScript("OnClick", function()
		local success, errorMessage = GuildPing:PlayPokerAction("FOLD")
		if not success then GuildPing:Print(errorMessage) end
	end)
	frame.callButton = CreateButton(frame, "Check", 108, 27, "primary")
	frame.callButton:SetPoint("LEFT", frame.foldButton, "RIGHT", 7, 0)
	frame.callButton:SetScript("OnClick", function()
		local game = GuildPing.activePokerGame
		local legal = GuildPing:GetPokerLegalActions(game)
		local success, errorMessage = GuildPing:PlayPokerAction(legal.canCheck and "CHECK" or "CALL")
		if not success then GuildPing:Print(errorMessage) end
	end)

	frame.raiseButtons = {}
	local raiseDefinitions = {
		{ key = "MIN", label = "Min" },
		{ key = "HALF", label = "½ Pot" },
		{ key = "POT", label = "Pot" },
		{ key = "ALLIN", label = "All In" },
	}
	local previous = frame.callButton
	for _, definition in ipairs(raiseDefinitions) do
		local button = CreateButton(frame, definition.label, definition.key == "ALLIN" and 91 or 112, 27)
		button:SetPoint("LEFT", previous, "RIGHT", 7, 0)
		button.preset = definition.key
		button.baseLabel = definition.label
		button:SetScript("OnClick", function(self)
			local game = GuildPing.activePokerGame
			local seat = GuildPing:GetPokerControllableSeat(game)
			local amount = GuildPing:GetPokerPresetRaiseTo(game, seat, self.preset)
			local success, errorMessage = GuildPing:PlayPokerAction("RAISE", amount)
			if not success then GuildPing:Print(errorMessage) end
		end)
		table.insert(frame.raiseButtons, button)
		previous = button
	end

	frame.customRaiseLabel = CreateLabel(frame, "Raise to", "GameFontNormalSmall")
	frame.customRaiseLabel:SetPoint("TOPLEFT", 210, -603)
	frame.customRaiseLabel:SetTextColor(Unpack(OLD_BRASS))
	frame.customRaiseInput = CreateInput(frame, 104, "Total", 7)
	frame.customRaiseInput:SetPoint("TOPLEFT", 274, -596)
	frame.customRaiseInput:SetNumeric(true)
	frame.customRaiseButton = CreateButton(frame, "Bet / Raise", 112, 27, "primary")
	frame.customRaiseButton:SetPoint("LEFT", frame.customRaiseInput, "RIGHT", 7, 0)
	frame.customRaiseHint = CreateLabel(frame, "", "GameFontDisableSmall")
	frame.customRaiseHint:SetPoint("LEFT", frame.customRaiseButton, "RIGHT", 10, 0)
	frame.customRaiseHint:SetPoint("RIGHT", -28, 0)
	frame.customRaiseHint:SetWordWrap(false)

	local function SubmitCustomRaise()
		frame.customRaiseInput:ClearFocus()
		local amount = math.floor(tonumber(frame.customRaiseInput:GetText()) or 0)
		local success, errorMessage = GuildPing:PlayPokerAction("RAISE", amount)
		if not success then GuildPing:Print(errorMessage) end
	end
	frame.customRaiseButton:SetScript("OnClick", SubmitCustomRaise)
	frame.customRaiseInput:SetScript("OnEnterPressed", SubmitCustomRaise)
	frame.customRaiseInput:HookScript("OnTextChanged", function()
		if frame.updatingCustomRaise then return end
		local game = GuildPing.activePokerGame
		if not game then return end
		local seat = GuildPing:GetPokerControllableSeat(game)
		local legal = GuildPing:GetPokerLegalActions(game, seat)
		local actionable = game.status == "ACTIVE" and game.handStatus == "ACTIVE"
			and game.turn == seat and not game.pendingAction
		RefreshCustomRaiseControl(frame, game, seat, legal, actionable)
	end)

	frame.nextHandButton = CreateButton(frame, "Deal Next Hand", 148, 27, "primary")
	frame.nextHandButton:SetPoint("TOP", 0, -559)
	frame.nextHandButton:SetScript("OnClick", function()
		local started, errorMessage = GuildPing:StartNextPokerHand()
		if not started then GuildPing:Print(errorMessage) end
	end)
	frame.nextHandButton:Hide()
	frame.playAgainButton = CreateButton(frame, "Play Again", 148, 27, "primary")
	frame.playAgainButton:SetPoint("TOP", 0, -559)
	frame.playAgainButton:SetScript("OnClick", function()
		local challenge, errorMessage = GuildPing:PlayPokerAgain()
		if not challenge then
			GuildPing:Print(errorMessage)
		elseif not challenge.practice then
			GuildPing:Print("Rematch challenge sent to " .. challenge.target .. ".")
		end
	end)
	frame.playAgainButton:Hide()
	frame.lifecycleResumeButton = CreateButton(frame, "Resume", 128, 27, "primary")
	frame.lifecycleResumeButton:SetPoint("TOP", -70, -559)
	frame.lifecycleResumeButton:SetScript("OnClick", function()
		local resumed, errorMessage = GuildPing:ResumePokerGame()
		if not resumed then GuildPing:Print(errorMessage) end
	end)
	frame.lifecycleResumeButton:Hide()
	frame.abandonButton = CreateButton(frame, "Abandon Game", 136, 27, "danger")
	frame.abandonButton:SetPoint("TOP", 70, -559)
	frame.abandonButton:SetScript("OnClick", function()
		StaticPopup_Hide(ABANDON_POPUP_KEY)
		StaticPopup_Show(ABANDON_POPUP_KEY)
	end)
	frame.abandonButton:Hide()

	frame.allGamesButton = CreateButton(frame, "All Games", 104, 25)
	frame.allGamesButton:SetPoint("BOTTOMLEFT", 24, 14)
	frame.allGamesButton:SetScript("OnClick", function()
		if GuildPing.ShowGames then GuildPing:ShowGames() end
	end)
	frame.lobbyButton = CreateButton(frame, "Poker Lobby", 112, 25)
	frame.lobbyButton:SetPoint("LEFT", frame.allGamesButton, "RIGHT", 7, 0)
	frame.lobbyButton:SetScript("OnClick", function() GuildPing:ShowPokerLobby() end)
	frame.historyButton = CreateButton(frame, "Hand History", 126, 25)
	frame.historyButton:SetPoint("BOTTOM", 0, 14)
	frame.historyButton:SetScript("OnClick", function() GuildPing:ShowPokerHandHistory() end)
	frame.resignButton = CreateButton(frame, "Leave Match", 116, 25, "danger")
	frame.resignButton:SetPoint("BOTTOMRIGHT", -24, 14)
	frame.resignButton:SetScript("OnClick", function()
		local game = GuildPing.activePokerGame
		StaticPopup_Show(game and game.practice and END_PRACTICE_POPUP_KEY or RESIGN_POPUP_KEY)
	end)
	frame:SetScript("OnShow", function() GuildPing:RefreshPokerTable() end)
	frame:SetScript("OnUpdate", function(_, elapsed) GuildPing:UpdatePokerDealAnimation(elapsed) end)
	frame:Hide()
	self.pokerTableFrame = frame
	return frame
end

function GuildPing:BeginPokerDealAnimation(firstCard, lastCard)
	local frame = self.pokerTableFrame
	if not frame or not frame:IsShown() then return end
	frame.dealAnimation = {
		firstCard = math.max(1, tonumber(firstCard) or 1),
		lastCard = math.min(5, tonumber(lastCard) or 5),
		elapsed = 0,
	}
	for index = frame.dealAnimation.firstCard, frame.dealAnimation.lastCard do
		frame.boardCards[index]:SetWidth(2)
	end
end

function GuildPing:UpdatePokerDealAnimation(elapsed)
	local frame = self.pokerTableFrame
	local animation = frame and frame.dealAnimation
	if not animation then return end
	animation.elapsed = animation.elapsed + (tonumber(elapsed) or 0)
	local complete = true
	for index = animation.firstCard, animation.lastCard do
		local progress = (animation.elapsed - ((index - animation.firstCard) * 0.12)) / 0.28
		if progress <= 0 then
			frame.boardCards[index]:SetWidth(2)
			complete = false
		elseif progress < 1 then
			local width = progress < 0.5 and 58 * (1 - (progress * 2)) or 58 * ((progress - 0.5) * 2)
			frame.boardCards[index]:SetWidth(math.max(2, width))
			complete = false
		else
			frame.boardCards[index]:SetWidth(58)
		end
	end
	if complete then frame.dealAnimation = nil end
end

local function SetAllActionButtonsShown(frame, shown)
	frame.foldButton:SetShown(shown)
	frame.callButton:SetShown(shown)
	for _, button in ipairs(frame.raiseButtons) do button:SetShown(shown) end
	frame.customRaiseLabel:SetShown(shown)
	frame.customRaiseInput:SetShown(shown)
	frame.customRaiseButton:SetShown(shown)
	frame.customRaiseHint:SetShown(shown)
end

local function RefreshActionButtons(frame, game, controllableSeat)
	local legal = GuildPing:GetPokerLegalActions(game, controllableSeat)
	local waitingForResume = GuildPing:IsGameWaitingForResume(game)
	local actionable = game.status == "ACTIVE" and game.handStatus == "ACTIVE"
		and game.turn == controllableSeat and not game.pendingAction and not waitingForResume
	SetAllActionButtonsShown(frame, game.handStatus == "ACTIVE" and not waitingForResume)
	frame.nextHandButton:SetShown(game.handStatus == "COMPLETE" and game.status == "ACTIVE" and not waitingForResume)
	frame.nextHandButton:SetEnabled(not game.pendingAction)
	frame.nextHandButton:SetText((not game.practice and not GuildPing:IsPokerAuthority(game))
		and "Request Next Hand" or "Deal Next Hand")
	local waitingForRematch = GuildPing.outgoingPokerChallenge ~= nil
	frame.playAgainButton:SetShown(game.status == "FINISHED" and not waitingForResume)
	frame.playAgainButton:SetText(waitingForRematch and "Challenge Sent" or "Play Again")
	frame.playAgainButton:SetEnabled(not waitingForRematch)
	frame.lifecycleResumeButton:SetShown(waitingForResume)
	frame.lifecycleResumeButton:SetEnabled(game.persistenceState ~= "RECONNECTING")
	frame.abandonButton:SetShown(waitingForResume)
	frame.abandonButton:SetEnabled(true)
	frame.foldButton:SetEnabled(actionable and legal.canFold)
	frame.callButton:SetEnabled(actionable and (legal.canCheck or legal.canCall))
	frame.callButton:SetText(legal.canCheck and "Check"
		or "Call " .. FormatNumber(legal.callAmount) .. (legal.callAmount < legal.toCall and " All In" or ""))
	for _, button in ipairs(frame.raiseButtons) do
		local amount = GuildPing:GetPokerPresetRaiseTo(game, controllableSeat, button.preset)
		button:SetEnabled(actionable and legal.canRaise and amount ~= nil)
		if amount then
			button:SetText(button.baseLabel .. " " .. FormatNumber(amount))
		else
			button:SetText(button.baseLabel)
		end
	end
	RefreshCustomRaiseControl(frame, game, controllableSeat, legal, actionable)
end

local function ResultStatus(game, localSeat)
	if game.finishReason == "PRACTICE" then return "Practice table closed.", MUTED_COLOR end
	if game.finishReason == "RESIGN" then
		if game.matchWinnerSeat == localSeat then return "You won the match — your opponent left.", GUILD_GREEN end
		return "You left the match.", UPDATE_RED
	end
	if game.tie then return "Split pot — both players have the same hand.", WARNING_GOLD end
	local winner = SeatName(game, game.winnerSeat, localSeat)
	if game.finishReason == "FOLD" then
		return winner .. " won " .. FormatNumber(game.resultPot) .. " chips when the other player folded.",
			(game.winnerSeat == localSeat or game.practice) and GUILD_GREEN or WARNING_GOLD
	end
	local handName = game.showdownNames and game.showdownNames[game.winnerSeat]
	local message = winner .. " won " .. FormatNumber(game.resultPot) .. " chips"
		.. (handName and " with " .. handName:lower() or " at showdown") .. "."
	return message, (game.winnerSeat == localSeat or game.practice) and GUILD_GREEN or UPDATE_RED
end

function GuildPing:RefreshPokerTable()
	local frame = self.pokerTableFrame
	local game = self.activePokerGame
	if not frame or not game then return end
	local localSeat = self:GetLocalPokerSeat(game)
	local bottomSeat = game.practice and GuildPing.POKER_CHALLENGER or localSeat
	bottomSeat = bottomSeat or GuildPing.POKER_CHALLENGER
	local topSeat = bottomSeat == GuildPing.POKER_CHALLENGER and GuildPing.POKER_OPPONENT
		or GuildPing.POKER_CHALLENGER
	local controllableSeat = self:GetPokerControllableSeat(game)
	local currentGuildGame = self:IsPokerGameForCurrentGuild(game)
	local waitingForResume = self:IsGameWaitingForResume(game)

	frame.handText:SetText(string.format("Hand %d  •  %s", game.handNumber or 0,
		game.round and game.round:gsub("PREFLOP", "Pre-flop"):gsub("FLOP", "Flop"):gsub("TURN", "Turn"):gsub("RIVER", "River"):gsub("SHOWDOWN", "Showdown") or "Waiting"))
	frame.topSeat.nameText:SetText(SeatName(game, topSeat, localSeat))
	frame.topSeat.stackText:SetText(FormatNumber(game.stacks[topSeat]) .. " chips")
	frame.bottomSeat.nameText:SetText(SeatName(game, bottomSeat, localSeat))
	frame.bottomSeat.stackText:SetText(FormatNumber(game.stacks[bottomSeat]) .. " chips")

	frame.dealerPuck:ClearAllPoints()
	if game.button == topSeat then
		frame.dealerPuck:SetPoint("LEFT", frame.topSeat, "RIGHT", 10, 0)
	else
		frame.dealerPuck:SetPoint("RIGHT", frame.bottomSeat, "LEFT", -10, 0)
	end
	frame.dealerPuck:SetShown(game.handNumber > 0)

	local hasHand = game.handNumber > 0 and game.handStatus ~= "WAITING"
	local revealShowdown = game.handStatus == "COMPLETE" and game.finishReason == "SHOWDOWN"
	local revealBottom = not game.practice or revealShowdown
		or (game.handStatus == "ACTIVE" and game.turn == bottomSeat)
	local revealTop = revealShowdown
		or (game.practice and game.handStatus == "ACTIVE" and game.turn == topSeat)
	local topCards = game.holeCards[topSeat] or {}
	local bottomCards = game.holeCards[bottomSeat] or {}
	for index = 1, 2 do
		SetPokerCard(frame.bottomCards[index], revealBottom and bottomCards[index] or nil, hasHand and not revealBottom)
		SetPokerCard(frame.topCards[index], revealTop and topCards[index] or nil, hasHand and not revealTop)
	end
	for index, cardFrame in ipairs(frame.boardCards) do
		SetPokerCard(cardFrame, game.board[index], false)
	end

	frame.potText:SetText(game.handStatus == "COMPLETE" and game.resultPot
		and "Pot won  " .. FormatNumber(game.resultPot)
		or "Pot  " .. FormatNumber(self:GetPokerTablePot(game)))
	frame.streetText:SetText(game.handStatus == "ACTIVE"
		and string.format("Current bet %s  •  minimum raise %s", FormatNumber(game.currentBet), FormatNumber(game.minRaise))
		or "Blinds 10 / 20")
	frame.lastActionText:SetText(ActionDescription(game, localSeat))

	local statusText, statusColor
	if not currentGuildGame then
		statusText, statusColor = "This table belongs to a different guild profile.", UPDATE_RED
	elseif game.persistenceState == "RECONNECTING" then
		statusText, statusColor = "Reconnecting to " .. (self:GetPokerOpponent(game) or "your opponent") .. "…", WARNING_GOLD
	elseif game.persistenceState == "STALE" then
		statusText, statusColor = "No response from your opponent. Resume to try again or abandon this match.", UPDATE_RED
	elseif game.persistenceState == "RESTORED" then
		statusText, statusColor = "Practice table restored. Resume when you're ready.", WARNING_GOLD
	elseif game.handStatus == "WAITING" then
		statusText, statusColor = "Waiting for the challenger to shuffle and deal.", WARNING_GOLD
	elseif game.handStatus == "COMPLETE" then
		statusText, statusColor = ResultStatus(game, localSeat)
	elseif game.pendingAction then
		statusText, statusColor = "Waiting for the table host to confirm the action…", WARNING_GOLD
	elseif game.turn == controllableSeat then
		local legal = self:GetPokerLegalActions(game, controllableSeat)
		local actor = game.practice and SeatName(game, controllableSeat, localSeat) or "Your"
		statusText = legal.toCall > 0
			and actor .. " action — " .. FormatNumber(legal.toCall) .. " to call."
			or actor .. " action — check or bet."
		statusColor = GUILD_GREEN
	else
		statusText = "Waiting for " .. SeatName(game, game.turn, localSeat) .. " to act."
		statusColor = WARNING_GOLD
	end
	frame.statusText:SetText(statusText)
	frame.statusText:SetTextColor(statusColor:GetRGB())
	RefreshActionButtons(frame, game, controllableSeat)
	if not currentGuildGame then
		frame.nextHandButton:SetEnabled(false)
		frame.playAgainButton:SetEnabled(false)
		frame.foldButton:SetEnabled(false)
		frame.callButton:SetEnabled(false)
		for _, button in ipairs(frame.raiseButtons) do button:SetEnabled(false) end
		frame.customRaiseInput:SetEnabled(false)
		frame.customRaiseButton:SetEnabled(false)
	end
	frame.resignButton:SetText(game.practice and "End Practice" or "Leave Match")
	frame.resignButton:SetShown(not waitingForResume)
	frame.resignButton:SetEnabled(currentGuildGame and game.status == "ACTIVE" and not game.pendingAction and not waitingForResume)
	frame.lifecycleResumeButton:SetEnabled(currentGuildGame and game.persistenceState ~= "RECONNECTING")
	frame.abandonButton:SetEnabled(currentGuildGame)
end

local function HistorySeatName(hand, seat)
	if hand.practice then
		return seat == GuildPing.POKER_CHALLENGER and "Player 1" or "Player 2"
	end
	if seat == hand.localSeat then return "You" end
	return seat == GuildPing.POKER_CHALLENGER
		and (hand.challenger or "Challenger") or (hand.opponent or "Opponent")
end

local function FormatPokerHandEntry(hand)
	local lines = {}
	local result
	if hand.tie then
		result = "Split pot"
	elseif hand.winnerSeat then
		result = HistorySeatName(hand, hand.winnerSeat) .. " won"
	else
		result = "Completed"
	end
	table.insert(lines, string.format("HAND %d  •  %s  •  pot %s", hand.handNumber or 0, result, FormatNumber(hand.resultPot or 0)))
	table.insert(lines, "Board  " .. FormatHistoryCards(hand.board))
	local challengerCards = hand.holeCards and hand.holeCards[GuildPing.POKER_CHALLENGER]
	local opponentCards = hand.holeCards and hand.holeCards[GuildPing.POKER_OPPONENT]
	if challengerCards then
		table.insert(lines, HistorySeatName(hand, GuildPing.POKER_CHALLENGER) .. "  " .. FormatHistoryCards(challengerCards))
	end
	if opponentCards then
		table.insert(lines, HistorySeatName(hand, GuildPing.POKER_OPPONENT) .. "  " .. FormatHistoryCards(opponentCards))
	end
	local winnerName = hand.showdownNames and hand.winnerSeat and hand.showdownNames[hand.winnerSeat]
	if winnerName then table.insert(lines, "Winning hand  " .. winnerName) end

	local currentRound
	local roundLabels = { PREFLOP = "Pre-flop", FLOP = "Flop", TURN = "Turn", RIVER = "River", SHOWDOWN = "Showdown" }
	for _, event in ipairs(hand.events or {}) do
		if event.round ~= currentRound then
			currentRound = event.round
			table.insert(lines, "")
			table.insert(lines, string.upper(roundLabels[currentRound] or tostring(currentRound or "Action")))
		end
		if event.action == "DEAL" then
			table.insert(lines, "Board dealt  " .. FormatHistoryCards(event.board))
		else
			local actor = HistorySeatName(hand, event.seat)
			local actionText = event.action == "SMALL_BLIND" and "posted the small blind"
				or event.action == "BIG_BLIND" and "posted the big blind"
				or event.action == "CHECK" and "checked"
				or event.action == "CALL" and "called"
				or event.action == "RAISE" and "bet or raised to"
				or event.action == "FOLD" and "folded"
				or tostring(event.action or "acted"):lower()
			local amount = tonumber(event.amount) or 0
			table.insert(lines, actor .. " " .. actionText .. (amount > 0 and " " .. FormatNumber(amount) or ""))
		end
	end
	table.insert(lines, "")
	return table.concat(lines, "\n")
end

function GuildPing:CreatePokerHandHistoryWindow()
	if self.pokerHandHistoryWindow then return self.pokerHandHistoryWindow end
	local frame = CreateWindow("GuildPingPokerHandHistoryFrame")
	frame:SetSize(690, 620)
	frame:SetPoint("CENTER")
	frame:SetFrameStrata("DIALOG")
	MakeDraggable(frame, frame.header)
	SetTitle(frame, "Guild Ping — Poker Hand History")
	frame.heading = CreateLabel(frame, "Recent hands", "GameFontNormalLarge")
	frame.heading:SetPoint("TOPLEFT", 24, -49)
	frame.description = CreateLabel(frame, "Only cards revealed to you during play are retained in this local history.", "GameFontHighlightSmall")
	frame.description:SetPoint("TOPLEFT", 24, -77)
	frame.description:SetTextColor(MUTED_COLOR:GetRGB())

	frame.scrollFrame = CreateFrame("ScrollFrame", nil, frame, "UIPanelScrollFrameTemplate")
	frame.scrollFrame:SetPoint("TOPLEFT", 24, -104)
	frame.scrollFrame:SetPoint("BOTTOMRIGHT", -46, 56)
	frame.scrollChild = CreateFrame("Frame", nil, frame.scrollFrame)
	frame.scrollChild:SetSize(606, 1)
	frame.scrollFrame:SetScrollChild(frame.scrollChild)
	frame.historyText = CreateLabel(frame.scrollChild, "", "GameFontHighlight")
	frame.historyText:SetPoint("TOPLEFT", 4, -4)
	frame.historyText:SetPoint("RIGHT", -4, 0)
	frame.historyText:SetJustifyH("LEFT")
	frame.historyText:SetJustifyV("TOP")
	frame.historyText:SetWordWrap(true)
	frame.emptyText = CreateLabel(frame, "Finish a Hold 'Em hand to add it to the history.", "GameFontDisable")
	frame.emptyText:SetPoint("TOPLEFT", 30, -115)

	frame.closeButton = CreateButton(frame, "Close", 90, 25)
	frame.closeButton:SetPoint("BOTTOMRIGHT", -24, 16)
	frame.closeButton:SetScript("OnClick", function() frame:Hide() end)
	frame:SetScript("OnShow", function() GuildPing:RefreshPokerHandHistoryWindow() end)
	frame:Hide()
	self.pokerHandHistoryWindow = frame
	return frame
end

function GuildPing:RefreshPokerHandHistoryWindow()
	local frame = self.pokerHandHistoryWindow
	if not frame then return end
	local history = self:GetPokerHandHistory()
	local entries = {}
	for index, hand in ipairs(history) do
		if index > 20 then break end
		table.insert(entries, FormatPokerHandEntry(hand))
	end
	local content = table.concat(entries, "\n----------------------------------------\n\n")
	frame.historyText:SetText(content)
	local height = math.max(1, (frame.historyText.GetStringHeight and frame.historyText:GetStringHeight() or 0) + 16)
	frame.scrollChild:SetHeight(height)
	frame.emptyText:SetShown(#history == 0)
end

function GuildPing:ShowPokerHandHistory()
	local frame = self:CreatePokerHandHistoryWindow()
	frame:Show()
	self:RefreshPokerHandHistoryWindow()
end

function GuildPing:ShowPokerLobby()
	if self.gamesHub then self.gamesHub:Hide() end
	if self.checkersLobby then self.checkersLobby:Hide() end
	if self.checkersBoard then self.checkersBoard:Hide() end
	if self.deathRollLobby then self.deathRollLobby:Hide() end
	if self.deathRollGameFrame then self.deathRollGameFrame:Hide() end
	if self.connectFourLobby then self.connectFourLobby:Hide() end
	if self.connectFourBoard then self.connectFourBoard:Hide() end
	if self.pokerTableFrame then self.pokerTableFrame:Hide() end
	local lobby = self:CreatePokerLobby()
	lobby:Show()
	self:RefreshPokerLobby()
end

function GuildPing:ShowPokerTable()
	if not self.activePokerGame then self:ShowPokerLobby() return end
	self:AcknowledgeGameTurn("poker")
	if self.gamesHub then self.gamesHub:Hide() end
	if self.checkersLobby then self.checkersLobby:Hide() end
	if self.checkersBoard then self.checkersBoard:Hide() end
	if self.deathRollLobby then self.deathRollLobby:Hide() end
	if self.deathRollGameFrame then self.deathRollGameFrame:Hide() end
	if self.connectFourLobby then self.connectFourLobby:Hide() end
	if self.connectFourBoard then self.connectFourBoard:Hide() end
	if self.pokerLobby then self.pokerLobby:Hide() end
	local frame = self:CreatePokerTable()
	frame:Show()
	self:RefreshPokerTable()
	if self.RequestPokerSync then self:RequestPokerSync() end
end

function GuildPing:ShowPoker()
	if self.fiveDiceFrame then self.fiveDiceFrame:Hide() end
	if self.runeMastermindFrame then self.runeMastermindFrame:Hide() end
	if self.activePokerGame then self:ShowPokerTable() else self:ShowPokerLobby() end
end
