local _, ns = ...
local SlickUI = ns.SlickUI
local Theme = SlickUI.Theme

local GoldLedger = {
	LastMoney = nil,
	SessionStartedAt = 0,
	SessionIncome = 0,
	SessionExpenses = 0,
	Context = nil,
	RecentContext = nil,
	PendingCategory = nil,
	Filter = { period = "today", direction = "all", category = "all" },
	ScrollOffset = 0,
	VisibleRows = 15,
	BalanceScrollOffset = 0,
	Tracker = { active = false, income = 0, expenses = 0, startedAt = 0, stoppedAt = 0 },
}
SlickUI.GoldLedger = GoldLedger

local function SafeNumber(value)
	if canaccessvalue and not canaccessvalue(value) then return nil end
	if issecretvalue and issecretvalue(value) then return nil end
	return type(value) == "number" and value or nil
end

local function Settings()
	return SlickUI.DB.goldLedger
end

local function TodayKey()
	local current = C_DateAndTime and C_DateAndTime.GetCurrentCalendarTime and C_DateAndTime.GetCurrentCalendarTime()
	if current and SafeNumber(current.year) and SafeNumber(current.month) and SafeNumber(current.monthDay) then
		return string.format("%04d-%02d-%02d", current.year, current.month, current.monthDay)
	end
	return date("%Y-%m-%d")
end

local function TimeText()
	local current = C_DateAndTime and C_DateAndTime.GetCurrentCalendarTime and C_DateAndTime.GetCurrentCalendarTime()
	if current and SafeNumber(current.hour) and SafeNumber(current.minute) then
		return string.format("%02d:%02d", current.hour, current.minute)
	end
	return date("%H:%M")
end

local function CharacterKey()
	local name = UnitName("player") or "Unknown"
	local realm = GetRealmName() or "Unknown Realm"
	return name .. " - " .. realm
end

function GoldLedger:UpdateBalanceCache()
	local current = SafeNumber(GetMoney())
	if not current then return end
	Settings().characterBalances = Settings().characterBalances or {}
	Settings().characterBalances[CharacterKey()] = current
end

function GoldLedger:UpdateWarbandBalance()
	if not (C_Bank and type(C_Bank.FetchDepositedMoney) == "function" and Enum and Enum.BankType) then return end
	local ok, amount = pcall(C_Bank.FetchDepositedMoney, Enum.BankType.Account)
	amount = ok and SafeNumber(amount) or nil
	if amount then Settings().warbandBalance = amount end
end

function GoldLedger:MatchesCharacterScope(entry)
	return Settings().characterScope == "all" or entry.character == CharacterKey()
end

local function MoneyParts(copper)
	copper = math.max(0, math.floor(math.abs(copper or 0) + 0.5))
	return math.floor(copper / 10000), math.floor((copper % 10000) / 100), copper % 100
end

local function GroupDigits(value)
	local text = tostring(math.max(0, math.floor(tonumber(value) or 0)))
	local grouped = text:reverse():gsub("(%d%d%d)", "%1,"):reverse()
	return grouped:gsub("^,", "")
end

function GoldLedger:FormatCoinMoney(copper)
	local gold, silver, coins = MoneyParts(SafeNumber(copper) or 0)
	return string.format("|cffffd24a%sg|r |cffc7c7cf%02ds|r |cffc87533%02dc|r", GroupDigits(gold), silver, coins)
end

function GoldLedger:FormatMoney(copper, signed, colored)
	local value = SafeNumber(copper) or 0
	local sign = signed and (value > 0 and "+" or value < 0 and "-" or "") or ""
	local gold, silver, coins = MoneyParts(value)
	local text = string.format("%s%dg %02ds %02dc", sign, gold, silver, coins)
	if not colored then return text end
	local color = value > 0 and "ff42df72" or value < 0 and "ffff4a45" or "ffd5d8e0"
	return "|c" .. color .. text .. "|r"
end

function GoldLedger:FormatCompact(copper, signed)
	local value = SafeNumber(copper) or 0
	local sign = signed and (value > 0 and "+" or value < 0 and "-" or "") or ""
	local absolute = math.abs(value)
	local text
	if absolute >= 100000000 then text = string.format("%.1fkg", absolute / 10000000)
	elseif absolute >= 10000 then text = string.format("%.1fg", absolute / 10000)
	elseif absolute >= 100 then text = string.format("%.1fs", absolute / 100)
	else text = string.format("%dc", absolute) end
	local color = value > 0 and "ff42df72" or value < 0 and "ffff4a45" or "ffd5d8e0"
	return "|c" .. color .. sign .. text .. "|r"
end

function GoldLedger:GetTodayTotals()
	local income, expenses = 0, 0
	local today = TodayKey()
	for _, entry in ipairs(Settings().entries) do
		if entry.date == today and self:MatchesCharacterScope(entry) then
			if entry.amount > 0 then income = income + entry.amount else expenses = expenses - entry.amount end
		end
	end
	return income, expenses
end

