local _, 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 DISCORD_BLUE = UI.DiscordBlue
local GUILD_GREEN = UI.GuildGreen
local MUTED_COLOR = UI.MutedColor
local TEXT_COLOR = UI.TextColor
local WARNING_GOLD = UI.WarningGold
local UPDATE_RED = UI.UpdateRed
local RESIGN_POPUP_KEY = "GUILD_PING_CHECKERS_RESIGN"
local END_PRACTICE_POPUP_KEY = "GUILD_PING_CHECKERS_END_PRACTICE"
local ABANDON_POPUP_KEY = "GUILD_PING_CHECKERS_ABANDON"
local Unpack = unpack or table.unpack

local LIGHT_SQUARE = { 0.69, 0.59, 0.45, 1 }
local DARK_SQUARE = { 0.23, 0.16, 0.12, 1 }
local BLACK_PIECE = { 0.08, 0.10, 0.15, 1 }
local RED_PIECE = { 0.78, 0.12, 0.15, 1 }

local function PositionDragPiece(dragPiece)
	if not GetCursorPosition or not UIParent then
		return
	end
	local cursorX, cursorY = GetCursorPosition()
	local scale = UIParent:GetEffectiveScale()
	dragPiece:ClearAllPoints()
	dragPiece:SetPoint("CENTER", UIParent, "BOTTOMLEFT", cursorX / scale, cursorY / scale)
end

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

local function CreateInviteRow(parent, width)
	local row = CreateFrame("Frame", nil, parent, "BackdropTemplate")
	row:SetSize(width, 50)
	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, 0)
	row.senderText:SetPoint("RIGHT", -184, 0)
	row.senderText:SetWordWrap(false)

	row.declineButton = CreateButton(row, "Decline", 78, 24)
	row.declineButton:SetPoint("RIGHT", -8, 0)
	row.declineButton:SetScript("OnClick", function()
		if row.inviteID then
			GuildPing:DeclineCheckersChallenge(row.inviteID)
		end
	end)

	row.acceptButton = CreateButton(row, "Accept", 82, 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:AcceptCheckersChallenge(row.inviteID)
		if game then
			GuildPing:ShowCheckersBoard()
		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(string.format("%s challenged you  •  %s", invite.sender, FormatInviteAge(invite.receivedAt)))
	row.acceptButton:SetEnabled(canAccept)
	row:Show()
end

function GuildPing:CreateCheckersLobby()
	if self.checkersLobby then
		return self.checkersLobby
	end

	local lobby = CreateWindow("GuildPingCheckersLobbyFrame")
	lobby:SetSize(620, 570)
	lobby:SetPoint("CENTER")
	lobby:SetFrameStrata("DIALOG")
	MakeDraggable(lobby, lobby.header)
	SetTitle(lobby, "Guild Ping — Checkers")

	lobby.heading = CreateLabel(lobby, "Checkers", "GameFontNormalLarge")
	lobby.heading:SetPoint("TOPLEFT", 24, -48)
	lobby.description = CreateLabel(
		lobby,
		"Challenge an online guild member from anywhere in the world, or enter your own character name to start a local practice game and control both sides.",
		"GameFontHighlight"
	)
	lobby.description:SetPoint("TOPLEFT", 24, -78)
	lobby.description:SetPoint("TOPRIGHT", -24, -78)
	lobby.description:SetWordWrap(true)

	lobby.challengeLabel = CreateLabel(lobby, "Guild member", "GameFontNormal")
	lobby.challengeLabel:SetPoint("TOPLEFT", 24, -128)
	lobby.challengeInput = CreateInput(lobby, 390, "Character or Character-Realm", 48)
	lobby.challengeInput:SetPoint("TOPLEFT", 26, -150)
	lobby.challengeButton = CreateButton(lobby, "Send Challenge", 150, 27, "primary")
	lobby.challengeButton:SetPoint("LEFT", lobby.challengeInput, "RIGHT", 12, 0)
	lobby.challengeButton:SetScript("OnClick", function()
		local challenge, errorMessage = GuildPing:ChallengeCheckersPlayer(lobby.challengeInput:GetText())
		if challenge then
			lobby.challengeInput:ClearFocus()
			if challenge.practice then
				lobby.statusText:SetText("Practice game started.")
				GuildPing:ShowCheckersBoard()
			else
				lobby.statusText:SetText("Challenge sent to " .. challenge.target .. ".")
			end
			lobby.statusText:SetTextColor(GUILD_GREEN:GetRGB())
		else
			lobby.statusText:SetText(errorMessage)
			lobby.statusText:SetTextColor(UPDATE_RED:GetRGB())
		end
		GuildPing:RefreshCheckersLobby()
	end)
	lobby.challengeInput:SetScript("OnEnterPressed", function(self)
		self:ClearFocus()
		lobby.challengeButton:Click()
	end)

	lobby.statusText = CreateLabel(lobby, "", "GameFontHighlightSmall")
	lobby.statusText:SetPoint("TOPLEFT", 26, -188)
	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, -218)
	activeDivider:SetPoint("TOPRIGHT", -24, -218)
	activeDivider:SetHeight(1)

	lobby.activeHeader = CreateLabel(lobby, "Current game", "GameFontNormalLarge")
	lobby.activeHeader:SetPoint("TOPLEFT", 24, -236)
	lobby.activeText = CreateLabel(lobby, "", "GameFontHighlight")
	lobby.activeText:SetPoint("TOPLEFT", 26, -267)
	lobby.activeText:SetPoint("RIGHT", -180, 0)
	lobby.activeText:SetWordWrap(false)
	lobby.resumeButton = CreateButton(lobby, "Open Board", 128, 25, "primary")
	lobby.resumeButton:SetPoint("TOPRIGHT", -24, -257)
	lobby.resumeButton:SetScript("OnClick", function()
		GuildPing:ShowCheckersBoard()
	end)
	lobby.resumeButton:Hide()

	lobby.inviteHeader = CreateLabel(lobby, "Incoming challenges", "GameFontNormalLarge")
	lobby.inviteHeader:SetPoint("TOPLEFT", 24, -310)
	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, 572)
		row:SetPoint("TOPLEFT", 24, -340 - ((index - 1) * 55))
		lobby.inviteRows[index] = row
	end
	lobby.emptyInvites = CreateLabel(lobby, "No incoming challenges.", "GameFontDisable")
	lobby.emptyInvites:SetPoint("TOPLEFT", 28, -349)

	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:RefreshCheckersLobby()
	end)
	lobby:Hide()
	self.checkersLobby = lobby
	return lobby
