--[[
	Cerebro - RCLC Import Module
	
	PURPOSE:
	Import GP data from RCLootCouncil's loot database to build/rebuild Cerebro's
	GP history with RCLC as the source of truth. Also provides loot data for the
	Loot History tab.
	
	RESPONSIBILITIES:
	1. Read RCLC's lootDB from SavedVariables or parse pasted JSON
	2. Calculate GP per item using Cerebro's EPGP module
	3. Void stale Bridge-generated GP transactions for the import window
	4. Create antedated RCLC-sourced GP transactions
	5. Bump lastUpdate on affected players for LWW convergence
	6. Provide loot data getters for the Loot History UI tab
	
	SECURITY:
	- Import functions gated by IsOfficer()
	- In-raid, further gated by IsSyncLeader()
	- Read-only functions (GetLootDates, GetLootEntries) available to all
	
	EXTERNAL DEPENDENCIES:
	- RCLootCouncilDB (SavedVariables from RCLootCouncil addon)
	- Models/Ledger.lua (AddTransaction, VoidTransactions, ReplayHistory)
	- Modules/EPGP.lua (GetItemGP)
	- Modules/SyncScheduler.lua (IsSyncLeader)
]]--

local AddonName, _PrivateTable = ...
local Cerebro = LibStub("AceAddon-3.0"):GetAddon(AddonName)
local RCLCImport = Cerebro:NewModule("RCLC_Import", "AceEvent-3.0", "AceTimer-3.0")

-- luacheck: globals RCLootCouncilDB RCLootCouncilLootDB UnitFactionGroup GetRealmName

--[[=============================================
	Constants & Mappings
===============================================]]

-- Map RCLC friendly equip location names → WoW INVTYPE_ constants
-- RCLC stores equipLoc as friendly strings in its lootDB
local EQUIP_LOC_MAP = {
	["Back"]       = "INVTYPE_CLOAK",
	["Finger"]     = "INVTYPE_FINGER",
	["Trinket"]    = "INVTYPE_TRINKET",
	["Head"]       = "INVTYPE_HEAD",
	["Neck"]       = "INVTYPE_NECK",
	["Shoulder"]   = "INVTYPE_SHOULDER",
	["Chest"]      = "INVTYPE_CHEST",
	["Robe"]       = "INVTYPE_ROBE",
	["Wrist"]      = "INVTYPE_WRIST",
	["Hands"]      = "INVTYPE_HAND",
	["Waist"]      = "INVTYPE_WAIST",
	["Legs"]       = "INVTYPE_LEGS",
	["Feet"]       = "INVTYPE_FEET",
	["One-Hand"]   = "INVTYPE_WEAPON",
	["Two-Hand"]   = "INVTYPE_2HWEAPON",
	["Main Hand"]  = "INVTYPE_WEAPONMAINHAND",
	["Off Hand"]   = "INVTYPE_WEAPONOFFHAND",
	["Shield"]     = "INVTYPE_SHIELD",
	["Ranged"]     = "INVTYPE_RANGEDRIGHT",
	["Held In Off-hand"] = "INVTYPE_HOLDABLE",
}



-- RCLC responses that should NOT generate GP charges
local ZERO_GP_RESPONSES = {
	["pass"]         = true,
	["greed"]        = true,
	["disenchant"]   = true,
	["bank"]         = true,
	["free"]         = true,
	["personal loot - loss"]          = true,
	["personal loot - non tradeable"] = true,
	["personal loot - non tradable"]  = true,
	["autopass"]     = true,
}

--[[=============================================
	State
===============================================]]

-- Cached loot dates (detect new dates after RCLC sync)
RCLCImport._cachedDateCount = 0
RCLCImport._tabStale = false

--[[=============================================
	Lifecycle
===============================================]]

function RCLCImport:OnEnable()
	-- Listen for RCLC award events to mark the Loot History tab as stale
	self:RegisterMessage("RCLootCouncil_Award_Item", "OnRCLCEvent")
	self:RegisterMessage("RCMLAwardSuccess", "OnRCLCEvent")
end

function RCLCImport:OnRCLCEvent()
	self._tabStale = true
	Cerebro:Debug("RCLC_Import: Loot event detected, marking tab stale")
