local _, namespace = ...
local GuildPing = namespace.GuildPing

local GAME_KEY = "fiveDice"
local DICE_COUNT = 5
local MAX_ROLLS = 3
local UPPER_BONUS_THRESHOLD = 63
local UPPER_BONUS = 35

local CATEGORIES = {
	{ key = "ONES", label = "Ones", upper = 1 },
	{ key = "TWOS", label = "Twos", upper = 2 },
	{ key = "THREES", label = "Threes", upper = 3 },
	{ key = "FOURS", label = "Fours", upper = 4 },
	{ key = "FIVES", label = "Fives", upper = 5 },
	{ key = "SIXES", label = "Sixes", upper = 6 },
	{ key = "THREE_KIND", label = "Three of a kind" },
	{ key = "FOUR_KIND", label = "Four of a kind" },
	{ key = "FULL_HOUSE", label = "Full house" },
	{ key = "SMALL_STRAIGHT", label = "Small straight" },
	{ key = "LARGE_STRAIGHT", label = "Large straight" },
	{ key = "FIVE_KIND", label = "Five of a kind" },
	{ key = "CHANCE", label = "Chance" },
}

local CATEGORY_LOOKUP = {}
for _, category in ipairs(CATEGORIES) do CATEGORY_LOOKUP[category.key] = category end

local function CreateGameID()
	local timestamp = GetServerTime and GetServerTime() or math.floor(GetTime and GetTime() or 0)
	return string.format("fd%x%x", timestamp, math.random(0, 0xFFFF))
end

local function CountDice(dice)
	local counts, total = {}, 0
	for index = 1, DICE_COUNT do
		local value = math.floor(tonumber(dice and dice[index]) or 0)
		if value >= 1 and value <= 6 then
			counts[value] = (counts[value] or 0) + 1
			total = total + value
		end
	end
	return counts, total
end

local function HasStraight(counts, length)
	local run = 0
	for value = 1, 6 do
		if counts[value] then
			run = run + 1
			if run >= length then return true end
		else
			run = 0
		end
	end
	return false
end

local function CalculateScore(categoryKey, dice)
	local category = CATEGORY_LOOKUP[categoryKey]
	if not category then return nil end
	local counts, total = CountDice(dice)
	if category.upper then return (counts[category.upper] or 0) * category.upper end
	if categoryKey == "CHANCE" then return total end
	if categoryKey == "SMALL_STRAIGHT" then return HasStraight(counts, 4) and 30 or 0 end
	if categoryKey == "LARGE_STRAIGHT" then return HasStraight(counts, 5) and 40 or 0 end
	local hasTwo, hasThree, largest = false, false, 0
	for value = 1, 6 do
		local count = counts[value] or 0
		hasTwo, hasThree, largest = hasTwo or count == 2, hasThree or count == 3, math.max(largest, count)
	end
	if categoryKey == "THREE_KIND" then return largest >= 3 and total or 0 end
	if categoryKey == "FOUR_KIND" then return largest >= 4 and total or 0 end
	if categoryKey == "FULL_HOUSE" then return hasTwo and hasThree and 25 or 0 end
	if categoryKey == "FIVE_KIND" then return largest == 5 and 50 or 0 end
	return 0
end

local function CalculateTotals(game)
	local upper, categoryTotal = 0, 0
	for _, category in ipairs(CATEGORIES) do
		local score = math.max(0, math.floor(tonumber(game.scores and game.scores[category.key]) or 0))
		categoryTotal = categoryTotal + score
		if category.upper then upper = upper + score end
	end
	local bonus = upper >= UPPER_BONUS_THRESHOLD and UPPER_BONUS or 0
	return upper, bonus, categoryTotal, categoryTotal + bonus
end

local function CreateGame(profileKey)
	return {
		id = CreateGameID(), profileKey = profileKey, status = "ACTIVE",
		dice = { 0, 0, 0, 0, 0 }, held = { false, false, false, false, false },
		scores = {}, rollCount = 0, round = 1,
		startedAt = GetServerTime and GetServerTime() or 0,
	}
end

local function IsValidSavedGame(game, profileKey)
	if type(game) ~= "table" or type(game.id) ~= "string" or game.profileKey ~= profileKey
		or not (game.status == "ACTIVE" or game.status == "FINISHED")
		or type(game.dice) ~= "table" or type(game.held) ~= "table" or type(game.scores) ~= "table" then return false end
	for index = 1, DICE_COUNT do
		local value = tonumber(game.dice[index])
		if not value or value < 0 or value > 6 or value ~= math.floor(value) then return false end
		game.dice[index] = value
		game.held[index] = game.held[index] == true
	end
	local cleanedScores, scoreCount = {}, 0
	for key, score in pairs(game.scores) do
		if not CATEGORY_LOOKUP[key] or type(score) ~= "number" or score < 0 then return false end
		cleanedScores[key] = math.floor(score)
		scoreCount = scoreCount + 1
	end
	game.scores = cleanedScores
	game.rollCount = math.max(0, math.min(MAX_ROLLS, math.floor(tonumber(game.rollCount) or 0)))
	game.round = scoreCount + 1
	game.status = scoreCount == #CATEGORIES and "FINISHED" or "ACTIVE"
	local _, _, _, grandTotal = CalculateTotals(game)
	game.totalScore = game.status == "FINISHED" and grandTotal or nil
	return true
