local addonName = ...
local SlickUI = _G.SlickUI
if not SlickUI then return end

local Ledger = { Rows = {}, SelectedExpansions = {} }
SlickUI.CraftingSalesLedger = Ledger

local ExpansionOrder = {
	"Midnight", "The War Within", "Dragon Isles", "Shadowlands", "Kul Tiran", "Legion", "Draenor",
	"Mists of Pandaria", "Cataclysm", "Northrend", "Outland", "Classic",
}
local ExpansionRank = {}
for index, expansion in ipairs(ExpansionOrder) do ExpansionRank[expansion] = index end

local function Settings()
	SlickUI.DB.crafting = SlickUI.DB.crafting or {}
	local settings = SlickUI.DB.crafting
	settings.salesLedger = settings.salesLedger or {}
	settings.ownedAuctionSnapshot = settings.ownedAuctionSnapshot or {}
	settings.ownedAuctionCounts = settings.ownedAuctionCounts or {}
	settings.seenAuctionMail = settings.seenAuctionMail or {}
	settings.salesLedgerPosition = settings.salesLedgerPosition or {}
	return settings
end

local function SafeCall(func, ...)
	if type(func) ~= "function" then return nil end
	local ok, result = pcall(func, ...)
	if ok then return result end
end

local function ItemName(itemID)
	return SafeCall(C_Item.GetItemNameByID, itemID) or ("Item " .. tostring(itemID))
end

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

function Ledger:GetRecord(itemID, create)
	itemID = tonumber(itemID)
	if not itemID then return nil end
	local records = Settings().salesLedger
	if create and type(records[itemID]) ~= "table" then
		records[itemID] = { itemID = itemID, name = ItemName(itemID), posts = 0, reposts = 0, expired = 0, sales = 0, quantitySold = 0, gross = 0, net = 0 }
	end
	return records[itemID]
end

function Ledger:RegisterItem(itemID, name, professionID, professionName, expansion)
	local record = self:GetRecord(itemID, true)
	if not record then return end
	record.name = name or record.name
	record.professionID = tonumber(professionID) or record.professionID
	record.professionName = professionName or record.professionName
	record.expansion = expansion or record.expansion
end

function Ledger:RecordCraft(itemID, quantity, name, professionID, professionName, expansion)
	itemID = tonumber(itemID)
	if not itemID then return end
	self:RegisterItem(itemID, name, professionID, professionName, expansion)
	local record = self:GetRecord(itemID, true)
	quantity = math.max(1, math.floor(tonumber(quantity) or 1))
	record.crafts = (tonumber(record.crafts) or 0) + 1
	record.quantityCrafted = (tonumber(record.quantityCrafted) or 0) + quantity
	record.lastCrafted = time()
	if self.Window and self.Window:IsShown() then self:Refresh() end
end

function Ledger:GetStockTarget()
	return math.max(0, math.min(99, math.floor(tonumber(Settings().stockTarget) or 3)))
end

function Ledger:GetInventoryCount(itemID)
	-- Include carried bags, character bank, reagent bank, and account bank.
	return tonumber(SafeCall(C_Item.GetItemCount, itemID, true, false, true, true)) or 0
end

function Ledger:GetAuctionCount(itemID)
	return math.max(0, tonumber(Settings().ownedAuctionCounts[tonumber(itemID)]) or 0)
end

function Ledger:GetStockInfo(itemID)
	local inventory, auctions = self:GetInventoryCount(itemID), self:GetAuctionCount(itemID)
	return inventory, auctions, inventory + auctions, self:GetStockTarget()
end