end

--- Parse a user-supplied date argument (MM-DD-YYYY) into RCLC's YYYY/MM/DD format.
-- @param dateArg string: date in MM-DD-YYYY format (e.g. "03-19-2026")
-- @return string|nil: RCLC-format date (e.g. "2026/03/19") or nil if invalid
function RCLCImport:ParseDateArg(dateArg)
	if not dateArg or type(dateArg) ~= "string" then return nil end
	
	local m, d, y = dateArg:match("^(%d%d)-(%d%d)-(%d%d%d%d)$")
	if not m then return nil end
	
	-- Validate ranges
	local month, day, year = tonumber(m), tonumber(d), tonumber(y)
	if month < 1 or month > 12 then return nil end
	if day < 1 or day > 31 then return nil end
	if year < 2020 or year > 2099 then return nil end
	
	-- Return RCLC's format: YYYY/MM/DD
	return string.format("%04d/%02d/%02d", year, month, day)
end

--[[=============================================
	RCLC Data Access
===============================================]]

--- Read all loot entries from RCLootCouncil's SavedVariables.
-- RCLC stores loot in a SEPARATE global: RCLootCouncilLootDB
-- Path: RCLootCouncilLootDB.factionrealm["Faction - Realm"][playerName] = {entry1, entry2, ...}
-- @return table: array of raw RCLC loot entries, or empty table if unavailable
function RCLCImport:ReadRCLCLootDB()
	-- RCLootCouncilLootDB is a separate global created by RCLC's loot history module
	local lootDB = RCLootCouncilLootDB
	if not lootDB then
		Cerebro:Debug("RCLC_Import: RCLootCouncilLootDB not found")
		return {}
	end
	
	local factionRealm = lootDB.factionrealm
	if not factionRealm then
		Cerebro:Debug("RCLC_Import: factionrealm not found in RCLootCouncilLootDB")
		return {}
	end
	
	-- Determine current faction-realm key
	local faction = UnitFactionGroup("player") or "Alliance"
	local realmName = GetRealmName() or "Unknown"
	local frKey = faction .. " - " .. realmName
	
	local realmData = factionRealm[frKey]
	if not realmData then
		-- Try iterating if exact key doesn't match
		for key, data in pairs(factionRealm) do
			if type(data) == "table" then
				realmData = data
				frKey = key
				break
			end
		end
	end
	
	if not realmData then
		Cerebro:Debug("RCLC_Import: No data for faction-realm: " .. frKey)
		return {}
	end
	
	-- RCLC LootDB structure: { [playerName] = { {entry1}, {entry2}, ... }, ... }
	-- RCLC stores entries under BOTH the ML/tracker AND the recipient after sync,
	-- so we must deduplicate by entry.id to avoid showing the same award twice.
	local entries = {}
	local seenIds = {}
	for playerKey, playerEntries in pairs(realmData) do
		if type(playerEntries) == "table" then
			for _, entry in ipairs(playerEntries) do
				if type(entry) == "table" then
					local entryId = entry.id
					if entryId and seenIds[entryId] then
						-- Skip duplicate: same award stored under a different player key
					else
						if entryId then seenIds[entryId] = true end
						entry._rclcPlayerKey = playerKey
						table.insert(entries, entry)
					end
				end
			end
		end
	end
	
	Cerebro:Debug(string.format("RCLC_Import: Read %d entries from %s", #entries, frKey))
	return entries
end

--- Get unique dates from RCLC loot data.
-- @param playerFilter string|nil: if set, only return dates where this player has entries
-- @return table: sorted array of date strings (YYYY/MM/DD)
function RCLCImport:GetLootDates(playerFilter)
	local entries = self:ReadRCLCLootDB()
	local dateSet = {}
	
	for _, entry in ipairs(entries) do
		if entry.date then
			-- If player filter is set, only include dates where this player got loot
			if not playerFilter or self:EntryMatchesPlayer(entry, playerFilter) then
				dateSet[entry.date] = true
			end
		end
	end
	
	-- Convert to sorted array (most recent first)
	local dates = {}
	for d in pairs(dateSet) do
		table.insert(dates, d)
	end
	table.sort(dates, function(a, b) return a > b end)
	
	return dates
end

--- Get loot entries for a specific date, optionally filtered by player.
-- @param dateStr string: date to filter by (YYYY/MM/DD format from RCLC)
-- @param playerFilter string|nil: if set, only return this player's entries
-- @return table: array of normalized entries sorted by time
function RCLCImport:GetLootEntries(dateStr, playerFilter)
	local rawEntries = self:ReadRCLCLootDB()
	local filtered = {}
	
	for _, entry in ipairs(rawEntries) do
		if entry.date == dateStr then
			-- Match RCLC's BuildData: index by storage key (_rclcPlayerKey), not owner
			if not playerFilter or entry._rclcPlayerKey == playerFilter then
				local normalized = self:NormalizeEntry(entry)
				if normalized then
					table.insert(filtered, normalized)
				end
			end
		end
	end
	
	-- Sort by servertime (chronological)
	table.sort(filtered, function(a, b)
		return (a.servertime or 0) < (b.servertime or 0)
	end)
	
	return filtered
end

--- Get all unique player names who received loot on a specific date.
-- @param dateStr string: date to query
-- @return table: sorted array of full player names
function RCLCImport:GetPlayersForDate(dateStr)
	local rawEntries = self:ReadRCLCLootDB()
	local playerSet = {}
	
	for _, entry in ipairs(rawEntries) do
		if entry.date == dateStr then
			-- Use storage key to match RCLC's BuildData indexing
			local playerName = entry._rclcPlayerKey
			if playerName then
				playerSet[playerName] = true
			end
		end
	end
	
	local players = {}
	for name in pairs(playerSet) do
		table.insert(players, name)
	end
	table.sort(players)
	
	return players
end

--- Check if RCLC has new dates since last cache.
-- Used to detect when RCLC auto-syncs data for missing raid nights.
-- @return boolean: true if new dates were detected
function RCLCImport:DetectNewRCLCDates()
	local dates = self:GetLootDates()
	local currentCount = #dates
	
	if currentCount > self._cachedDateCount then
		Cerebro:Debug(string.format("RCLC_Import: New dates detected (%d -> %d)",
			self._cachedDateCount, currentCount))
		self._cachedDateCount = currentCount
		self._tabStale = true
		return true
	end
	
	return false
end

--[[=============================================
	Entry Normalization
===============================================]]

--- Resolve the full player name from an RCLC entry.
-- RCLC uses various name formats, we normalize to Name-Realm.
-- @param entry table: raw RCLC entry
-- @return string|nil: full player name (Name-Realm) or nil
function RCLCImport:ResolvePlayerName(entry)
	-- RCLC stores owner as "Name-Realm" or just "Name"
	-- Use _rclcPlayerKey to match RCLC's BuildData indexing (NOT owner)
	local playerKey = entry._rclcPlayerKey or entry.owner
	if not playerKey then return nil end
	
	-- Normalize via Cerebro's utility
	return Cerebro:GetFullPlayerName(playerKey)
end

--- Check if an RCLC entry matches a player name filter.
-- @param entry table: raw RCLC entry
-- @param playerFilter string: full player name (Name-Realm)
-- @return boolean
function RCLCImport:EntryMatchesPlayer(entry, playerFilter)
	local entryPlayer = self:ResolvePlayerName(entry)
	if not entryPlayer or not playerFilter then return false end
	
	-- Compare full names
	return entryPlayer == playerFilter
end

--- Normalize a raw RCLC entry into our internal format.
-- RCLC field mapping:
--   lootWon   → itemLink
--   owner     → player name (Name-Realm)
--   date      → "YYYY/MM/DD"
--   time      → "HH:MM:SS" (string, not number)
--   response  → response text
--   difficultyID → WoW difficulty ID (number)
--   class     → player class (e.g. "PRIEST")
--   boss      → boss name
--   instance  → instance name (e.g. "The Dreamrift-Normal")
--   id        → RCLC unique ID (e.g. "1773970200-2")
-- @param rclcEntry table: raw entry from RCLootCouncilLootDB
-- @return table|nil: normalized entry or nil if invalid
function RCLCImport:NormalizeEntry(rclcEntry)
	if not rclcEntry then return nil end
	
	local playerName = self:ResolvePlayerName(rclcEntry)
	if not playerName then return nil end
	
	-- Extract item info (RCLC uses "lootWon" for the item link)
	local itemLink = rclcEntry.lootWon or rclcEntry.itemLink
	local itemString = rclcEntry.itemString
	
	-- Try to get item name from the link
	local itemName = rclcEntry.item
	if not itemName and itemLink and itemLink:find("item:") then
		local name = C_Item.GetItemInfo(itemLink)
		if name then itemName = name end
	end
	if not itemName then
		-- Extract from link display text: |Hitem:...|h[Item Name]|h
		itemName = itemLink and itemLink:match("%[(.-)%]") or "Unknown"
	end
	
	-- Get equip location from RCLC data or from WoW API
	local equipLoc = rclcEntry.equipLoc
	if equipLoc and EQUIP_LOC_MAP[equipLoc] then
		equipLoc = EQUIP_LOC_MAP[equipLoc]
	elseif itemLink and itemLink:find("item:") then
		-- Fallback: use WoW API
		local _, _, _, eqLoc = C_Item.GetItemInfoInstant(itemLink)
		equipLoc = eqLoc
	end
	
	-- Use RCLC's difficultyID directly (it's already numeric)
	local difficultyID = rclcEntry.difficultyID or 14  -- Default to Normal
	
	-- Response text and ID
	local response = rclcEntry.response or "Unknown"
	local responseID = rclcEntry.responseID
	
	-- RCLC's time field is a string "HH:MM:SS", not a Unix timestamp
	-- Parse it into seconds-since-midnight for sorting
	local timeStr = rclcEntry.time or ""
	local servertime = 0
	if type(timeStr) == "string" then
		local h, m, s = timeStr:match("(%d+):(%d+):(%d+)")
		if h then
			servertime = tonumber(h) * 3600 + tonumber(m) * 60 + (tonumber(s) or 0)
		end
	end
	
	-- Build a full Unix timestamp from RCLC's date (YYYY/MM/DD) + time (HH:MM:SS)
	-- This is used for transaction antedating in the import pipeline
	local fullTimestamp = 0
	local dateStr = rclcEntry.date or ""
	local yr, mo, dy = dateStr:match("(%d+)/(%d+)/(%d+)")
	if yr then
		local th, tm, ts = 0, 0, 0
		if type(timeStr) == "string" then
			th, tm, ts = timeStr:match("(%d+):(%d+):(%d+)")
			th, tm, ts = tonumber(th) or 0, tonumber(tm) or 0, tonumber(ts) or 0
		end
		fullTimestamp = time({
			year = tonumber(yr), month = tonumber(mo), day = tonumber(dy),
			hour = th, min = tm, sec = ts
		})
	end
	
	-- Generate a stable RCLC ID for deduplication
	-- Use RCLC's own unique id if available, otherwise construct one
	local rclcId = rclcEntry.id or string.format("%s|%s|%s",
		playerName,
		rclcEntry.date or "?",
		itemLink or "?")
	
	return {
		player = playerName,
		itemLink = itemLink,
		itemString = itemString,
		itemName = itemName,
		equipLoc = equipLoc,
		difficultyID = difficultyID,
		timestamp = fullTimestamp,  -- Full Unix timestamp for import antedating
		response = response,
		responseID = responseID,
		servertime = servertime,
		rclcId = rclcId,
		boss = rclcEntry.boss or "Unknown",
		class = rclcEntry.class,
		date = rclcEntry.date,
		time = timeStr,
		instance = rclcEntry.instance or "",
	}
end

--[[=============================================
	GP Calculation
===============================================]]

--- Calculate GP cost for an RCLC loot entry.
-- Uses Cerebro's EPGP module for consistent GP calculations.
-- @param entry table: normalized entry from NormalizeEntry()
-- @return number: GP cost (0 for pass/transmog/etc.)
function RCLCImport:CalculateGP(entry)
	if not entry then return 0 end
	
	-- Check if this response should generate 0 GP
	local lowerResp = string.lower(entry.response or "")
	if ZERO_GP_RESPONSES[lowerResp] then
		return 0
	end
	
	-- Also check for "Transmog" or "Offspec" patterns (0 GP)
	if lowerResp:find("transmog") or lowerResp:find("offspec") then
		return 0
	end
	
	-- Use EPGP module to calculate GP
	if entry.itemLink and entry.itemLink:find("item:") then
		local gp = Cerebro.modules.EPGP:GetItemGP(
			entry.itemLink,
			entry.difficultyID
		)
		return gp or 0
	end
	
	return 0
end

--[[=============================================
	Import Pipeline
===============================================]]

--- Main import function. Reads RCLC data, voids old transactions,
-- creates new ones, and triggers convergence.
-- @param sinceDate number: Unix timestamp; only import entries >= this date
-- @return table: import results { imported=N, skipped=N, voided=N, errors={} }
function RCLCImport:ImportFromRCLC(sinceDate)
	-- Gate: Officer check
	if not Cerebro:IsOfficer() then
		Cerebro:Print("RCLC Import requires officer permissions.")
		return { imported = 0, skipped = 0, voided = 0, errors = { "Not an officer" } }
	end
	
	-- Gate: In-raid, only SyncLeader can import
	if IsInRaid() and not Cerebro.modules.SyncScheduler:IsSyncLeader() then
		Cerebro:Print("In raid, only the Sync Leader can run RCLC import.")
		return { imported = 0, skipped = 0, voided = 0, errors = { "Not Sync Leader" } }
	end
	
	-- Read RCLC data
	local rawEntries = self:ReadRCLCLootDB()
	if #rawEntries == 0 then
		Cerebro:Print("No RCLC loot data found. Is RCLootCouncil installed?")
		return { imported = 0, skipped = 0, voided = 0, errors = { "No RCLC data" } }
	end
	
	-- Normalize all entries
	local entries = {}
	for _, raw in ipairs(rawEntries) do
		local normalized = self:NormalizeEntry(raw)
		if normalized then
			table.insert(entries, normalized)
		end
	end
	
	-- Date scope: filter to entries >= sinceDate
	-- sinceDate is now a RCLC-format date string (e.g. "2026/03/19")
	-- Compare as strings since YYYY/MM/DD sorts lexicographically
	local scoped = {}
	for _, entry in ipairs(entries) do
		if entry.date and entry.date >= sinceDate then
			table.insert(scoped, entry)
		end
	end
	
	Cerebro:Debug(string.format("RCLC Import: %d entries in scope (of %d total, since %s)",
		#scoped, #entries, sinceDate))
	
	-- Dedup check: skip entries already imported
	Cerebro.db.global.rclcImportedIds = Cerebro.db.global.rclcImportedIds or {}
	local dedup = Cerebro.db.global.rclcImportedIds
	local toImport = {}
	local skippedDedup = 0
	
	for _, entry in ipairs(scoped) do
		if dedup[entry.rclcId] then
			skippedDedup = skippedDedup + 1
		else
			table.insert(toImport, entry)
		end
	end
	
	-- Void phase: mark existing GP-LOOT transactions from RCLC import as voided
	-- Convert sinceDate string to Unix timestamp for comparison with ledger timestamps
	local yr, mo, dy = sinceDate:match("(%d+)/(%d+)/(%d+)")
	local sinceDateUnix = yr and time({ year = tonumber(yr), month = tonumber(mo), day = tonumber(dy), hour = 0, min = 0, sec = 0 }) or 0
	local voidedCount = Cerebro.Ledger:VoidTransactions(function(t)
		return t.currency == "GP"
			and t.reason == "LOOT"
			and t.timestamp >= sinceDateUnix
			and not t.voided
	end)
	
	-- Create phase: insert new RCLC-sourced transactions
	local importedCount = 0
	local affectedPlayers = {}
	
	for _, entry in ipairs(toImport) do
		local gp = self:CalculateGP(entry)
		
		-- Only create a transaction if GP > 0 (skip pass/transmog items)
		-- BUT still record in dedup to prevent future re-processing
		dedup[entry.rclcId] = entry.servertime
		
		if gp > 0 then
			-- Only create GP transaction for players in Cerebro's database
			-- Non-guild players show "N/A" in tab but don't get transactions
			local playerData = Cerebro.db.global.players[entry.player]
			if playerData then
				-- Create antedated transaction
				Cerebro.Ledger:AddTransaction(
					entry.player,  -- Full player name (Name-Realm)
					gp,            -- GP amount
					"GP",          -- Currency
					"LOOT",        -- Reason (same as RCLC Bridge uses)
					{              -- Details
						item = entry.itemLink or entry.itemName,
						slot = entry.equipLoc and Cerebro:GetFriendlySlotName(entry.equipLoc) or "Unknown",
						boss = entry.boss,
						response = entry.response,
						rclcId = entry.rclcId,
						source = "RCLC-Import",
						importedAt = time(),
					},
					true,  -- isImport = true
					{      -- options
						overrideTimestamp = entry.timestamp, -- Antedate to original RCLC date+time
					}
				)
				
				affectedPlayers[entry.player] = true
				importedCount = importedCount + 1
			end
		end
	end
	
	-- Replay: rebuild all EP/GP from ledger
	Cerebro.Ledger:ReplayHistory()
	
	-- LWW Anchor: bump lastUpdate for ALL affected players
	-- This is the critical convergence step — V2 heartbeat will detect
	-- roster hash mismatch and propagate corrected GP to all clients
	local now = GetServerTime and GetServerTime() or time()
	for playerName in pairs(affectedPlayers) do
		local pData = Cerebro.db.global.players[playerName]
		if pData then
			pData.lastUpdate = now
		end
	end
	
	-- Also bump lastUpdate for ALL players whose GP was reset by void
	-- (their GP changed even if they didn't get new RCLC entries)
	for name, data in pairs(Cerebro.db.global.players) do
		if not affectedPlayers[name] then
			-- Check if this player had voided transactions
			-- Simple approach: bump all players to ensure convergence
			data.lastUpdate = now
		end
	end
	
	-- Recalculate PR
	Cerebro:RecalculateAllPR()
	
	-- Refresh UI
	Cerebro:RefreshAllUI()
	
	-- Store import metadata
	Cerebro.db.global.rclcLastImport = {
		timestamp = time(),
		sinceDate = sinceDate,
		imported = importedCount,
		voided = voidedCount,
		skipped = skippedDedup,
		importedBy = Cerebro:GetPlayerFullName(),
	}
	
	-- Generate report
	local results = {
		imported = importedCount,
		skipped = skippedDedup,
		voided = voidedCount,
		errors = {},
	}
	self:GenerateReport(results)
	
	-- Audit log
	Cerebro.Ledger:AddAuditLog("RCLC_IMPORT",
		string.format("Imported %d GP transactions from RCLC (voided %d, skipped %d dedup) since %s by %s",
			importedCount, voidedCount, skippedDedup,
			sinceDate,
			Cerebro:GetPlayerFullName()))
	
	return results
end

--- Dry-run simulation: shows exactly what an import WOULD do without touching data.
-- Completely read-only: no transactions, no voiding, no ReplayHistory.
-- @param sinceDate string: RCLC-format date (YYYY/MM/DD) from ParseDateArg
function RCLCImport:SimulateImport(sinceDate)
	Cerebro:Print("=== RCLC Import Dry-Run ===")
	Cerebro:Print(string.format("Scope: entries from %s forward", sinceDate))
	
	-- 1. Read RCLC data
	local rawEntries = self:ReadRCLCLootDB()
	if #rawEntries == 0 then
		Cerebro:Print("|cffff0000No RCLC loot data found.|r")
		return
	end
	Cerebro:Print(string.format("  Raw RCLC entries: %d", #rawEntries))
	
	-- 2. Normalize
	local entries = {}
	for _, raw in ipairs(rawEntries) do
		local normalized = self:NormalizeEntry(raw)
		if normalized then
			table.insert(entries, normalized)
		end
	end
	Cerebro:Print(string.format("  Normalized entries: %d", #entries))
	
	-- 3. Date scope
	local scoped = {}
	for _, entry in ipairs(entries) do
		if entry.date and entry.date >= sinceDate then
			table.insert(scoped, entry)
		end
	end
	Cerebro:Print(string.format("  In-scope entries: %d", #scoped))
	
	-- 4. Dedup check (read-only — just count)
	Cerebro.db.global.rclcImportedIds = Cerebro.db.global.rclcImportedIds or {}
	local dedup = Cerebro.db.global.rclcImportedIds
	local toImport = {}
	local skippedDedup = 0
	for _, entry in ipairs(scoped) do
		if dedup[entry.rclcId] then
			skippedDedup = skippedDedup + 1
		else
			table.insert(toImport, entry)
		end
	end
	Cerebro:Print(string.format("  Already imported (dedup skip): %d", skippedDedup))
	Cerebro:Print(string.format("  New entries to process: %d", #toImport))
	
	-- 5. Calculate GP per player (read-only)
	local playerGP = {}     -- [playerName] = total GP
	local playerItems = {}  -- [playerName] = count of GP-charging items
	local zeroGPCount = 0
	local noPlayerCount = 0
	
	for _, entry in ipairs(toImport) do
		local gp = self:CalculateGP(entry)
		
		if gp > 0 then
			local playerData = Cerebro.db.global.players[entry.player]
			if playerData then
				playerGP[entry.player] = (playerGP[entry.player] or 0) + gp
				playerItems[entry.player] = (playerItems[entry.player] or 0) + 1
			else
				noPlayerCount = noPlayerCount + 1
			end
		else
			zeroGPCount = zeroGPCount + 1
		end
	end
	
	-- 6. Count what would be voided
	local yr, mo, dy = sinceDate:match("(%d+)/(%d+)/(%d+)")
	local sinceDateUnix = yr and time({ year = tonumber(yr), month = tonumber(mo), day = tonumber(dy), hour = 0, min = 0, sec = 0 }) or 0
	local wouldVoid = 0
	for _, t in ipairs(Cerebro.db.global.transactions) do
		if t.currency == "GP" and t.reason == "LOOT" and t.timestamp >= sinceDateUnix and not t.voided then
			wouldVoid = wouldVoid + 1
		end
	end
	
	-- 7. Print report
	Cerebro:Print("")
	Cerebro:Print("|cffFFD700--- Results ------|r")
	Cerebro:Print(string.format("  Would void: %d existing GP-LOOT transactions", wouldVoid))
	Cerebro:Print(string.format("  Zero-GP items (pass/personal/transmog): %d", zeroGPCount))
	Cerebro:Print(string.format("  Players not in Cerebro DB (skipped): %d", noPlayerCount))
	
	-- Per-player breakdown
	local sortedPlayers = {}
	for name in pairs(playerGP) do
		table.insert(sortedPlayers, name)
	end
	table.sort(sortedPlayers)
	
	local totalGP = 0
	local totalItems = 0
	Cerebro:Print(string.format("  |cff00ff00Would create GP transactions for %d players:|r", #sortedPlayers))
	for _, name in ipairs(sortedPlayers) do
		local shortName = name:match("^([^-]+)") or name
		Cerebro:Print(string.format("    %s: %d items, %s GP",
			shortName, playerItems[name], Cerebro:FormatNumber(playerGP[name])))
		totalGP = totalGP + playerGP[name]
		totalItems = totalItems + playerItems[name]
	end
	
	Cerebro:Print("")
	Cerebro:Print(string.format("|cffFFD700Total: %d items → %s GP across %d players|r",
		totalItems, Cerebro:FormatNumber(totalGP), #sortedPlayers))
	Cerebro:Print("|cff00ff00This is a dry run. No data was modified.|r")
	Cerebro:Print("To run the real import: /cb rclc import " ..
		(sinceDate:gsub("(%d+)/(%d+)/(%d+)", "%2-%3-%1")))
end

--[[=============================================
	Override Timestamp Support
===============================================]]

-- We need AddTransaction to support overrideTimestamp for antedating.
-- This is done by patching the entry's timestamp after creation.
-- Since AddTransaction returns the UUID, we can find and patch it.
-- 
-- Actually, looking at the existing AddTransaction code, it takes
-- options as the 7th parameter. We'll need to check if it supports
-- timestamp override. If not, we'll post-patch.
-- 
-- For now, we'll use a post-patch approach: after AddTransaction,
-- find the entry by UUID and update its timestamp.

--- Wrapped AddTransaction that supports overrideTimestamp in options.
-- We hook this in OnEnable so it only applies when RCLC_Import is loaded.
function RCLCImport:PatchAddTransactionForAntedate()
	if self._addTransactionPatched then return end
	
	local originalFn = Cerebro.Ledger.AddTransaction
	
	Cerebro.Ledger.AddTransaction = function(ledger, player, amount, currency, reason, details, isImport, options)
		-- Call original
		local uuid = originalFn(ledger, player, amount, currency, reason, details, isImport, options)
		
		-- Post-patch: if options.overrideTimestamp, find the entry and update it
		if options and options.overrideTimestamp and uuid then
			for i = #Cerebro.db.global.transactions, 1, -1 do
				local t = Cerebro.db.global.transactions[i]
				if t.id == uuid then
					t.timestamp = options.overrideTimestamp
					t.timestampUTC = date("!%Y-%m-%d %H:%M:%S", options.overrideTimestamp)
					t.source = "RCLC-Import"
					break
				end
			end
		end
		
		return uuid
	end
	
	self._addTransactionPatched = true
	Cerebro:Debug("RCLC_Import: AddTransaction patched for antedate support")
end

-- Ensure patch is applied on enable
local _originalOnEnable = RCLCImport.OnEnable
RCLCImport.OnEnable = function(self)
	self:PatchAddTransactionForAntedate()
	if _originalOnEnable then
		_originalOnEnable(self)
	end
end

--[[=============================================
	Date Parsing
===============================================]]



--[[=============================================
	GP Calculation Helpers for UI
===============================================]]

--- Get the starting GP for a player on a specific date.
-- Replays ledger transactions before that date to compute GP state.
-- @param playerName string: full player name (Name-Realm)
-- @param dayTimestamp number: Unix timestamp of the start of the day
-- @return number: GP value at the start of that day
function RCLCImport:GetStartingGP(playerName, dayTimestamp)
	local baseGP = (Cerebro.db.global.config.epgp and Cerebro.db.global.config.epgp.baseGP) or 200
	
	if not playerName or not dayTimestamp then return baseGP end
	
	-- Replay only non-voided GP transactions for this player before the day
	local gp = baseGP
	for _, t in ipairs(Cerebro.db.global.transactions) do
		if not t.voided
			and t.player == playerName
			and t.currency == "GP"
			and t.timestamp < dayTimestamp then
			gp = gp + t.amount
		end
	end
	
	return gp
end

--[[=============================================
	Reporting
===============================================]]

--- Print import summary to chat.
-- @param results table: { imported=N, skipped=N, voided=N, errors={} }
function RCLCImport:GenerateReport(results)
	Cerebro:Print("=== RCLC GP Import Summary ===")
	Cerebro:Print(string.format("  Imported: %d GP transactions", results.imported))
	Cerebro:Print(string.format("  Skipped:  %d (already imported)", results.skipped))
	Cerebro:Print(string.format("  Voided:   %d old transactions", results.voided))
	
	if #results.errors > 0 then
		Cerebro:Print("  Errors:")
		for _, err in ipairs(results.errors) do
			Cerebro:Print("    - " .. err)
		end
	end
	
	Cerebro:Print("GP values updated. V2 sync will propagate to all online players.")
end

--- Print status of last import.
function RCLCImport:PrintStatus()
	local last = Cerebro.db.global.rclcLastImport
	if not last then
		Cerebro:Print("No RCLC import has been performed yet.")
		return
	end
	
	Cerebro:Print("=== RCLC Import Status ===")
	Cerebro:Print(string.format("  Last import: %s", date("!%Y-%m-%d %H:%M:%S", last.timestamp)))
	Cerebro:Print(string.format("  Since date:  %s", date("!%Y-%m-%d", last.sinceDate)))
	Cerebro:Print(string.format("  Imported:    %d transactions", last.imported))
	Cerebro:Print(string.format("  Voided:      %d old transactions", last.voided))
	Cerebro:Print(string.format("  Skipped:     %d (dedup)", last.skipped))
	Cerebro:Print(string.format("  Imported by: %s", last.importedBy or "?"))
end