end

function GuildPing:GetFiveDiceCategories() return CATEGORIES end
function GuildPing:GetFiveDiceCategoryScore(categoryKey, dice) return CalculateScore(categoryKey, dice) end
function GuildPing:GetFiveDiceTotals(game) return CalculateTotals(game or self.activeFiveDiceGame or { scores = {} }) end

function GuildPing:RefreshFiveDiceUI()
	local game = self.activeFiveDiceGame
	if game then
		if game.status == "FINISHED" then
			local _, _, _, total = CalculateTotals(game)
			game.totalScore = total
			self:RecordSoloGameResult(GAME_KEY, game, true, { score = total, reason = total .. " points" })
		end
		self:PersistGameState(GAME_KEY, game)
	end
	if self.RefreshFiveDiceFrame and self.fiveDiceFrame and self.fiveDiceFrame:IsShown() then self:RefreshFiveDiceFrame() end
	if self.RefreshGamesIndicators then self:RefreshGamesIndicators() end
end

function GuildPing:StartFiveDiceGame()
	local _, profileKey = self:GetProfile()
	self.activeFiveDiceGame = CreateGame(profileKey)
	self:RefreshFiveDiceUI()
	return self.activeFiveDiceGame
end

function GuildPing:AbandonFiveDiceGame()
	self.activeFiveDiceGame = nil
	self:ClearSavedGameState(GAME_KEY)
	if self.RefreshFiveDiceFrame and self.fiveDiceFrame and self.fiveDiceFrame:IsShown() then self:RefreshFiveDiceFrame() end
	if self.RefreshGamesIndicators then self:RefreshGamesIndicators() end
	return true
end

function GuildPing:RollFiveDice()
	local game = self.activeFiveDiceGame
	if not game or game.status ~= "ACTIVE" then return nil, "Start a new Five Dice game first." end
	if game.rollCount >= MAX_ROLLS then return nil, "Choose a score before rolling again." end
	local rolledIndices = {}
	for index = 1, DICE_COUNT do
		if not game.held[index] then
			game.dice[index] = math.random(1, 6)
			table.insert(rolledIndices, index)
		end
	end
	if #rolledIndices == 0 then return nil, "Release at least one die before rolling." end
	game.rollCount = game.rollCount + 1
	game.lastRoll = { count = game.rollCount, indices = rolledIndices }
	if self.AnimateFiveDiceRoll then self:AnimateFiveDiceRoll(game, rolledIndices) end
	self:RefreshFiveDiceUI()
	return game.dice
end

function GuildPing:ToggleFiveDiceHold(index)
	local game = self.activeFiveDiceGame
	index = math.floor(tonumber(index) or 0)
	if not game or game.status ~= "ACTIVE" then return nil, "There is no active Five Dice game." end
	if game.rollCount < 1 then return nil, "Roll the dice before choosing holds." end
	if index < 1 or index > DICE_COUNT then return nil, "Choose a die from 1 to 5." end
	game.held[index] = not game.held[index]
	self:RefreshFiveDiceUI()
	return game.held[index]
end

function GuildPing:ScoreFiveDiceCategory(categoryKey)
	local game = self.activeFiveDiceGame
	if not game or game.status ~= "ACTIVE" then return nil, "There is no active Five Dice game." end
	if game.rollCount < 1 then return nil, "Roll the dice before choosing a score." end
	if not CATEGORY_LOOKUP[categoryKey] then return nil, "Choose a valid score category." end
	if game.scores[categoryKey] ~= nil then return nil, "That category has already been scored." end
	local score = CalculateScore(categoryKey, game.dice)
	game.scores[categoryKey] = score
	game.round = game.round + 1
	if game.round > #CATEGORIES then
		game.status = "FINISHED"
		game.held = { false, false, false, false, false }
	else
		game.dice = { 0, 0, 0, 0, 0 }
		game.held = { false, false, false, false, false }
		game.rollCount = 0
	end
	self:RefreshFiveDiceUI()
	return score
end

function GuildPing:InitializeFiveDice()
	local restored, reason = self:RestoreSavedGameState(GAME_KEY)
	local _, profileKey = self:GetProfile()
	if restored and IsValidSavedGame(restored, profileKey) then
		self.activeFiveDiceGame = restored
		self:RefreshFiveDiceUI()
	elseif restored then
		self:ClearSavedGameState(GAME_KEY)
	elseif reason == "EXPIRED" then
		self:Print("An unfinished Five Dice game expired and was removed.")
	end
end

GuildPing.FIVE_DICE_MAX_ROLLS = MAX_ROLLS
GuildPing.FIVE_DICE_CATEGORY_COUNT = #CATEGORIES