function Ledger:ScanOwnedAuctions()
	if not C_AuctionHouse or type(C_AuctionHouse.GetNumOwnedAuctions) ~= "function" or type(C_AuctionHouse.GetOwnedAuctionInfo) ~= "function" then return end
	local settings = Settings()
	local previous = settings.ownedAuctionSnapshot
	local current, counts = {}, {}
	local total = tonumber(SafeCall(C_AuctionHouse.GetNumOwnedAuctions)) or 0
	for index = 1, total do
		local info = SafeCall(C_AuctionHouse.GetOwnedAuctionInfo, index)
		local itemID = info and info.itemKey and tonumber(info.itemKey.itemID)
		local auctionID = info and tostring(info.auctionID or "")
		if itemID and auctionID ~= "" then
			local quantity = math.max(1, tonumber(info.quantity) or 1)
			current[auctionID] = { itemID = itemID, quantity = quantity, seenAt = time() }
			counts[itemID] = (counts[itemID] or 0) + quantity
			if settings.ownedAuctionInitialized and not previous[auctionID] then
				local record = self:GetRecord(itemID, true)
				record.posts = (tonumber(record.posts) or 0) + 1
				record.quantityPosted = (tonumber(record.quantityPosted) or 0) + quantity
				record.lastPosted = time()
				if (tonumber(record.expired) or 0) > (tonumber(record.reposts) or 0) then
					record.reposts = (tonumber(record.reposts) or 0) + 1
				end
			end
		end
	end
	settings.ownedAuctionInitialized = true
	settings.ownedAuctionSnapshot = current
	settings.ownedAuctionCounts = counts
	settings.ownedAuctionUpdated = time()
	if self.Window and self.Window:IsShown() then self:Refresh() end
	local planner = SlickUI.CraftingPlanner
	if planner and planner.DashboardWindow and planner.DashboardWindow:IsShown() then planner:RefreshDashboardRows() end
end

local function MailSignature(index, subject, money, daysLeft, itemID, quantity)
	local expires = math.floor((time() + (tonumber(daysLeft) or 0) * 86400) / 300 + 0.5)
	return table.concat({ CharacterKey(), tostring(subject or ""), tostring(money or 0), tostring(expires), tostring(itemID or 0), tostring(quantity or 0) }, "|")
end

function Ledger:ScanAuctionMail()
	if type(GetInboxNumItems) ~= "function" or type(GetInboxHeaderInfo) ~= "function" then return end
	local settings = Settings()
	local count = GetInboxNumItems()
	count = tonumber(count) or 0
	for index = 1, count do
		local _, _, sender, subject, money, _, daysLeft, attachments = GetInboxHeaderInfo(index)
		if issecretvalue and (issecretvalue(subject) or issecretvalue(money) or issecretvalue(daysLeft) or issecretvalue(attachments)) then subject, money, daysLeft, attachments = "", 0, 0, 0 end
		local lowered = string.lower(tostring(subject or ""))
		local isExpired = string.find(lowered, "auction expired", 1, true) ~= nil
		local invoiceType, itemName, _, _, buyout, deposit, consignment, _, _, invoiceCount
		if type(GetInboxInvoiceInfo) == "function" then
			local ok
			ok, invoiceType, itemName, _, _, buyout, deposit, consignment, _, _, invoiceCount = pcall(GetInboxInvoiceInfo, index)
			if not ok or (issecretvalue and (issecretvalue(invoiceType) or issecretvalue(itemName))) then invoiceType, itemName = nil, nil end
		end
		if invoiceType == "seller" and itemName then
			local itemID
			for id, record in pairs(settings.salesLedger) do if record.name == itemName then itemID = tonumber(id); break end end
			local signature = MailSignature(index, subject, money, daysLeft, itemID, invoiceCount)
			if itemID and not settings.seenAuctionMail[signature] then
				settings.seenAuctionMail[signature] = time()
				local record = self:GetRecord(itemID, true)
				local quantity = math.max(1, tonumber(invoiceCount) or 1)
				record.sales = (tonumber(record.sales) or 0) + 1
				record.quantitySold = (tonumber(record.quantitySold) or 0) + quantity
				record.gross = (tonumber(record.gross) or 0) + (tonumber(buyout) or tonumber(money) or 0)
				record.net = (tonumber(record.net) or 0) + math.max(0, (tonumber(buyout) or tonumber(money) or 0) - (tonumber(consignment) or 0))
				record.lastSold = time()
			end
		elseif isExpired and (tonumber(attachments) or 0) > 0 and type(GetInboxItemLink) == "function" then
			for attachment = 1, attachments do
				local link = GetInboxItemLink(index, attachment)
				local itemID = link and tonumber(string.match(link, "item:(%d+)"))
				local quantity
				if type(GetInboxItem) == "function" then
					local _, _, _, attachmentCount = GetInboxItem(index, attachment)
					quantity = attachmentCount
				end
				local signature = MailSignature(index, subject, money, daysLeft, itemID, quantity)
				if itemID and not settings.seenAuctionMail[signature] then
					settings.seenAuctionMail[signature] = time()
					local record = self:GetRecord(itemID, true)
					record.expired = (tonumber(record.expired) or 0) + 1
					record.lastExpired = time()
				end
			end
		end
	end
