-- Portal Pro :: Modules/Stats.lua
-- Session clock, aggregation, and the leads -> paid funnel.
--
-- The session clock pauses when you go idle so "gold per hour" reflects time actually
-- spent working, not time spent parked in Orgrimmar with the addon loaded.

local ADDON_NAME, PP = ...
local U = PP.U

local S = PP:NewModule("Stats")
PP.Stats = S

local IDLE_AFTER = 300   -- seconds without activity before the clock pauses

local session = {
	start        = 0,
	activeTime   = 0,
	lastActivity = 0,
	baseline     = nil,   -- counter snapshot at login
}

--------------------------------------------------------------------------------
-- Session
--------------------------------------------------------------------------------

function S:OnEnable()
	session.start        = time()
	session.lastActivity = time()
	session.activeTime   = 0
	session.baseline = {
		leads      = PP.global.counters.leads or 0,
		whispers   = PP.global.counters.whispers or 0,
		invites    = PP.global.counters.invites or 0,
		casts      = PP.global.counters.casts or 0,
		broadcasts = PP.global.counters.broadcasts or 0,
	}

	local function touch() S:Touch() end
	PP:On("LEAD_ADDED", touch)
	PP:On("WHISPER_SENT", touch)
	PP:On("INVITE_SENT", touch)
	PP:On("PORTAL_CAST", touch)
	PP:On("PAYMENT_RECEIVED", touch)
	PP:On("BROADCAST_SENT", touch)

	C_Timer.NewTicker(10, function() S:Tick() end)

	PP:RegisterEvent("PLAYER_LOGOUT", function() S:Persist() end)
end

--- Register activity, resuming the clock if it had gone idle.
function S:Touch()
	session.lastActivity = time()
end

function S:Tick()
	local now = time()
	if (now - session.lastActivity) <= IDLE_AFTER then
		session.activeTime = session.activeTime + 10
	end
end

function S:SessionStart()
	return session.start
end

function S:ActiveSeconds()
	return session.activeTime
end

function S:ElapsedSeconds()
	return time() - session.start
end

function S:Persist()
	local totals = PP.Ledger:Totals(session.start)
	table.insert(PP.global.sessions, {
		start       = session.start,
		stop        = time(),
		activeTime  = session.activeTime,
		netCopper   = totals.net,
		grossCopper = totals.gross,
		casts       = (PP.global.counters.casts or 0) - (session.baseline.casts or 0),
		leads       = (PP.global.counters.leads or 0) - (session.baseline.leads or 0),
	})
	-- Keep the last 100 sessions; beyond that the SavedVariables file just gets fat.
	while #PP.global.sessions > 100 do table.remove(PP.global.sessions, 1) end
end

--------------------------------------------------------------------------------
-- Windows
--------------------------------------------------------------------------------

function S:TodayStart()
	local t = date("*t")
	t.hour, t.min, t.sec = 0, 0, 0
	return time(t)
end

function S:WeekStart()
	return self:TodayStart() - 6 * 86400
end

--- @param window string "session" | "today" | "week" | "all"
function S:Totals(window)
	local since
	if window == "session" then since = session.start
	elseif window == "today" then since = self:TodayStart()
	elseif window == "week"  then since = self:WeekStart()
	end
	return PP.Ledger:Totals(since), since
end

--------------------------------------------------------------------------------
-- Derived figures
--------------------------------------------------------------------------------

--- Copper per hour of *active* time. Returns 0 until there's a meaningful sample,
--- because extrapolating from 40 seconds of data produces nonsense numbers.
function S:GoldPerHour(window)
	local totals = self:Totals(window or "session")
	local seconds = (window == nil or window == "session") and session.activeTime or self:WindowSeconds(window)
	if seconds < 60 then return 0 end
	return math.floor(totals.net / (seconds / 3600))
end

function S:WindowSeconds(window)
	if window == "today" then return time() - self:TodayStart() end
	if window == "week"  then return time() - self:WeekStart() end
	if window == "all" then
		local first = PP.Ledger:All()[1]
		return first and (time() - first.ts) or 0
	end
	return session.activeTime
end

--- leads -> whispered -> invited -> cast -> paid, for the current session.
function S:Funnel()
	local base = session.baseline or {}
	local counters = PP.global.counters
	local paid = 0
	for _, e in ipairs(PP.Ledger:Query({ since = session.start, kind = "payment" })) do
		paid = paid + 1
	end
	return {
		leads    = (counters.leads or 0)    - (base.leads or 0),
		whispers = (counters.whispers or 0) - (base.whispers or 0),
		invites  = (counters.invites or 0)  - (base.invites or 0),
		casts    = (counters.casts or 0)    - (base.casts or 0),
		paid     = paid,
	}
end

function S:ConversionRate()
	local f = self:Funnel()
	if f.leads == 0 then return 0 end
	return f.paid / f.leads
end

function S:AveragePayment(window)
	local since = select(2, self:Totals(window or "all"))
	local payments = PP.Ledger:Query({ since = since, kind = "payment" })
	if #payments == 0 then return 0 end
	local total = 0
	for _, e in ipairs(payments) do total = total + e.copper end
	return math.floor(total / #payments)
end

--- Best-earning destination in a window: key, copper.
function S:BestDestination(window)
	local since = select(2, self:Totals(window or "all"))
	local byDest = PP.Ledger:ByDestination(since)
	local bestKey, bestValue
	for key, value in pairs(byDest) do
		if not bestValue or value > bestValue then bestKey, bestValue = key, value end
	end
	return bestKey, bestValue or 0
end