end

function GuildPing:RefreshCheckersLobby()
	local lobby = self.checkersLobby
	if not lobby then
		return
	end
	self:CleanupCheckersInvites()
	local game = self.activeCheckersGame
	local active = game and game.status == "ACTIVE"
	lobby.challengeButton:SetEnabled(self.checkersInitialized and not active)
	local currentStatus = lobby.statusText:GetText() or ""
	if self.outgoingCheckersChallenge then
		lobby.statusText:SetText("Waiting for " .. self.outgoingCheckersChallenge.target .. " to respond.")
		lobby.statusText:SetTextColor(WARNING_GOLD:GetRGB())
	elseif currentStatus == "" or currentStatus:find("^Waiting for ") then
		lobby.statusText:SetText("Challenges expire after five minutes.")
		lobby.statusText:SetTextColor(MUTED_COLOR:GetRGB())
	end

	if game then
		local stateText = game.status == "FINISHED" and "Finished" or "In progress"
		if game.practice then
			lobby.activeText:SetText(string.format("Practice game  •  Both sides  •  %s", stateText))
		else
			local opponent = self:GetCheckersOpponent(game) or "Unknown"
			lobby.activeText:SetText(string.format("%s vs %s  •  %s", self:GetLocalCheckersPlayerName(), opponent, stateText))
		end
		lobby.resumeButton:Show()
		lobby.resumeButton:SetEnabled(true)
	else
		lobby.activeText:SetText("No checkers game in this session.")
		lobby.resumeButton:Hide()
		lobby.resumeButton:SetEnabled(false)
	end

	local invites = self:GetCheckersInvites()
	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 EnsureResignPopup()
	if not StaticPopupDialogs[RESIGN_POPUP_KEY] then
		StaticPopupDialogs[RESIGN_POPUP_KEY] = {
			text = "Resign this Guild Ping Checkers game? Your opponent will win.",
			button1 = "Resign",
			button2 = CANCEL,
			timeout = 0,
			whileDead = true,
			hideOnEscape = true,
			preferredIndex = 3,
			OnAccept = function()
				local resigned, errorMessage = GuildPing:ResignCheckersGame()
				if not resigned then
					GuildPing:Print(errorMessage)
				end
			end,
		}
	end
	if not StaticPopupDialogs[END_PRACTICE_POPUP_KEY] then
		StaticPopupDialogs[END_PRACTICE_POPUP_KEY] = {
			text = "End this local checkers practice game?",
			button1 = "End Practice",
			button2 = CANCEL,
			timeout = 0,
			whileDead = true,
			hideOnEscape = true,
			preferredIndex = 3,
			OnAccept = function()
				local ended, errorMessage = GuildPing:ResignCheckersGame()
				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 checkers game? 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:AbandonCheckersGame()
				if abandoned then GuildPing:ShowCheckersLobby() else GuildPing:Print(errorMessage) end
			end,
		}
	end