end

function Ledger:GetVisibleRecords()
	local records = {}
	for _, record in pairs(Settings().salesLedger) do
		if type(record) == "table" and ((tonumber(record.quantityCrafted) or 0) > 0 or (tonumber(record.posts) or 0) > 0 or (tonumber(record.sales) or 0) > 0 or self:GetAuctionCount(record.itemID) > 0) then
			local professionMatches = not self.ProfessionID or tonumber(record.professionID) == tonumber(self.ProfessionID)
			local expansionMatches = not next(self.SelectedExpansions) or self.SelectedExpansions[record.expansion or "Classic"]
			if professionMatches and expansionMatches then records[#records + 1] = record end
		end
	end
	table.sort(records, function(a, b) return (tonumber(a.posts) or 0) > (tonumber(b.posts) or 0) or ((tonumber(a.posts) or 0) == (tonumber(b.posts) or 0) and tostring(a.name) < tostring(b.name)) end)
	return records
end

function Ledger:Refresh()
	if not self.Window then return end
	local records = self:GetVisibleRecords()
	local maximum = math.max(0, #records - #self.Rows)
	self.Offset = math.max(0, math.min(maximum, tonumber(self.Offset) or 0))
	for index, row in ipairs(self.Rows) do
		local record = records[self.Offset + index]
		if record then
			local inventory, auctions = self:GetStockInfo(record.itemID)
			row.Icon:SetTexture(SafeCall(C_Item.GetItemIconByID, record.itemID) or 134400)
			row.Name:SetText(record.name or ItemName(record.itemID))
			row.Crafted:SetText(tonumber(record.quantityCrafted) or 0)
			row.Posts:SetText(tonumber(record.posts) or 0)
			row.Reposts:SetText(tonumber(record.reposts) or 0)
			row.Sold:SetText(tonumber(record.quantitySold) or 0)
			row.Stock:SetText(inventory)
			row.Auctions:SetText(auctions)
			row.Net:SetText(GetMoneyString and GetMoneyString(tonumber(record.net) or 0, true) or tostring(record.net or 0))
			row.Record = record
			row:Show()
		else row.Record = nil; row:Hide() end
	end
	self.Window.Summary:SetText(string.format("Showing %d-%d of %d tracked items", math.min(#records, self.Offset + 1), math.min(#records, self.Offset + #self.Rows), #records))
end

function Ledger:OpenExpansionMenu(owner)
	if not MenuUtil or type(MenuUtil.CreateContextMenu) ~= "function" then return end
	local found = {}
	for _, record in pairs(Settings().salesLedger) do if record.expansion then found[record.expansion] = true end end
	local expansions = {}
	for expansion in pairs(found) do expansions[#expansions + 1] = expansion end
	table.sort(expansions, function(a, b)
		local aRank, bRank = ExpansionRank[a] or math.huge, ExpansionRank[b] or math.huge
		if aRank ~= bRank then return aRank < bRank end
		return tostring(a) < tostring(b)
	end)
	MenuUtil.CreateContextMenu(owner, function(_, root)
		root:SetTag("SLICKUI_CRAFTING_LEDGER_EXPANSIONS")
		root:CreateTitle("Select Expansions")
		root:CreateCheckbox("All Expansions", function() return not next(Ledger.SelectedExpansions) end, function() wipe(Ledger.SelectedExpansions); Ledger.Offset=0; owner:SetText("All Expansions"); Ledger:Refresh() end)
		for _, expansion in ipairs(expansions) do
			local value = expansion
			root:CreateCheckbox(value, function() return Ledger.SelectedExpansions[value] == true end, function()
				Ledger.SelectedExpansions[value] = Ledger.SelectedExpansions[value] and nil or true
				local total = 0; for _ in pairs(Ledger.SelectedExpansions) do total = total + 1 end
				Ledger.Offset=0; owner:SetText(total == 0 and "All Expansions" or total == 1 and value or (total .. " Expansions")); Ledger:Refresh()
			end)
		end
	end)
end

function Ledger:CreateWindow()
	if self.Window then return end
	local window = CreateFrame("Frame", "SlickUICraftingSalesLedger", UIParent, "BackdropTemplate")
	window:SetSize(820, 620)
	local savedPosition = Settings().salesLedgerPosition
	window:SetPoint("CENTER", UIParent, "CENTER", tonumber(savedPosition.x) or 0, tonumber(savedPosition.y) or 0)
	window:SetFrameStrata("HIGH"); window:SetToplevel(true)
	window:SetMovable(true); window:EnableMouse(true); window:SetClampedToScreen(true); window:RegisterForDrag("LeftButton")
	window:SetScript("OnDragStart", window.StartMoving)
	window:SetScript("OnDragStop", function(self)
		self:StopMovingOrSizing()
		local centerX, centerY = self:GetCenter()
		local parentX, parentY = UIParent:GetCenter()
		if centerX and centerY and parentX and parentY then
			local position = Settings().salesLedgerPosition
			position.x = centerX - parentX
			position.y = centerY - parentY
			self:ClearAllPoints()
			self:SetPoint("CENTER", UIParent, "CENTER", position.x, position.y)
		end
	end)
	window:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1 }); window:SetBackdropColor(0.015, 0.018, 0.025, 0.97); window:SetBackdropBorderColor(1, 0.58, 0, 1)
	local logo = window:CreateTexture(nil, "ARTWORK"); logo:SetSize(44, 44); logo:SetPoint("TOPLEFT", 10, -8); logo:SetTexture(SlickUI.Theme.Icon)
	local title = window:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge"); title:SetPoint("TOPLEFT", 62, -16); title:SetText("SlickUI Crafting Sales Ledger")
	local close = CreateFrame("Button", nil, window, "UIPanelCloseButton"); close:SetPoint("TOPRIGHT", -3, -3)
	local profession = CreateFrame("Button", nil, window, "UIPanelButtonTemplate"); profession:SetSize(170, 24); profession:SetPoint("TOPLEFT", 14, -58); profession:SetText("All Professions"); window.Profession = profession
	profession:SetScript("OnClick", function(owner)
		if not MenuUtil then return end
		local found = {}; for _, record in pairs(Settings().salesLedger) do if record.professionID then found[record.professionID] = record.professionName or "Profession" end end
		MenuUtil.CreateContextMenu(owner, function(_, root)
			root:SetTag("SLICKUI_CRAFTING_LEDGER_PROFESSIONS"); root:CreateTitle("Select Profession")
			root:CreateRadio("All Professions", function() return not Ledger.ProfessionID end, function() Ledger.ProfessionID=nil; Ledger.Offset=0; owner:SetText("All Professions"); Ledger:Refresh() end)
			for id, name in pairs(found) do local selectedID, selectedName=id,name; root:CreateRadio(selectedName, function() return Ledger.ProfessionID==selectedID end, function() Ledger.ProfessionID=selectedID; Ledger.Offset=0; owner:SetText(selectedName); Ledger:Refresh() end) end
		end)
	end)
	local expansion = CreateFrame("Button", nil, window, "UIPanelButtonTemplate"); expansion:SetSize(150, 24); expansion:SetPoint("LEFT", profession, "RIGHT", 8, 0); expansion:SetText("All Expansions"); expansion:SetScript("OnClick", function(owner) Ledger:OpenExpansionMenu(owner) end)
	local header = CreateFrame("Frame", nil, window); header:SetPoint("TOPLEFT", 14, -96); header:SetPoint("TOPRIGHT", -14, -96); header:SetHeight(24)
	local columns={{"Item",0,240},{"Crafted",244,65},{"Posts",312,52},{"Reposts",367,65},{"Sold",435,55},{"Bags/Bank",493,75},{"At AH",571,55},{"Net Sales",629,160}}
	for _, column in ipairs(columns) do local button=CreateFrame("Button",nil,header,"UIPanelButtonTemplate"); button:SetPoint("LEFT",column[2],0); button:SetSize(column[3],22); button:SetText(column[1]) end
	local function Field(row,x,width,justify) local text=row:CreateFontString(nil,"ARTWORK","GameFontHighlightSmall"); text:SetPoint("LEFT",x,0); text:SetWidth(width); text:SetJustifyH(justify or "LEFT"); return text end
	for index=1,16 do
		local row=CreateFrame("Frame",nil,window); row:SetPoint("TOPLEFT",14,-124-(index-1)*28); row:SetPoint("TOPRIGHT",-14,-124-(index-1)*28); row:SetHeight(27)
		row:EnableMouse(true)
		row:SetScript("OnEnter", function(self)
			local record = self.Record
			if not record then return end
			GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
			local link = SafeCall(C_Item.GetItemLinkByID, record.itemID)
			if link then GameTooltip:SetHyperlink(link) else GameTooltip:SetText(record.name or ItemName(record.itemID)) end
			GameTooltip:AddLine(" ")
			GameTooltip:AddDoubleLine("Quantity crafted", tostring(tonumber(record.quantityCrafted) or 0), 1, 1, 1, 1, 0.82, 0)
			GameTooltip:AddDoubleLine("Posts", tostring(tonumber(record.posts) or 0), 1, 1, 1, 1, 0.82, 0)
			GameTooltip:AddDoubleLine("Confirmed reposts", tostring(tonumber(record.reposts) or 0), 1, 1, 1, 1, 0.82, 0)
			GameTooltip:AddDoubleLine("Quantity sold", tostring(tonumber(record.quantitySold) or 0), 1, 1, 1, 1, 0.82, 0)
			GameTooltip:Show()
		end)
		row:SetScript("OnLeave", function() GameTooltip:Hide() end)
		if index%2==0 then local bg=row:CreateTexture(nil,"BACKGROUND"); bg:SetAllPoints(); bg:SetColorTexture(0.08,0.09,0.12,0.55) end
		row.Icon=row:CreateTexture(nil,"ARTWORK"); row.Icon:SetSize(24,24); row.Icon:SetPoint("LEFT",3,0); row.Name=Field(row,32,208); row.Crafted=Field(row,244,65,"RIGHT"); row.Posts=Field(row,312,52,"RIGHT"); row.Reposts=Field(row,367,65,"RIGHT"); row.Sold=Field(row,435,55,"RIGHT"); row.Stock=Field(row,493,75,"RIGHT"); row.Auctions=Field(row,571,55,"RIGHT"); row.Net=Field(row,629,160,"RIGHT"); self.Rows[index]=row
	end
	local summary=window:CreateFontString(nil,"ARTWORK","GameFontHighlightSmall"); summary:SetPoint("BOTTOM",0,18); window.Summary=summary
	window:EnableMouseWheel(true); window:SetScript("OnMouseWheel", function(_, delta) Ledger.Offset=(Ledger.Offset or 0)-(delta>0 and 3 or -3); Ledger:Refresh() end)
	window:Hide(); self.Window=window
	if SlickUI.Theme then SlickUI.Theme:RegisterWindowFontControl(window) end
end

function Ledger:Toggle(professionID, professionName)
	self:CreateWindow()
	if self.Window:IsShown() then self.Window:Hide(); return end
	self.ProfessionID = tonumber(professionID) or self.ProfessionID
	if professionName and self.ProfessionID then self.Window.Profession:SetText(professionName) end
	self.Window:Show(); self:Refresh()
end

local events = CreateFrame("Frame")
events:RegisterEvent("OWNED_AUCTIONS_UPDATED")
events:RegisterEvent("AUCTION_HOUSE_AUCTION_CREATED")
events:RegisterEvent("AUCTION_HOUSE_SHOW")
events:RegisterEvent("MAIL_INBOX_UPDATE")
events:SetScript("OnEvent", function(_, event)
	if event == "MAIL_INBOX_UPDATE" then Ledger:ScanAuctionMail() else C_Timer.After(event == "AUCTION_HOUSE_SHOW" and 1 or 0.1, function() Ledger:ScanOwnedAuctions() end) end
end)
