--[[
	Cerebro - Player Model
	
	PURPOSE:
	Pure EPGP mathematical logic for Priority Ratio (PR), Effort Points (EP),
	and Gear Points (GP) calculations. This module is WoW API agnostic and
	authorization agnostic - it only performs calculations based on database
	values.
	
	CONTRACTS (See docs/SPEC.md Section 2.1, 2.4):
	- Priority Ratio: PR = EP / GP (rounded to 4 decimal places)
	- GP Floor: Effective GP = max(Raw GP, BaseGP)
	- Decay Formula: NewValue = OldValue * (1 - DecayRate)
	- BaseGP Default: 200 (configurable)
	
	KEY INVARIANTS:
	1. GetPR() always returns exactly 4 decimal places (via string.format)
	2. GetGP() never returns a value below BaseGP, even if stored GP is lower
	3. Decay applies to Raw GP; effective GP remains floored by BaseGP
	4. UpdatePR() caches PR in database for performance (updated on all mutations)
	
	DESIGN DECISIONS:
	- BaseGP is the authoritative floor. MinGP config exists in Database but is
	  not used per current implementation (See docs/SPEC_AMBIGUITIES.md Issue 1.4)
	- PR is cached in db.global.players[name].pr for UI performance optimization
	
	SECURITY:
	This module has NO authorization checks. Security is enforced at caller level
	(Modules/EPGP.lua, Core.lua). All methods are safe to call by any code.
	
	DEPENDENCIES:
	- Cerebro:GetPlayerData() - Database accessor
	- Cerebro:GetFullPlayerName() - Name normalization
	- Cerebro.db.global.config.epgp.baseGP - Configuration value
]]--

local AddonName, PrivateTable = ...
local Cerebro = LibStub("AceAddon-3.0"):GetAddon(AddonName)
local Player = {}
Cerebro.Player = Player

-- Constructor / Factory
function Player:New(nameOrUnit)
	local o = {}
	setmetatable(o, self)
	self.__index = self
	
	o.name = Cerebro:GetFullPlayerName(nameOrUnit)
	return o
end

-- Retrieve raw data from DB (safe: never returns nil)
function Player:GetData()
	return Cerebro:GetPlayerData(self.name) or Cerebro.GUEST_STUB or { ep = 0, gp = 200, pr = 0, status = "Pending" }
end

-- Getters
function Player:GetEP()
	return self:GetData().ep or 0
end

-- Returns effective GP (never below BaseGP floor)
-- Note: Raw GP can be lower due to decay, but effective GP is always >= BaseGP
-- This ensures PR calculations never divide by 0 or produce inflated values
function Player:GetGP()
	local data = self:GetData()
	local baseGP = Cerebro.db.global.config.epgp.baseGP or 200
	-- Floor: max(RawGP, BaseGP)
	return math.max(data.gp or baseGP, baseGP)
end

function Player:GetRawGP()
	local data = self:GetData()
	return data.gp or 0
end

-- Calculates Priority Ratio: PR = EP / GP
-- Returns: Number rounded to exactly 4 decimal places (SPEC.md 2.1)
-- Contract: Must always return 4-decimal precision for consistency
function Player:GetPR()
	local ep = self:GetEP()
	local gp = self:GetGP() -- Guaranteed >= BaseGP
	
	-- Defensive check (should never trigger due to BaseGP floor)
	if gp == 0 then return 0 end
	
	-- Precision: Always 4 decimals (e.g., "0.3333")
	return tonumber(string.format("%.4f", ep / gp))
end

function Player:GetClass()
	return self:GetData().class or "WARRIOR"
end

function Player:GetStatus()
	return self:GetData().status or "Core"
end

-- Setters (Internal use, prefer using Ledger/EPGP Module for transaction safety)
function Player:SetEP(amount)
	self:GetData().ep = amount
	self:UpdatePR()
end

function Player:SetGP(amount)
	self:GetData().gp = amount
	self:UpdatePR()
end

-- Performance Optimization: Cache PR in database
-- Called automatically after all EP/GP mutations to keep cached value fresh
-- This avoids recalculating PR on every UI render (Standings frame uses cached value)
function Player:UpdatePR()
	self:GetData().pr = self:GetPR()
end

-- Modifiers
function Player:AddEP(amount)
	local data = self:GetData()
	data.ep = (data.ep or 0) + amount
	data.lastUpdate = GetServerTime and GetServerTime() or time() -- LWW sync
	self:UpdatePR()
	Cerebro:MarkPlayerDirty(self.name, {"ep"})  -- Delta Sync v2
	return data.ep
end

function Player:AddGP(amount)
	local data = self:GetData()
	data.gp = (data.gp or 0) + amount
	data.lastUpdate = GetServerTime and GetServerTime() or time() -- LWW sync
	self:UpdatePR()
	Cerebro:MarkPlayerDirty(self.name, {"gp"})  -- Delta Sync v2
	return data.gp
end

-- Applies weekly decay to both EP and GP
-- Formula: NewValue = OldValue * (1 - rate) (SPEC.md 2.4)
-- Example: 100 EP at 10% rate (0.1) becomes 90 EP
-- Returns: (epDecayed, gpDecayed) - the amounts removed
-- Note: Decay modifies RAW GP. Effective GP (via GetGP) remains >= BaseGP
function Player:Decay(rate)
	local data = self:GetData()
	local oldEP = data.ep or 0
	local oldGP = data.gp or 0 -- Decay acts on raw GP
	
	-- Apply decay formula (floor to prevent floating-point precision creep)
	data.ep = math.floor(oldEP * (1 - rate))
	-- Decay raw GP. GetGP() will still enforce BaseGP floor on retrieval.
	data.gp = math.floor(oldGP * (1 - rate))
	
	-- Update timestamp and cached PR
	data.lastUpdate = GetServerTime and GetServerTime() or time() -- LWW sync
	self:UpdatePR()
	Cerebro:MarkPlayerDirty(self.name, {"ep", "gp"})  -- Delta Sync v2
	
	-- Return amounts decayed (for logging/reporting)
	return (oldEP - data.ep), (oldGP - data.gp)
end