end

function GuildPing:CreateCheckersBoard()
	if self.checkersBoard then
		return self.checkersBoard
	end

	local frame = CreateWindow("GuildPingCheckersBoardFrame")
	frame:SetSize(570, 650)
	frame:SetPoint("CENTER")
	frame:SetFrameStrata("DIALOG")
	MakeDraggable(frame, frame.header)
	SetTitle(frame, "Guild Ping — Checkers")

	frame.opponentText = CreateLabel(frame, "", "GameFontHighlight")
	frame.opponentText:SetPoint("TOPLEFT", 24, -47)
	frame.opponentText:SetPoint("TOPRIGHT", -24, -47)
	frame.opponentText:SetJustifyH("CENTER")
	frame.statusText = CreateLabel(frame, "", "GameFontNormal")
	frame.statusText:SetPoint("TOPLEFT", 24, -72)
	frame.statusText:SetPoint("TOPRIGHT", -24, -72)
	frame.statusText:SetJustifyH("CENTER")

	frame.boardBackdrop = CreateFrame("Frame", nil, frame, "BackdropTemplate")
	frame.boardBackdrop:SetSize(424, 424)
	frame.boardBackdrop:SetPoint("TOP", 0, -104)
	frame.boardBackdrop:SetBackdrop(ROW_BACKDROP)
	frame.boardBackdrop:SetBackdropColor(0.04, 0.04, 0.05, 1)
	frame.boardBackdrop:SetBackdropBorderColor(0.75, 0.64, 0.46, 1)

	frame.squares = {}
	for visualRow = 1, 8 do
		for visualColumn = 1, 8 do
			local square = CreateFrame("Button", nil, frame.boardBackdrop, "BackdropTemplate")
			square:SetSize(52, 52)
			square:SetPoint("TOPLEFT", 4 + ((visualColumn - 1) * 52), -4 - ((visualRow - 1) * 52))
			square:SetBackdrop(ROW_BACKDROP)
			square.visualRow = visualRow
			square.visualColumn = visualColumn

			square.piece = square:CreateTexture(nil, "ARTWORK")
			square.piece:SetTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask")
			square.piece:SetSize(38, 38)
			square.piece:SetPoint("CENTER")
			square.piece:Hide()

			square.kingText = square:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
			square.kingText:SetPoint("CENTER", 0, 0)
			square.kingText:SetText("K")
			square.kingText:SetTextColor(1, 0.86, 0.30, 1)
			square.kingText:Hide()

			square.destination = square:CreateTexture(nil, "OVERLAY")
			square.destination:SetTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask")
			square.destination:SetSize(14, 14)
			square.destination:SetPoint("CENTER")
			square.destination:SetVertexColor(GUILD_GREEN:GetRGBA())
			square.destination:Hide()

			square:SetScript("OnClick", function(self)
				if self.boardIndex then
					GuildPing:HandleCheckersSquareClick(self.boardIndex)
				end
			end)
			square:RegisterForDrag("LeftButton")
			square:SetScript("OnDragStart", function(self)
				if self.boardIndex then
					GuildPing:StartCheckersPieceDrag(self.boardIndex)
				end
			end)
			square:SetScript("OnDragStop", function()
				GuildPing:StopCheckersPieceDrag()
			end)
			frame.squares[((visualRow - 1) * 8) + visualColumn] = square
		end
	end

	frame.dragPiece = CreateFrame("Frame", nil, UIParent)
	frame.dragPiece:SetSize(44, 44)
	frame.dragPiece:SetFrameStrata("TOOLTIP")
	frame.dragPiece:SetClampedToScreen(true)
	frame.dragPiece:EnableMouse(false)
	frame.dragPiece.piece = frame.dragPiece:CreateTexture(nil, "ARTWORK")
	frame.dragPiece.piece:SetTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask")
	frame.dragPiece.piece:SetAllPoints()
	frame.dragPiece.piece:SetAlpha(0.9)
	frame.dragPiece.kingText = frame.dragPiece:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
	frame.dragPiece.kingText:SetPoint("CENTER")
	frame.dragPiece.kingText:SetText("K")
	frame.dragPiece.kingText:SetTextColor(1, 0.86, 0.30, 1)
	frame.dragPiece:SetScript("OnUpdate", PositionDragPiece)
	frame.dragPiece:Hide()

	frame.moveText = CreateLabel(frame, "", "GameFontHighlightSmall")
	frame.moveText:SetPoint("TOP", frame.boardBackdrop, "BOTTOM", 0, -13)
	frame.moveText:SetTextColor(MUTED_COLOR:GetRGB())

	frame.lobbyButton = CreateButton(frame, "Checkers Lobby", 124, 25)
	frame.lobbyButton:SetPoint("BOTTOMLEFT", 24, 16)
	frame.lobbyButton:SetScript("OnClick", function()
		frame:Hide()
		GuildPing:ShowCheckersLobby()
	end)
	frame.resignButton = CreateButton(frame, "Resign", 100, 25, "danger")
	frame.resignButton:SetPoint("BOTTOM", 0, 16)
	frame.resignButton:SetScript("OnClick", function()
		EnsureResignPopup()
		local popupKey = GuildPing.activeCheckersGame and GuildPing.activeCheckersGame.practice
			and END_PRACTICE_POPUP_KEY or RESIGN_POPUP_KEY
		StaticPopup_Hide(popupKey)
		StaticPopup_Show(popupKey)
	end)
	frame.playAgainButton = CreateButton(frame, "Play Again", 112, 25, "primary")
	frame.playAgainButton:SetPoint("BOTTOM", 0, 16)
	frame.playAgainButton:SetScript("OnClick", function()
		local challenge, errorMessage = GuildPing:PlayCheckersAgain()
		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", 112, 25, "primary")
	frame.lifecycleResumeButton:SetPoint("BOTTOM", -66, 16)
	frame.lifecycleResumeButton:SetScript("OnClick", function()
		local resumed, errorMessage = GuildPing:ResumeCheckersGame()
		if not resumed then GuildPing:Print(errorMessage) end
	end)
	frame.lifecycleResumeButton:Hide()
	frame.abandonButton = CreateButton(frame, "Abandon Game", 126, 25, "danger")
	frame.abandonButton:SetPoint("BOTTOM", 62, 16)
	frame.abandonButton:SetScript("OnClick", function()
		EnsureResignPopup()
		StaticPopup_Hide(ABANDON_POPUP_KEY)
		StaticPopup_Show(ABANDON_POPUP_KEY)
	end)
	frame.abandonButton:Hide()
	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:RefreshCheckersBoard()
	end)
	frame:SetScript("OnHide", function()
		frame.dragSourceIndex = nil
		frame.dragPiece:Hide()
	end)
	frame:Hide()
	self.checkersBoard = frame
	return frame