function GoldLedger:GetBarText()
	local balance = SafeNumber(GetMoney()) or self.LastMoney or 0
	local todayIncome, todayExpenses = self:GetTodayTotals()
	local sessionNet = self.SessionIncome - self.SessionExpenses
	local todayNet = todayIncome - todayExpenses
	local gold, silver = MoneyParts(balance)
	return string.format("Gold: |cffffd24a%dg|r |cffd5d8e0%02ds|r  Session: %s  Today: %s", gold, silver, self:FormatCompact(sessionNet, true), self:FormatCompact(todayNet, true))
end

function GoldLedger:BuildTooltip(tooltip)
	local todayIncome, todayExpenses = self:GetTodayTotals()
	tooltip:AddLine("Gold Ledger", unpack(Theme.Colors.goldText))
	tooltip:AddDoubleLine("Ledger scope", Settings().characterScope == "all" and "All Characters" or "Current Character", 1, 1, 1, 0.40, 0.82, 1.00)
	tooltip:AddDoubleLine("Current balance", self:FormatMoney(SafeNumber(GetMoney()) or 0, false, true), 1, 1, 1, 1, 1, 1)
	tooltip:AddDoubleLine("Session income", self:FormatMoney(self.SessionIncome, true, true), 1, 1, 1, 1, 1, 1)
	tooltip:AddDoubleLine("Session expenses", self:FormatMoney(-self.SessionExpenses, true, true), 1, 1, 1, 1, 1, 1)
	tooltip:AddDoubleLine("Session net", self:FormatMoney(self.SessionIncome - self.SessionExpenses, true, true), 1, 1, 1, 1, 1, 1)
	tooltip:AddLine(" ")
	tooltip:AddDoubleLine("Today's income", self:FormatMoney(todayIncome, true, true), 1, 1, 1, 1, 1, 1)
	tooltip:AddDoubleLine("Today's expenses", self:FormatMoney(-todayExpenses, true, true), 1, 1, 1, 1, 1, 1)
	tooltip:AddDoubleLine("Today's net", self:FormatMoney(todayIncome - todayExpenses, true, true), 1, 1, 1, 1, 1, 1)
	if self.Tracker.active then
		tooltip:AddLine(" ")
		tooltip:AddLine("Timed tracking is active.", 0.40, 0.82, 1.00)
	end
end

function GoldLedger:SetPendingCategory(category, duration)
	self.PendingCategory = { category = category, expires = (GetTime() or 0) + (duration or 2) }
end

function GoldLedger:ResolveCategory(amount)
	local now = GetTime() or 0
	if self.PendingCategory and self.PendingCategory.expires >= now then
		local category = self.PendingCategory.category
		self.PendingCategory = nil
		return category
	end
	local context = self.Context
	if not context and self.RecentContext and self.RecentContext.expires >= now then context = self.RecentContext.name end
	if context == "merchant" then return amount > 0 and "Vendor Sales" or "Vendor Purchases"
	elseif context == "trainer" then return amount > 0 and "Other Income" or "Training"
	elseif context == "auction" then return amount > 0 and "Auction Sales" or "Auction Purchases / Fees"
	elseif context == "mail" then return amount > 0 and "Mail / Auction Income" or "Mail Expenses"
	elseif context == "trade" then return amount > 0 and "Player Trade Income" or "Player Trade Expense"
	elseif context == "loot" and amount > 0 then return "Looted Gold" end
	return amount > 0 and "Other Income" or "Other Expense"
end

function GoldLedger:Record(amount, balance)
	if not SafeNumber(amount) or amount == 0 then return end
	local entry = {
		date = TodayKey(), time = TimeText(), timestamp = GetServerTime and GetServerTime() or time(),
		amount = amount, balance = balance, category = self:ResolveCategory(amount), character = CharacterKey(),
	}
	if Settings().enabled then
		table.insert(Settings().entries, 1, entry)
		while #Settings().entries > Settings().maxEntries do table.remove(Settings().entries) end
	end
	if amount > 0 then self.SessionIncome = self.SessionIncome + amount else self.SessionExpenses = self.SessionExpenses - amount end
	if self.Tracker.active then
		if amount > 0 then self.Tracker.income = self.Tracker.income + amount else self.Tracker.expenses = self.Tracker.expenses - amount end
	end
	self:RefreshWindows()
	if SlickUI.InformationBars then SlickUI.InformationBars:UpdateBlocks() end
end

function GoldLedger:OnMoneyChanged()
	local current = SafeNumber(GetMoney())
	if not current then return end
	self:UpdateBalanceCache()
	if self.LastMoney == nil then self.LastMoney = current return end
	local delta = current - self.LastMoney
	self.LastMoney = current
	if delta == 0 then return end
	C_Timer.After(0.20, function() GoldLedger:Record(delta, current) end)
end

function GoldLedger:SetContext(name) self.Context = name end
function GoldLedger:ClearContext(name)
	if self.Context == name then
		self.Context = nil
		self.RecentContext = { name = name, expires = (GetTime() or 0) + 1.5 }
	end
end