end

function GuildPing:StartCheckersPieceDrag(boardIndex)
	local frame = self.checkersBoard
	local game = self.activeCheckersGame
	if not frame or not game or game.status ~= "ACTIVE" or not self:IsCheckersGameForCurrentGuild(game)
		or self:IsGameWaitingForResume(game) then
		return false
	end
	local controllableColor = self:GetCheckersControllableColor(game)
	if game.turn ~= controllableColor then
		return false
	end

	local canMovePiece = false
	for _, move in ipairs(self:GetCheckersLegalMoves(game, controllableColor)) do
		if move.from == boardIndex then
			canMovePiece = true
			break
		end
	end
	if not canMovePiece then
		return false
	end

	local piece = game.board[boardIndex]
	if not piece then
		return false
	end
	frame.dragSourceIndex = boardIndex
	frame.selectedIndex = boardIndex
	frame.dragPiece.piece:SetVertexColor(Unpack((piece == "b" or piece == "B") and BLACK_PIECE or RED_PIECE))
	frame.dragPiece.kingText:SetShown(piece == "B" or piece == "R")
	PositionDragPiece(frame.dragPiece)
	frame.dragPiece:Show()
	self:RefreshCheckersBoard()
	return true
end

function GuildPing:StopCheckersPieceDrag()
	local frame = self.checkersBoard
	if not frame or not frame.dragSourceIndex then
		return false
	end

	frame.dragSourceIndex = nil
	frame.dragPiece:Hide()
	local targetIndex
	for _, square in ipairs(frame.squares) do
		if square:IsMouseOver() then
			targetIndex = square.boardIndex
			break
		end
	end
	if targetIndex then
		self:HandleCheckersSquareClick(targetIndex)
	else
		self:RefreshCheckersBoard()
	end
	return true
end

function GuildPing:HandleCheckersSquareClick(boardIndex)
	local frame = self.checkersBoard
	local game = self.activeCheckersGame
	if not frame or not game or game.status ~= "ACTIVE" or not self:IsCheckersGameForCurrentGuild(game)
		or self:IsGameWaitingForResume(game) then
		return
	end
	local controllableColor = self:GetCheckersControllableColor(game)
	if game.turn ~= controllableColor then
		return
	end

	local legalMoves = self:GetCheckersLegalMoves(game, controllableColor)
	if frame.selectedIndex then
		for _, move in ipairs(legalMoves) do
			if move.from == frame.selectedIndex and move.to == boardIndex then
				local applied, errorMessage = self:PlayCheckersMove(move.from, move.to)
				if not applied then
					self:Print(errorMessage)
				else
					frame.selectedIndex = game.forcedPiece
				end
				self:RefreshCheckersBoard()
				return
			end
		end
	end

	local selectable = false
	for _, move in ipairs(legalMoves) do
		if move.from == boardIndex then
			selectable = true
			break
		end
	end
	if selectable then
		frame.selectedIndex = boardIndex
	elseif not game.forcedPiece then
		frame.selectedIndex = nil
	end
	self:RefreshCheckersBoard()
end

function GuildPing:RefreshCheckersBoard()
	local frame = self.checkersBoard
	if not frame then
		return
	end
	local game = self.activeCheckersGame
	if not game then
		frame:Hide()
		return
	end

	local localColor = self:GetLocalCheckersColor(game)
	local controllableColor = self:GetCheckersControllableColor(game)
	local opponent = self:GetCheckersOpponent(game) or "Unknown"
	local localLabel = localColor == self.CHECKERS_BLACK and "Black" or "Red"
	if game.practice then
		frame.opponentText:SetText("Practice mode  •  You control Black and Red")
	else
		frame.opponentText:SetText(string.format("You: %s  •  Opponent: %s", localLabel, opponent))
	end
	local currentGuildGame = self:IsCheckersGameForCurrentGuild(game)
	local waitingForResume = self:IsGameWaitingForResume(game)

	if not currentGuildGame then
		frame.statusText:SetText("This game belongs to a different guild and cannot continue here.")
		frame.statusText:SetTextColor(UPDATE_RED:GetRGB())
		frame.selectedIndex = nil
	elseif game.persistenceState == "RECONNECTING" then
		frame.statusText:SetText("Reconnecting to " .. opponent .. "…")
		frame.statusText:SetTextColor(WARNING_GOLD:GetRGB())
		frame.selectedIndex = nil
	elseif game.persistenceState == "STALE" then
		frame.statusText:SetText("No response from " .. opponent .. ". Resume to try again or abandon this game.")
		frame.statusText:SetTextColor(UPDATE_RED:GetRGB())
		frame.selectedIndex = nil
	elseif game.persistenceState == "RESTORED" then
		frame.statusText:SetText("Practice game restored. Resume when you're ready.")
		frame.statusText:SetTextColor(WARNING_GOLD:GetRGB())
		frame.selectedIndex = nil
	elseif game.status == "FINISHED" then
		if game.practice then
			if game.winner then
				local winnerLabel = game.winner == self.CHECKERS_BLACK and "Black" or "Red"
				frame.statusText:SetText(winnerLabel .. " wins — " .. tostring(game.finishReason or "game complete") .. ".")
			else
				frame.statusText:SetText("Practice game ended.")
			end
			frame.statusText:SetTextColor(GUILD_GREEN:GetRGB())
		elseif game.winner == localColor then
			frame.statusText:SetText("You won — " .. tostring(game.finishReason or "game complete") .. ".")
			frame.statusText:SetTextColor(GUILD_GREEN:GetRGB())
		else
			frame.statusText:SetText("You lost — " .. tostring(game.finishReason or "game complete") .. ".")
			frame.statusText:SetTextColor(UPDATE_RED:GetRGB())
		end
		frame.selectedIndex = nil
	elseif game.practice then
		local turnLabel = game.turn == self.CHECKERS_BLACK and "Black" or "Red"
		if game.forcedPiece then
			frame.statusText:SetText("Practice — " .. turnLabel .. " must continue the capture.")
		else
			frame.statusText:SetText("Practice — " .. turnLabel .. " to move. Drag a piece or click to select.")
		end
		frame.statusText:SetTextColor(GUILD_GREEN:GetRGB())
	elseif game.turn == localColor then
		if game.forcedPiece then
			frame.statusText:SetText("Your turn — continue the capture with the selected piece.")
		else
			frame.statusText:SetText("Your turn — drag a piece, or click it and a highlighted destination.")
		end
		frame.statusText:SetTextColor(GUILD_GREEN:GetRGB())
	else
		frame.statusText:SetText("Waiting for " .. opponent .. " to move.")
		frame.statusText:SetTextColor(WARNING_GOLD:GetRGB())
		frame.selectedIndex = nil
	end
	frame.moveText:SetText(string.format("Move %d  •  captures are optional; started multi-jumps must continue", game.moveNumber or 0))
	frame.resignButton:SetText(game.practice and "End Practice" or "Resign")
	frame.resignButton:SetShown(game.status == "ACTIVE" and not waitingForResume)
	frame.resignButton:SetEnabled(currentGuildGame and game.status == "ACTIVE" and not waitingForResume)
	local waitingForRematch = self.outgoingCheckersChallenge ~= nil
	frame.playAgainButton:SetShown(game.status == "FINISHED" and not waitingForResume)
	frame.playAgainButton:SetText(waitingForRematch and "Challenge Sent" or "Play Again")
	frame.playAgainButton:SetEnabled(currentGuildGame and game.status == "FINISHED" and not waitingForRematch)
	frame.lifecycleResumeButton:SetShown(waitingForResume)
	frame.lifecycleResumeButton:SetEnabled(currentGuildGame and game.persistenceState ~= "RECONNECTING")
	frame.abandonButton:SetShown(waitingForResume)
	frame.abandonButton:SetEnabled(currentGuildGame)

	local destinations = {}
	local selectable = {}
	if currentGuildGame and game.status == "ACTIVE" and not waitingForResume and game.turn == controllableColor then
		for _, move in ipairs(self:GetCheckersLegalMoves(game, controllableColor)) do
			selectable[move.from] = true
			if frame.selectedIndex == move.from then
				destinations[move.to] = true
			end
		end
		if game.forcedPiece then
			frame.selectedIndex = game.forcedPiece
			for _, move in ipairs(self:GetCheckersLegalMoves(game, controllableColor)) do
				if move.from == game.forcedPiece then
					destinations[move.to] = true
				end
			end
		end
	end

	for _, square in ipairs(frame.squares) do
		local visualRow = square.visualRow
		local visualColumn = square.visualColumn
		local boardRow = localColor == self.CHECKERS_BLACK and (9 - visualRow) or visualRow
		local boardColumn = localColor == self.CHECKERS_BLACK and (9 - visualColumn) or visualColumn
		local boardIndex = ((boardRow - 1) * 8) + boardColumn
		square.boardIndex = boardIndex
		local playable = (boardRow + boardColumn) % 2 == 1
		local baseColor = playable and DARK_SQUARE or LIGHT_SQUARE
		square:SetBackdropColor(Unpack(baseColor))

		if boardIndex == frame.selectedIndex then
			square:SetBackdropBorderColor(DISCORD_BLUE:GetRGB())
		elseif destinations[boardIndex] then
			square:SetBackdropBorderColor(GUILD_GREEN:GetRGB())
		elseif game.lastMove and (game.lastMove.from == boardIndex or game.lastMove.to == boardIndex) then
			square:SetBackdropBorderColor(WARNING_GOLD:GetRGB())
		else
			square:SetBackdropBorderColor(Unpack(baseColor))
		end

		local piece = game.board[boardIndex]
		if piece then
			local color
			if piece == "b" or piece == "B" then
				color = BLACK_PIECE
			else
				color = RED_PIECE
			end
			square.piece:SetVertexColor(Unpack(color))
			square.piece:Show()
			square.kingText:SetShown(piece == "B" or piece == "R")
		else
			square.piece:Hide()
			square.kingText:Hide()
		end
		square.destination:SetShown(destinations[boardIndex] == true)
		square:EnableMouse(currentGuildGame and game.status == "ACTIVE" and not waitingForResume and game.turn == controllableColor
			and (selectable[boardIndex] or destinations[boardIndex] or frame.selectedIndex ~= nil))
	end