function GoldLedger:GetAccountBalances()
	self:UpdateBalanceCache()
	self:UpdateWarbandBalance()
	local balances, known = {}, {}
	for character, amount in pairs(Settings().characterBalances or {}) do
		amount = SafeNumber(amount)
		if type(character) == "string" and amount then
			known[character] = true
			balances[#balances + 1] = { name = character, amount = amount }
		end
	end
	-- Seed characters recorded before the dedicated balance cache existed.
	for _, entry in ipairs(Settings().entries or {}) do
		if type(entry.character) == "string" and not known[entry.character] then
			local amount = SafeNumber(entry.balance)
			if amount then
				known[entry.character] = true
				balances[#balances + 1] = { name = entry.character, amount = amount }
			end
		end
	end
	table.sort(balances, function(left, right)
		if left.amount == right.amount then return left.name < right.name end
		return left.amount > right.amount
	end)
	return balances, SafeNumber(Settings().warbandBalance) or 0
end

function GoldLedger:CreateBalanceWindow()
	if self.BalanceWindow then return self.BalanceWindow end
	local window = CreateFrame("Frame", "SlickUIAccountGoldFrame", UIParent, "BackdropTemplate")
	window:SetSize(500, 470)
	local position = Settings().balanceWindowPosition or {}
	window:SetPoint("CENTER", UIParent, "CENTER", position.x or 0, position.y or 80)
	window:SetFrameStrata("DIALOG")
	window:SetClampedToScreen(true)
	window:SetMovable(true)
	window:EnableMouse(true)
	window:RegisterForDrag("LeftButton")
	window:SetScript("OnDragStart", function(self) self:StartMoving() end)
	window:SetScript("OnDragStop", function(self)
		self:StopMovingOrSizing()
		local x, y = self:GetCenter()
		local ux, uy = UIParent:GetCenter()
		if x and y and ux and uy then Settings().balanceWindowPosition = { x = x - ux, y = y - uy } end
	end)
	Theme:CreatePanel(window)
	window:Hide()
	table.insert(UISpecialFrames, window:GetName())

	Theme:CreateHeaderIcon(window, 36, 14, -9)
	local title = Theme:CreateText(window, 18)
	title:SetPoint("TOPLEFT", 56, -16)
	title:SetText("Account Gold")
	title:SetTextColor(unpack(Theme.Colors.goldText))
	local close = CreateFrame("Button", nil, window, "UIPanelCloseButton")
	close:SetPoint("TOPRIGHT", -4, -4)
	local total = Theme:CreateText(window, 13)
	total:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -10)
	window.Total = total
	local hint = Theme:CreateText(window, 9)
	hint:SetPoint("TOPLEFT", total, "BOTTOMLEFT", 0, -5)
	hint:SetText("Warband Bank is always first. Mouse-wheel to browse characters.")
	hint:SetTextColor(unpack(Theme.Colors.mutedText))
	window.Hint = hint

	local header = CreateFrame("Frame", nil, window, "BackdropTemplate")
	header:SetPoint("TOPLEFT", 18, -90)
	header:SetPoint("TOPRIGHT", -18, -90)
	header:SetHeight(25)
	header:SetBackdrop({ bgFile = Theme.Texture })
	header:SetBackdropColor(unpack(Theme.Colors.panelSoft))
	local nameHeader = Theme:CreateText(header, 10)
	nameHeader:SetPoint("LEFT", 10, 0)
	nameHeader:SetText("Account / Character")
	nameHeader:SetTextColor(unpack(Theme.Colors.goldText))
	local amountHeader = Theme:CreateText(header, 10)
	amountHeader:SetPoint("RIGHT", -10, 0)
	amountHeader:SetText("Balance")
	amountHeader:SetTextColor(unpack(Theme.Colors.goldText))

	window.Rows = {}
	for index = 1, 12 do
		local row = CreateFrame("Frame", nil, window, "BackdropTemplate")
		row:SetPoint("TOPLEFT", header, "BOTTOMLEFT", 0, -2 - ((index - 1) * 27))
		row:SetPoint("TOPRIGHT", header, "BOTTOMRIGHT", 0, -2 - ((index - 1) * 27))
		row:SetHeight(25)
		row:SetBackdrop({ bgFile = Theme.Texture })
		row:SetBackdropColor(index % 2 == 0 and 0.07 or 0.04, 0.05, 0.08, 0.72)
		row.Name = Theme:CreateText(row, 10)
		row.Name:SetPoint("LEFT", 10, 0)
		row.Amount = Theme:CreateText(row, 10)
		row.Amount:SetPoint("RIGHT", -10, 0)
		window.Rows[index] = row
	end
	window:EnableMouseWheel(true)
	window:SetScript("OnMouseWheel", function(_, delta)
		local characters = GoldLedger:GetAccountBalances()
		local maximum = math.max(0, #characters - 11)
		GoldLedger.BalanceScrollOffset = math.max(0, math.min(maximum, GoldLedger.BalanceScrollOffset - delta * 3))
		GoldLedger:RefreshBalanceWindow()
	end)
	self.BalanceWindow = window
	return window
end

function GoldLedger:RefreshBalanceWindow()
	if not self.BalanceWindow then return end
	local characters, warband = self:GetAccountBalances()
	local total = warband
	for _, entry in ipairs(characters) do total = total + entry.amount end
	self.BalanceWindow.Total:SetText("Total Gold (characters + Warband): " .. self:FormatCoinMoney(total))
	local maximum = math.max(0, #characters - 11)
	self.BalanceScrollOffset = math.max(0, math.min(maximum, self.BalanceScrollOffset or 0))
	local display = { { name = "Warband Bank", amount = warband, warband = true } }
	for index = self.BalanceScrollOffset + 1, math.min(#characters, self.BalanceScrollOffset + 11) do
		display[#display + 1] = characters[index]
	end
	if #characters > 11 then
		local first = self.BalanceScrollOffset + 1
		local last = math.min(#characters, self.BalanceScrollOffset + 11)
		self.BalanceWindow.Hint:SetText(string.format("Warband Bank is always first. Characters %d-%d of %d; mouse-wheel to browse.", first, last, #characters))
	else
		self.BalanceWindow.Hint:SetText("Warband Bank is always first. Characters are sorted by balance.")
	end
	for index, row in ipairs(self.BalanceWindow.Rows) do
		local entry = display[index]
		row:SetShown(entry ~= nil)
		if entry then
			row.Name:SetText(entry.name)
			if entry.warband then row.Name:SetTextColor(unpack(Theme.Colors.goldText)) else row.Name:SetTextColor(0.85, 0.88, 0.95) end
			row.Amount:SetText(self:FormatCoinMoney(entry.amount))
		end
	end
end

function GoldLedger:ToggleBalanceWindow()
	local window = self:CreateBalanceWindow()
	if window:IsShown() then window:Hide() else self:RefreshBalanceWindow(); window:Show(); window:Raise() end
end

function GoldLedger:StartTracker()
	self.Tracker.active, self.Tracker.income, self.Tracker.expenses = true, 0, 0
	self.Tracker.startedAt, self.Tracker.stoppedAt = GetTime() or 0, 0
	if Settings().showTracker then self:ShowTracker() end
	self:RefreshWindows()
end

function GoldLedger:StopTracker()
	if self.Tracker.active then self.Tracker.stoppedAt = GetTime() or self.Tracker.startedAt end
	self.Tracker.active = false
	self:RefreshWindows()
end

function GoldLedger:GetTrackerElapsed()
	local finish = self.Tracker.active and (GetTime() or self.Tracker.startedAt) or self.Tracker.stoppedAt
	return math.max(0, finish - self.Tracker.startedAt)
end

local function DurationText(seconds)
	seconds = math.floor(seconds or 0)
	return string.format("%02d:%02d:%02d", math.floor(seconds / 3600), math.floor(seconds / 60) % 60, seconds % 60)
end

function GoldLedger:CreateTracker()
	if self.TrackerWindow then return self.TrackerWindow end
	local frame = CreateFrame("Frame", "SlickUIGoldTrackerFrame", UIParent, "BackdropTemplate")
	frame:SetSize(310, 158)
	local position = Settings().trackerPosition
	frame:SetPoint("CENTER", UIParent, "CENTER", position.x or 0, position.y or 180)
	frame:SetFrameStrata("DIALOG")
	frame:SetClampedToScreen(true)
	frame:SetMovable(true)
	frame:EnableMouse(true)
	frame:RegisterForDrag("LeftButton")
	frame:SetScript("OnDragStart", frame.StartMoving)
	frame:SetScript("OnDragStop", function(self)
		self:StopMovingOrSizing()
		local x, y = self:GetCenter()
		local ux, uy = UIParent:GetCenter()
		if x and y and ux and uy then Settings().trackerPosition.x, Settings().trackerPosition.y = x - ux, y - uy end
	end)
	Theme:CreatePanel(frame)
	local title = Theme:CreateText(frame, 14)
	title:SetPoint("TOPLEFT", 14, -12)
	title:SetText("Timed Gold Tracker")
	title:SetTextColor(unpack(Theme.Colors.goldText))
	local close = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
	close:SetPoint("TOPRIGHT", -2, -2)
	local body = Theme:CreateText(frame, 11)
	body:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -10)
	body:SetJustifyH("LEFT")
	body:SetSpacing(3)
	frame.Body = body
	local stop = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	stop:SetSize(112, 22)
	stop:SetPoint("BOTTOMLEFT", 14, 12)
	stop:SetText("Stop Tracking")
	stop:SetScript("OnClick", function() GoldLedger:StopTracker() end)
	frame.Stop = stop
	local ledger = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	ledger:SetSize(112, 22)
	ledger:SetPoint("LEFT", stop, "RIGHT", 8, 0)
	ledger:SetText("Open Ledger")
	ledger:SetScript("OnClick", function() GoldLedger:ShowWindow() end)
	frame:Hide()
	self.TrackerWindow = frame
	return frame
end

function GoldLedger:RefreshTracker()
	if not self.TrackerWindow then return end
	local elapsed = self:GetTrackerElapsed()
	local net = self.Tracker.income - self.Tracker.expenses
	local perHour = elapsed >= 1 and net / elapsed * 3600 or 0
	self.TrackerWindow.Body:SetText(table.concat({
		"Time: |cff48b8ff" .. DurationText(elapsed) .. "|r",
		"Income: " .. self:FormatMoney(self.Tracker.income, true, true),
		"Expenses: " .. self:FormatMoney(-self.Tracker.expenses, true, true),
		"Net: " .. self:FormatMoney(net, true, true) .. "   Per hour: " .. self:FormatMoney(perHour, true, true),
	}, "\n"))
	self.TrackerWindow.Stop:SetEnabled(self.Tracker.active)
	self.TrackerWindow.Stop:SetText(self.Tracker.active and "Stop Tracking" or "Tracking Stopped")
end

function GoldLedger:ShowTracker()
	local frame = self:CreateTracker()
	self:RefreshTracker()
	frame:Show()
	frame:Raise()
end

local function CreateDropdown(parent, name, width, choices, getter, setter)
	local dropdown = CreateFrame("Frame", name, parent, "UIDropDownMenuTemplate")
	UIDropDownMenu_SetWidth(dropdown, width)
	UIDropDownMenu_Initialize(dropdown, function()
		for _, choice in ipairs(choices()) do
			local info = UIDropDownMenu_CreateInfo()
			info.text, info.value = choice.label, choice.value
			info.checked = getter() == choice.value
			info.func = function() setter(choice.value) end
			UIDropDownMenu_AddButton(info)
		end
	end)
	return dropdown
end

function GoldLedger:GetCategories()
	local seen, result = {}, { { label = "All Categories", value = "all" } }
	for _, entry in ipairs(Settings().entries) do
		if self:MatchesCharacterScope(entry) then seen[entry.category] = true end
	end
	local names = {}
	for name in pairs(seen) do names[#names + 1] = name end
	table.sort(names)
	for _, name in ipairs(names) do result[#result + 1] = { label = name, value = name } end
	return result
end

function GoldLedger:GetFilteredEntries()
	local results, today = {}, TodayKey()
	for _, entry in ipairs(Settings().entries) do
		local periodOK = self.Filter.period == "all" or (self.Filter.period == "today" and entry.date == today) or (self.Filter.period == "session" and (entry.timestamp or 0) >= self.SessionStartedAt)
		local directionOK = self.Filter.direction == "all" or (self.Filter.direction == "income" and entry.amount > 0) or (self.Filter.direction == "expenses" and entry.amount < 0)
		local categoryOK = self.Filter.category == "all" or self.Filter.category == entry.category
		local characterOK = self:MatchesCharacterScope(entry)
		if periodOK and directionOK and categoryOK and characterOK then results[#results + 1] = entry end
	end
	return results
end

function GoldLedger:GetCategorySummary(entries)
	local grouped = {}
	for _, entry in ipairs(entries or {}) do
		local category = entry.category or "Other"
		local row = grouped[category]
		if not row then
			row = { category = category, count = 0, income = 0, expenses = 0, net = 0 }
			grouped[category] = row
		end
		row.count = row.count + 1
		if entry.amount > 0 then
			row.income = row.income + entry.amount
		else
			row.expenses = row.expenses - entry.amount
		end
		row.net = row.net + entry.amount
	end
	local result = {}
	for _, row in pairs(grouped) do result[#result + 1] = row end
	table.sort(result, function(left, right) return string.lower(left.category) < string.lower(right.category) end)
	return result
end

function GoldLedger:CreateWindow()
	if self.Window then return self.Window end
	local window = CreateFrame("Frame", "SlickUIGoldLedgerFrame", UIParent, "BackdropTemplate")
	window:SetSize(1040, 610)
	window:SetPoint("CENTER")
	window:SetFrameStrata("DIALOG")
	window:SetClampedToScreen(true)
	window:SetMovable(true)
	window:EnableMouse(true)
	window:RegisterForDrag("LeftButton")
	window:SetScript("OnDragStart", window.StartMoving)
	window:SetScript("OnDragStop", window.StopMovingOrSizing)
	Theme:CreatePanel(window)
	window:Hide()
	table.insert(UISpecialFrames, window:GetName())
	Theme:CreateHeaderIcon(window, 36, 14, -9)
	local title = Theme:CreateText(window, 18)
	title:SetPoint("TOPLEFT", 56, -16)
	title:SetText("SlickUI Gold Ledger")
	title:SetTextColor(unpack(Theme.Colors.goldText))
	local close = CreateFrame("Button", nil, window, "UIPanelCloseButton")
	close:SetPoint("TOPRIGHT", -4, -4)
	local summary = Theme:CreateText(window, 11)
	summary:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -8)
	summary:SetPoint("RIGHT", window, "RIGHT", -225, 0)
	summary:SetJustifyH("LEFT")
	window.Summary = summary
	local viewToggle = CreateFrame("Button", nil, window, "UIPanelButtonTemplate")
	viewToggle:SetSize(190, 24)
	viewToggle:SetPoint("TOPRIGHT", window, "TOPRIGHT", -42, -48)
	viewToggle:SetScript("OnClick", function()
		Settings().viewMode = Settings().viewMode == "summary" and "transactions" or "summary"
		GoldLedger.ScrollOffset = 0
		GoldLedger:RefreshLedger()
	end)
	window.ViewToggle = viewToggle

	local periods = function() return { {label="Today",value="today"},{label="This Session",value="session"},{label="All History",value="all"} } end
	local period = CreateDropdown(window, "SlickUIGoldPeriodDropdown", 115, periods, function() return GoldLedger.Filter.period end, function(value) GoldLedger.Filter.period=value; GoldLedger.ScrollOffset=0; GoldLedger:RefreshLedger() end)
	period:SetPoint("TOPLEFT", 6, -82)
	window.PeriodDropdown = period
	local directions = function() return { {label="Income & Expenses",value="all"},{label="Income Only",value="income"},{label="Expenses Only",value="expenses"} } end
	local direction = CreateDropdown(window, "SlickUIGoldDirectionDropdown", 140, directions, function() return GoldLedger.Filter.direction end, function(value) GoldLedger.Filter.direction=value; GoldLedger.ScrollOffset=0; GoldLedger:RefreshLedger() end)
	direction:SetPoint("LEFT", period, "RIGHT", 2, 0)
	window.DirectionDropdown = direction
	local category = CreateDropdown(window, "SlickUIGoldCategoryDropdown", 155, function() return GoldLedger:GetCategories() end, function() return GoldLedger.Filter.category end, function(value) GoldLedger.Filter.category=value; GoldLedger.ScrollOffset=0; GoldLedger:RefreshLedger() end)
	category:SetPoint("LEFT", direction, "RIGHT", 2, 0)
	window.CategoryDropdown = category
	local characters = function() return { {label="Current Character",value="current"},{label="All Characters",value="all"} } end
	local character = CreateDropdown(window, "SlickUIGoldCharacterDropdown", 145, characters, function() return Settings().characterScope end, function(value)
		Settings().characterScope=value
		GoldLedger.ScrollOffset=0
		GoldLedger:RefreshLedger()
		if SlickUI.InformationBars then SlickUI.InformationBars:UpdateBlocks() end
	end)
	character:SetPoint("LEFT", category, "RIGHT", 2, 0)
	window.CharacterDropdown = character

	local start = CreateFrame("Button", nil, window, "UIPanelButtonTemplate")
	-- Anchor the action buttons to the character filter instead of the window
	-- edge. UIDropDownMenuTemplate draws beyond its nominal frame width, so a
	-- fixed right anchor can overlap its arrow and border at larger UI scales.
	start:SetSize(100, 24)
	start:SetPoint("LEFT", character, "RIGHT", 18, 0)
	start:SetScript("OnClick", function() if GoldLedger.Tracker.active then GoldLedger:StopTracker() else GoldLedger:StartTracker() end end)
	window.StartButton = start
	local tracker = CreateFrame("Button", nil, window, "UIPanelButtonTemplate")
	tracker:SetSize(120, 24)
	tracker:SetPoint("LEFT", start, "RIGHT", 8, 0)
	tracker:SetText("Show Tracker")
	tracker:SetScript("OnClick", function() GoldLedger:ShowTracker() end)

	local header = CreateFrame("Frame", nil, window, "BackdropTemplate")
	header:SetPoint("TOPLEFT", 18, -126)
	header:SetPoint("TOPRIGHT", -18, -126)
	header:SetHeight(24)
	header:SetBackdrop({bgFile=Theme.Texture})
	header:SetBackdropColor(unpack(Theme.Colors.panelSoft))
	local headers = { {"Date / Time",8}, {"Character",142}, {"Category",300}, {"Amount",530}, {"Balance",690} }
	window.HeaderTexts = {}
	for index, info in ipairs(headers) do local text=Theme:CreateText(header,10); text:SetPoint("LEFT",info[2],0); text:SetText(info[1]); text:SetTextColor(unpack(Theme.Colors.goldText)); window.HeaderTexts[index]=text end

	local list = CreateFrame("Frame", nil, window)
	list:SetPoint("TOPLEFT", header, "BOTTOMLEFT", 0, -2)
	list:SetPoint("BOTTOMRIGHT", window, "BOTTOMRIGHT", -18, 68)
	list:EnableMouseWheel(true)
	list:SetScript("OnMouseWheel", function(_, delta)
		local maximum = math.max(0, #(GoldLedger.DisplayRows or {}) - GoldLedger.VisibleRows)
		GoldLedger.ScrollOffset = math.max(0, math.min(maximum, GoldLedger.ScrollOffset - delta * 3))
		GoldLedger:RefreshRows()
	end)
	window.Rows = {}
	for index=1,self.VisibleRows do
		local row=CreateFrame("Frame",nil,list,"BackdropTemplate")
		row:SetPoint("TOPLEFT",0,-(index-1)*27)
		row:SetPoint("TOPRIGHT",0,-(index-1)*27)
		row:SetHeight(25)
		row:SetBackdrop({bgFile=Theme.Texture})
		row:SetBackdropColor(index%2==0 and 0.07 or 0.04,0.05,0.08,0.72)
		row.Fields={}
		for field,x in ipairs({8,142,300,530,690}) do local text=Theme:CreateText(row,9); text:SetPoint("LEFT",x,0); text:SetWidth(field==3 and 220 or 150); text:SetJustifyH("LEFT"); row.Fields[field]=text end
		window.Rows[index]=row
	end
	local hint=Theme:CreateText(window,9)
	hint:SetPoint("BOTTOMLEFT",18,48)
	hint:SetText("Mouse-wheel the ledger to browse. Categories are inferred from WoW events and active windows; uncertain changes remain Other.")
	hint:SetTextColor(unpack(Theme.Colors.mutedText))
	window.Hint=hint
	local showTracker=CreateFrame("CheckButton",nil,window,"UICheckButtonTemplate")
	showTracker:SetPoint("BOTTOMLEFT",14,12)
	showTracker.Text:SetText("Show the timed tracker window while tracking")
	Theme:SetFont(showTracker.Text,10)
	showTracker:SetScript("OnClick",function(self) Settings().showTracker=self:GetChecked()==true; if Settings().showTracker and GoldLedger.Tracker.active then GoldLedger:ShowTracker() elseif GoldLedger.TrackerWindow then GoldLedger.TrackerWindow:Hide() end end)
	window.ShowTracker=showTracker
	local logging=CreateFrame("CheckButton",nil,window,"UICheckButtonTemplate")
	logging:SetPoint("LEFT",showTracker,"RIGHT",315,0)
	logging.Text:SetText("Record persistent transaction history")
	Theme:SetFont(logging.Text,10)
	logging:SetScript("OnClick",function(self) Settings().enabled=self:GetChecked()==true end)
	window.Logging=logging
	self.Window=window
	return window
end

function GoldLedger:RefreshRows()
	if not self.Window then return end
	local entries=self.DisplayRows or {}
	local summaryMode=Settings().viewMode=="summary"
	for index,row in ipairs(self.Window.Rows) do
		local entry=entries[self.ScrollOffset+index]
		row:SetShown(entry~=nil)
		if entry then
			if summaryMode then
				row.Fields[1]:SetText(entry.category)
				row.Fields[2]:SetText(tostring(entry.count))
				row.Fields[3]:SetText(self:FormatMoney(entry.income,true,true))
				row.Fields[4]:SetText(self:FormatMoney(-entry.expenses,true,true))
				row.Fields[5]:SetText(self:FormatMoney(entry.net,true,true))
			else
				row.Fields[1]:SetText((entry.date or "?").." "..(entry.time or "?"))
				row.Fields[2]:SetText(entry.character or "Unknown")
				row.Fields[3]:SetText(entry.category or "Other")
				row.Fields[4]:SetText(self:FormatMoney(entry.amount,true,true))
				row.Fields[5]:SetText(self:FormatMoney(entry.balance,false,false))
			end
		end
	end
end

function GoldLedger:RefreshLedger()
	if not self.Window then return end
	self.FilteredEntries=self:GetFilteredEntries()
	local summaryMode=Settings().viewMode=="summary"
	self.DisplayRows=summaryMode and self:GetCategorySummary(self.FilteredEntries) or self.FilteredEntries
	local maximum=math.max(0,#self.DisplayRows-self.VisibleRows)
	self.ScrollOffset=math.min(self.ScrollOffset,maximum)
	local income,expenses=0,0
	for _,entry in ipairs(self.FilteredEntries) do if entry.amount>0 then income=income+entry.amount else expenses=expenses-entry.amount end end
	local scopeText=Settings().characterScope=="all" and "All Characters" or CharacterKey()
	self.Window.Summary:SetText("Scope: |cff48b8ff"..scopeText.."|r    Income: "..self:FormatMoney(income,true,true).."    Expenses: "..self:FormatMoney(-expenses,true,true).."    Net: "..self:FormatMoney(income-expenses,true,true).."    Transactions: "..#self.FilteredEntries)
	local transactionHeaders={"Date / Time","Character","Category","Amount","Balance"}
	local summaryHeaders={"Category","Transactions","Income","Expenses","Net"}
	local transactionColumns={{8,126},{142,150},{300,220},{530,150},{690,170}}
	local summaryColumns={{8,275},{300,115},{430,135},{580,125},{720,150}}
	local columns=summaryMode and summaryColumns or transactionColumns
	for index,text in ipairs(self.Window.HeaderTexts) do
		text:ClearAllPoints()
		text:SetPoint("LEFT",columns[index][1],0)
		text:SetText((summaryMode and summaryHeaders or transactionHeaders)[index])
	end
	for _,row in ipairs(self.Window.Rows) do
		for index,text in ipairs(row.Fields) do
			text:ClearAllPoints()
			text:SetPoint("LEFT",columns[index][1],0)
			text:SetWidth(columns[index][2])
		end
	end
	self.Window.ViewToggle:SetText(summaryMode and "Show Transactions" or "Show Category Summary")
	self.Window.Hint:SetText(summaryMode and "Category totals use the active period, income/expense, and category filters. Mouse-wheel to browse." or "Mouse-wheel the ledger to browse. Categories are inferred from WoW events and active windows; uncertain changes remain Other.")
	UIDropDownMenu_SetText(self.Window.PeriodDropdown, self.Filter.period=="today" and "Today" or self.Filter.period=="session" and "This Session" or "All History")
	UIDropDownMenu_SetText(self.Window.DirectionDropdown, self.Filter.direction=="income" and "Income Only" or self.Filter.direction=="expenses" and "Expenses Only" or "Income & Expenses")
	UIDropDownMenu_SetText(self.Window.CategoryDropdown, self.Filter.category=="all" and "All Categories" or self.Filter.category)
	UIDropDownMenu_SetText(self.Window.CharacterDropdown, Settings().characterScope=="all" and "All Characters" or "Current Character")
	self.Window.StartButton:SetText(self.Tracker.active and "Stop Timer" or "Start Timer")
	self.Window.ShowTracker:SetChecked(Settings().showTracker)
	self.Window.Logging:SetChecked(Settings().enabled)
	self:RefreshRows()
end

function GoldLedger:RefreshWindows()
	if self.Window and self.Window:IsShown() then self:RefreshLedger() end
	if self.BalanceWindow and self.BalanceWindow:IsShown() then self:RefreshBalanceWindow() end
	self:RefreshTracker()
end

function GoldLedger:ShowWindow()
	local window=self:CreateWindow()
	self:RefreshLedger()
	window:Show(); window:Raise()
end

function GoldLedger:ToggleWindow()
	if self.Window and self.Window:IsShown() then self.Window:Hide() else self:ShowWindow() end
end

function GoldLedger:Initialize()
	self.SessionStartedAt=GetServerTime and GetServerTime() or time()
	self.LastMoney=SafeNumber(GetMoney())
	self:UpdateBalanceCache()
	self:UpdateWarbandBalance()
	local frame=CreateFrame("Frame")
	for _,event in ipairs({"PLAYER_ENTERING_WORLD","PLAYER_MONEY","BANKFRAME_OPENED","BANKFRAME_CLOSED","CHAT_MSG_MONEY","QUEST_TURNED_IN","MERCHANT_SHOW","MERCHANT_CLOSED","TRAINER_SHOW","TRAINER_CLOSED","AUCTION_HOUSE_SHOW","AUCTION_HOUSE_CLOSED","MAIL_SHOW","MAIL_CLOSED","TRADE_SHOW","TRADE_CLOSED","LOOT_OPENED","LOOT_CLOSED"}) do frame:RegisterEvent(event) end
	frame:SetScript("OnEvent",function(_,event,...)
		if event=="PLAYER_ENTERING_WORLD" then GoldLedger.LastMoney=SafeNumber(GetMoney()); GoldLedger:UpdateBalanceCache(); GoldLedger:UpdateWarbandBalance()
		elseif event=="PLAYER_MONEY" then GoldLedger:OnMoneyChanged()
		elseif event=="BANKFRAME_OPENED" or event=="BANKFRAME_CLOSED" then GoldLedger:UpdateWarbandBalance(); GoldLedger:RefreshWindows()
		elseif event=="CHAT_MSG_MONEY" then GoldLedger:SetPendingCategory("Looted Gold",2)
		elseif event=="QUEST_TURNED_IN" then local _,_,reward=...; if SafeNumber(reward) and reward>0 then GoldLedger:SetPendingCategory("Quest Rewards",2) end
		elseif event=="MERCHANT_SHOW" then GoldLedger:SetContext("merchant") elseif event=="MERCHANT_CLOSED" then GoldLedger:ClearContext("merchant")
		elseif event=="TRAINER_SHOW" then GoldLedger:SetContext("trainer") elseif event=="TRAINER_CLOSED" then GoldLedger:ClearContext("trainer")
		elseif event=="AUCTION_HOUSE_SHOW" then GoldLedger:SetContext("auction") elseif event=="AUCTION_HOUSE_CLOSED" then GoldLedger:ClearContext("auction")
		elseif event=="MAIL_SHOW" then GoldLedger:SetContext("mail") elseif event=="MAIL_CLOSED" then GoldLedger:ClearContext("mail")
		elseif event=="TRADE_SHOW" then GoldLedger:SetContext("trade") elseif event=="TRADE_CLOSED" then GoldLedger:ClearContext("trade")
		elseif event=="LOOT_OPENED" then GoldLedger:SetContext("loot") elseif event=="LOOT_CLOSED" then GoldLedger:ClearContext("loot") end
	end)
	frame:SetScript("OnUpdate",function(_,elapsed)
		GoldLedger.UpdateElapsed=(GoldLedger.UpdateElapsed or 0)+elapsed
		if GoldLedger.UpdateElapsed>=1 then GoldLedger.UpdateElapsed=0; GoldLedger:RefreshTracker() end
	end)
	self.EventFrame=frame
	if type(RepairAllItems)=="function" then hooksecurefunc("RepairAllItems",function() GoldLedger:SetPendingCategory("Repairs",2) end) end
	if type(BuyTrainerService)=="function" then hooksecurefunc("BuyTrainerService",function() GoldLedger:SetPendingCategory("Training",3) end) end
end

SlickUI:RegisterModule("GoldLedger",GoldLedger)