end

function GuildPing:ShowCheckersLobby()
	if self.gamesHub then self.gamesHub:Hide() end
	if self.deathRollLobby then self.deathRollLobby:Hide() end
	if self.deathRollGameFrame then self.deathRollGameFrame:Hide() end
	if self.pokerLobby then self.pokerLobby:Hide() end
	if self.pokerTableFrame then self.pokerTableFrame:Hide() end
	if self.connectFourLobby then self.connectFourLobby:Hide() end
	if self.connectFourBoard then self.connectFourBoard:Hide() end
	if self.checkersBoard then
		self.checkersBoard:Hide()
	end
	local lobby = self:CreateCheckersLobby()
	lobby:Show()
	self:RefreshCheckersLobby()
end

function GuildPing:ShowCheckersBoard()
	if not self.activeCheckersGame then
		self:ShowCheckersLobby()
		return
	end
	self:AcknowledgeGameTurn("checkers")
	if self.checkersLobby then
		self.checkersLobby:Hide()
	end
	if self.gamesHub then self.gamesHub:Hide() end
	if self.deathRollLobby then self.deathRollLobby:Hide() end
	if self.deathRollGameFrame then self.deathRollGameFrame:Hide() end
	if self.pokerLobby then self.pokerLobby:Hide() end
	if self.pokerTableFrame then self.pokerTableFrame:Hide() end
	if self.connectFourLobby then self.connectFourLobby:Hide() end
	if self.connectFourBoard then self.connectFourBoard:Hide() end
	local frame = self:CreateCheckersBoard()
	frame:Show()
	self:RefreshCheckersBoard()
end

function GuildPing:ShowCheckers()
	if self.fiveDiceFrame then self.fiveDiceFrame:Hide() end
	if self.runeMastermindFrame then self.runeMastermindFrame:Hide() end
	if self.activeCheckersGame then
		self:ShowCheckersBoard()
	else
		self:ShowCheckersLobby()
	end
end

function GuildPing:RefreshCheckersIndicators()
	if self.RefreshGamesIndicators then
		self:RefreshGamesIndicators()
		return
	end
	local count = self:GetCheckersInviteCount()
	if self.picker and self.picker.gamesButton then
		self.picker.gamesButton:SetText(count > 0 and ("Games (" .. count .. ")") or "Games")
		self.picker.gamesButton:SetVariant(count > 0 and "primary" or "secondary")
	end
end
