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

local Bank = {
	Buttons = {},
	TabButtons = {},
	BankType = nil,
	SelectedTabID = nil,
	Sorting = false,
	SectionLabels = {},
}
SlickUI.Bank = Bank

StaticPopupDialogs.SLICKUI_NATIVE_BANK_MANAGEMENT = {
	text = "Bank Tab Management\n\nWoW requires the protected Blizzard bank interface to purchase bank tabs. SlickUI will reload the interface and temporarily use Blizzard's bank.\n\nAfter reloading, open the banker and click the bank-tab purchase button again. SlickUI Bank will return after you close the banker.",
	button1 = "Reload and Continue",
	button2 = CANCEL,
	OnAccept = function()
		SlickUI.DB.bags.nativeBankManagement = true
		ReloadUI()
	end,
	timeout = 0,
	whileDead = true,
	hideOnEscape = true,
	preferredIndex = 3,
}

local function CapacityColor(used, total)
	if total <= 0 then return "8f98a8" end
	local fullness = used / total
	if fullness >= 0.90 then return "ef5350" end
	if fullness >= 0.75 then return "ffd54f" end
	return "4fd17b"
end

local function GetTabs(bankType)
	if not C_Bank or not C_Bank.FetchPurchasedBankTabData then return {} end
	local ok, tabs = pcall(C_Bank.FetchPurchasedBankTabData, bankType)
	return ok and type(tabs) == "table" and tabs or {}
end

local function GetTabCapacity(tabID)
	local total = C_Container.GetContainerNumSlots(tabID) or 0
	local free = C_Container.GetContainerNumFreeSlots(tabID) or 0
	return math.max(0, total - free), total
end

local CATEGORY_ORDER = { "Quest", "Consumables", "Equipment", "Trade Goods", "Gems", "Recipes", "Containers", "Battle Pets", "Junk", "Miscellaneous", "Empty Slots" }
local CATEGORY_BY_CLASS = { [0] = "Consumables", [1] = "Containers", [2] = "Equipment", [3] = "Gems", [4] = "Equipment", [7] = "Trade Goods", [9] = "Recipes", [12] = "Quest", [17] = "Battle Pets", [19] = "Trade Goods" }

local function GetItemCategory(info)
	if not info then return "Empty Slots" end
	if info.quality == 0 then return "Junk" end
	local classID = select(12, C_Item.GetItemInfo(info.itemID))
	return CATEGORY_BY_CLASS[classID] or "Miscellaneous"
end

local function HideNativeBank()
	if not BankFrame then return end
	BankFrame:SetAlpha(0)
	BankFrame:EnableMouse(false)
end

local function RestoreNativeBank()
	if not BankFrame then return end
	BankFrame:SetAlpha(1)
	BankFrame:EnableMouse(true)
end

function Bank:GetButton(index)
	local button = self.Buttons[index]
	if button then return button end
	button = CreateFrame("ItemButton", "SlickUIBankItem" .. index, self.Content, "BankItemButtonTemplate")
	self.Buttons[index] = button
	return button
end

function Bank:GetSectionLabel(index)
	local label = self.SectionLabels[index]
	if label then return label end
	label = Theme:CreateText(self.Content, 12)
	label:SetTextColor(unpack(Theme.Colors.goldText))
	label:SetJustifyH("LEFT")
	self.SectionLabels[index] = label
	return label
end

function Bank:UpdateCapacity()
	local used, total = 0, 0
	for _, tab in ipairs(GetTabs(self.BankType)) do
		local tabUsed, tabTotal = GetTabCapacity(tab.ID)
		used, total = used + tabUsed, total + tabTotal
	end
	local label = self.BankType == Enum.BankType.Account and "Warband Space" or "Bank Space"
	self.Capacity:SetFormattedText("%s: |cff%s%d/%d|r", label, CapacityColor(used, total), used, total)
end

function Bank:RefreshTabs()
	local tabs = GetTabs(self.BankType)
	if not self.SelectedTabID or not self:GetTabData(self.SelectedTabID, tabs) then
		self.SelectedTabID = tabs[1] and tabs[1].ID or nil
	end
	for index, tab in ipairs(tabs) do
		local button = self.TabButtons[index]
		if not button then
			button = CreateFrame("Button", nil, self.TabRail, "BackdropTemplate")
			button:SetSize(42, 42)
			button:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
			button.Icon = button:CreateTexture(nil, "ARTWORK")
			button.Icon:SetPoint("TOPLEFT", 4, -4)
			button.Icon:SetPoint("BOTTOMRIGHT", -4, 4)
			button:SetScript("OnClick", function(self) Bank:SelectTab(self.TabID) end)
			button:SetScript("OnEnter", function(self)
				GameTooltip:SetOwner(self, "ANCHOR_LEFT")
				GameTooltip:SetText(self.TabName or "Bank Tab")
				GameTooltip:Show()
			end)
			button:SetScript("OnLeave", GameTooltip_Hide)
			self.TabButtons[index] = button
		end
		button:ClearAllPoints()
		button:SetPoint("TOP", self.TabRail, "TOP", 0, -10 - (index - 1) * 48)
		button.TabID, button.TabName = tab.ID, tab.name
		button.Icon:SetTexture(tab.icon)
		button:SetBackdropColor(0.04, 0.05, 0.07, 0.96)
		button:SetBackdropBorderColor(unpack(tab.ID == self.SelectedTabID and Theme.Colors.border or Theme.Colors.borderMuted))
		button:Show()
	end
	for index = #tabs + 1, #self.TabButtons do self.TabButtons[index]:Hide() end
	local canPurchase = C_Bank.CanPurchaseBankTab(self.BankType)
	self.PurchaseTab:SetPoint("TOP", self.TabRail, "TOP", 0, -10 - #tabs * 48)
	self.PurchaseTab:SetShown(canPurchase)
	self.TabRail:SetHeight(math.max(58, (#tabs + (canPurchase and 1 or 0)) * 48 + 16))
end

function Bank:GetTabData(tabID, tabs)
	for _, tab in ipairs(tabs or GetTabs(self.BankType)) do
		if tab.ID == tabID then return tab end
	end
end

function Bank:SelectTab(tabID)
	self.SelectedTabID = tabID
	SlickUI.DB.bags.bankMode = "tab"
	self:Refresh()
end

function Bank:SetMode(mode)
	if mode ~= "single" and mode ~= "categories" then return end
	SlickUI.DB.bags.bankMode = mode
	self:Refresh()
end

function Bank:CollectSlots()
	local slots = {}
	local query = self.SearchBox and self.SearchBox:GetText():lower() or ""
	local tabs = GetTabs(self.BankType)
	if SlickUI.DB.bags.bankMode == "tab" then
		tabs = self.SelectedTabID and { { ID = self.SelectedTabID } } or {}
	end
	for _, tab in ipairs(tabs) do
		local total = C_Container.GetContainerNumSlots(tab.ID) or 0
		for slotID = 1, total do
			local info = C_Container.GetContainerItemInfo(tab.ID, slotID)
			local category = GetItemCategory(info)
			if query == "" or (info and info.itemName and info.itemName:lower():find(query, 1, true)) or category:lower():find(query, 1, true) then
				table.insert(slots, { tabID = tab.ID, slotID = slotID, info = info, category = category })
			end
		end
	end
	return slots
end

function Bank:PlaceButton(button, slot, index, columns, size, spacing, topOffset)
	local zero = index - 1
	button:ClearAllPoints()
	button:SetSize(size, size)
	button:SetPoint("TOPLEFT", self.Content, "TOPLEFT", (zero % columns) * (size + spacing), -(topOffset + math.floor(zero / columns) * (size + spacing)))
	button:Init(self.BankType, slot.tabID, slot.slotID)
	button:Show()
end

function Bank:Refresh()
	if not self.Frame or not self.Frame:IsShown() or self.Sorting then return end
	HideNativeBank()
	self:RefreshTabs()
	for _, button in ipairs(self.Buttons) do button:Hide() end
	for _, label in ipairs(self.SectionLabels) do label:Hide() end
	local tab = self:GetTabData(self.SelectedTabID)
	local mode = SlickUI.DB.bags.bankMode or "single"
	self.TabTitle:SetText(mode == "categories" and "Category View" or mode == "tab" and (tab and tab.name or "Bank Tab") or "Single Bank")
	self.SingleButton:SetEnabled(mode ~= "single")
	self.CategoriesButton:SetEnabled(mode ~= "categories")
	local slots = self:CollectSlots()
	local size = SlickUI.DB.bags.buttonSize
	local spacing = SlickUI.DB.bags.spacing
	local columns = SlickUI.DB.bags.columns
	local height = 0
	if mode == "categories" then
		local grouped = {}
		for _, category in ipairs(CATEGORY_ORDER) do grouped[category] = {} end
		for _, slot in ipairs(slots) do table.insert(grouped[slot.category], slot) end
		local buttonIndex, labelIndex = 0, 0
		for _, category in ipairs(CATEGORY_ORDER) do
			local group = grouped[category]
			if #group > 0 then
				labelIndex = labelIndex + 1
				local label = self:GetSectionLabel(labelIndex)
				label:ClearAllPoints()
				label:SetPoint("TOPLEFT", self.Content, "TOPLEFT", 0, -height)
				label:SetText(category .. "  |cff8f98a8" .. #group .. "|r")
				label:Show()
				height = height + 22
				for index, slot in ipairs(group) do buttonIndex = buttonIndex + 1; self:PlaceButton(self:GetButton(buttonIndex), slot, index, columns, size, spacing, height) end
				height = height + math.ceil(#group / columns) * (size + spacing) + 12
			end
		end
	else
		for index, slot in ipairs(slots) do self:PlaceButton(self:GetButton(index), slot, index, columns, size, spacing, 0) end
		height = math.max(100, math.ceil(#slots / columns) * (size + spacing))
	end
	local width = columns * (size + spacing)
	self.Content:SetSize(math.max(100, width), math.max(100, height))
	self.Frame:SetWidth(math.max(520, math.min(1180, width + 92)))
	self:UpdateCapacity()
end

function Bank:SetType(bankType)
	if not C_Bank.CanViewBank(bankType) then return end
	self.BankType = bankType
	self.SelectedTabID = nil
	self.CharacterButton:SetEnabled(bankType ~= Enum.BankType.Character)
	self.AccountButton:SetEnabled(bankType ~= Enum.BankType.Account)
	self.CharacterButton:SetShown(C_Bank.CanViewBank(Enum.BankType.Character))
	self.AccountButton:SetShown(C_Bank.CanViewBank(Enum.BankType.Account))
	self:Refresh()
end

function Bank:FinishSorting()
	if not self.Sorting then return end
	self.Sorting = false
	self.SortButton:Enable()
	self.SortButton:SetText("Sort Bank")
	self.SortOverlay:SetScript("OnUpdate", nil)
	self.SortOverlay:Hide()
	self.Content:Show()
	self:Refresh()
end

function Bank:StartSorting()
	if self.Sorting or not self.BankType then return end
	self.Sorting = true
	self.SortButton:Disable()
	self.SortButton:SetText("Sorting...")
	self.Content:Hide()
	self.SortOverlay.Elapsed = 0
	self.SortOverlay:SetScript("OnUpdate", function(overlay, elapsed)
		overlay.Elapsed = (overlay.Elapsed + elapsed) % 1.5
		overlay.Progress:SetValue(overlay.Elapsed / 1.5 * 100)
	end)
	self.SortOverlay:Show()
	local ok = pcall(C_Container.SortBank, self.BankType)
	if not ok then self:FinishSorting() return end
	C_Timer.After(20, function() if Bank.Sorting then Bank:FinishSorting() end end)
end

function Bank:SetRailCollapsed(collapsed)
	SlickUI.DB.bags.bankRailCollapsed = collapsed == true
	self.TabRail:SetShown(not collapsed)
	self.RailToggle:SetText(collapsed and ">" or "<")
end

function Bank:Show()
	if not SlickUI.DB.bags.bankEnabled then return end
	if SlickUI.DB.bags.nativeBankManagement then
		self.NativeBankManagementStarted = true
		RestoreNativeBank()
		if self.Frame then self.Frame:Hide() end
		return
	end
	if not BankPanelItemButtonMixin then
		RestoreNativeBank()
		SlickUI:Print("SlickUI Bank is waiting for Blizzard's bank interface to load.")
		return
	end
	HideNativeBank()
	if SlickUI.DB.bags.enabled and SlickUI.Bags then
		if not self.Frame:IsShown() then
			self.BagsWereShown = SlickUI.Bags.Frame and SlickUI.Bags.Frame:IsShown() or false
			self.OpenedBagsForBank = not self.BagsWereShown
		end
		SlickUI.Bags:Show()
	elseif SlickUI.Bags and SlickUI.Bags.HideBlizzardBags then
		-- Blizzard opens the character bags alongside the bank. Keep those
		-- suppressed even when only the SlickUI bank replacement is enabled.
		SlickUI.Bags:HideBlizzardBags()
	end
	C_Timer.After(0, function()
		if Bank.Frame and Bank.Frame:IsShown() and SlickUI.Bags and SlickUI.Bags.HideBlizzardBags then
			SlickUI.Bags:HideBlizzardBags()
		end
	end)
	local settings = SlickUI.DB.bags
	local position = settings.bankPosition or {}
	self.Frame:SetScale(settings.scale or 1)
	self.Frame:ClearAllPoints()
	self.Frame:SetPoint(position.point or "CENTER", UIParent, position.relativePoint or "CENTER", tonumber(position.x) or 100, tonumber(position.y) or 10)
	self.Frame:Show()
	if not self.BankType then
		self.BankType = C_Bank.CanViewBank(Enum.BankType.Character) and Enum.BankType.Character or Enum.BankType.Account
	end
	self:SetType(self.BankType)
end

function Bank:Hide(closeBank)
	if self.Frame then self.Frame:Hide() end
	if self.OpenedBagsForBank and SlickUI.Bags then SlickUI.Bags:Hide() end
	self.OpenedBagsForBank = false
	self.BagsWereShown = false
	RestoreNativeBank()
	if closeBank and C_Bank and C_Bank.CloseBankFrame then pcall(C_Bank.CloseBankFrame) end
end

function Bank:CreateFrame()
	local frame = CreateFrame("Frame", "SlickUIBankFrame", UIParent, "BackdropTemplate")
	frame:SetSize(680, 590)
	frame:SetPoint("CENTER", UIParent, "CENTER", 100, 10)
	frame:SetFrameStrata("HIGH")
	frame:SetClampedToScreen(true)
	frame:SetMovable(true)
	frame:EnableMouse(true)
	frame:RegisterForDrag("LeftButton")
	frame:SetScript("OnDragStart", function(self) if not InCombatLockdown() then self:StartMoving() end end)
	frame:SetScript("OnDragStop", function(self)
		self:StopMovingOrSizing()
		local point, _, relativePoint, x, y = self:GetPoint(1)
		SlickUI.DB.bags.bankPosition = {
			point = point or "CENTER", relativePoint = relativePoint or "CENTER",
			x = math.floor((x or 0) + 0.5), y = math.floor((y or 0) + 0.5),
		}
	end)
	frame:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
	frame:SetBackdropColor(0.025, 0.03, 0.045, 0.97)
	frame:SetBackdropBorderColor(unpack(Theme.Colors.border))
	frame:Hide()
	self.Frame = frame

	local title = Theme:CreateText(frame, 18)
	title:SetPoint("TOPLEFT", 18, -16)
	title:SetText("SlickUI Bank")
	title:SetTextColor(unpack(Theme.Colors.goldText))
	local tabTitle = Theme:CreateText(frame, 11)
	tabTitle:SetPoint("LEFT", title, "RIGHT", 12, 0)
	tabTitle:SetTextColor(unpack(Theme.Colors.mutedText))
	self.TabTitle = tabTitle
	local capacity = Theme:CreateText(frame, 10)
	capacity:SetPoint("TOP", frame, "TOP", 70, -18)
	capacity:SetTextColor(unpack(Theme.Colors.mutedText))
	self.Capacity = capacity

	local close = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
	close:SetPoint("TOPRIGHT", -5, -5)
	close:SetScript("OnClick", function() Bank:Hide(true) end)
	local character = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	character:SetSize(110, 22)
	character:SetPoint("TOPLEFT", 18, -48)
	character:SetText("Character Bank")
	character:SetScript("OnClick", function() Bank:SetType(Enum.BankType.Character) end)
	self.CharacterButton = character
	local account = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	account:SetSize(110, 22)
	account:SetPoint("LEFT", character, "RIGHT", 8, 0)
	account:SetText("Warband Bank")
	account:SetScript("OnClick", function() Bank:SetType(Enum.BankType.Account) end)
	self.AccountButton = account
	local single = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	single:SetSize(100, 22)
	single:SetPoint("TOPLEFT", character, "BOTTOMLEFT", 0, -8)
	single:SetText("Single Bank")
	single:SetScript("OnClick", function() Bank:SetMode("single") end)
	self.SingleButton = single
	local categories = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	categories:SetSize(100, 22)
	categories:SetPoint("LEFT", single, "RIGHT", 8, 0)
	categories:SetText("Categories")
	categories:SetScript("OnClick", function() Bank:SetMode("categories") end)
	self.CategoriesButton = categories
	local sort = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	sort:SetSize(90, 22)
	sort:SetPoint("TOPRIGHT", -46, -78)
	sort:SetText("Sort Bank")
	sort:SetScript("OnClick", function() Bank:StartSorting() end)
	self.SortButton = sort
	local search = CreateFrame("EditBox", nil, frame, "SearchBoxTemplate")
	search:SetSize(180, 22)
	search:SetPoint("RIGHT", sort, "LEFT", -10, 0)
	search:SetAutoFocus(false)
	search:HookScript("OnTextChanged", function() Bank:Refresh() end)
	self.SearchBox = search

	local scroll = CreateFrame("ScrollFrame", "SlickUIBankScrollFrame", frame, "UIPanelScrollFrameTemplate")
	scroll:SetPoint("TOPLEFT", 18, -112)
	scroll:SetPoint("BOTTOMRIGHT", -36, 18)
	local content = CreateFrame("Frame", nil, scroll)
	content:SetSize(600, 480)
	scroll:SetScrollChild(content)
	self.Content = content

	local rail = CreateFrame("Frame", nil, frame, "BackdropTemplate")
	rail:SetWidth(58)
	rail:SetPoint("TOPRIGHT", frame, "TOPLEFT", -4, -112)
	rail:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
	rail:SetBackdropColor(0.025, 0.03, 0.045, 0.97)
	rail:SetBackdropBorderColor(unpack(Theme.Colors.border))
	self.TabRail = rail
	local purchaseTab = CreateFrame("Button", nil, rail, "BackdropTemplate")
	purchaseTab:SetSize(42, 42)
	purchaseTab:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
	purchaseTab:SetBackdropColor(0.04, 0.05, 0.07, 0.96)
	purchaseTab:SetBackdropBorderColor(unpack(Theme.Colors.border))
	local purchaseText = Theme:CreateText(purchaseTab, 22)
	purchaseText:SetPoint("CENTER")
	purchaseText:SetText("+")
	purchaseText:SetTextColor(unpack(Theme.Colors.goldText))
	purchaseTab:SetScript("OnClick", function()
		StaticPopup_Show("SLICKUI_NATIVE_BANK_MANAGEMENT")
	end)
	purchaseTab:SetScript("OnEnter", function(self)
		GameTooltip:SetOwner(self, "ANCHOR_LEFT")
		GameTooltip:SetText("Manage Bank Tabs")
		GameTooltip:AddLine("Reload into Blizzard's bank interface to safely purchase or configure tabs.", 0.68, 0.71, 0.78, true)
		GameTooltip:Show()
	end)
	purchaseTab:SetScript("OnLeave", GameTooltip_Hide)
	self.PurchaseTab = purchaseTab
	local railToggle = CreateFrame("Button", nil, frame, "BackdropTemplate")
	railToggle:SetSize(26, 52)
	railToggle:SetPoint("BOTTOMRIGHT", rail, "TOPRIGHT", 0, 2)
	railToggle:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
	railToggle:SetBackdropColor(0.025, 0.03, 0.045, 0.97)
	railToggle:SetBackdropBorderColor(unpack(Theme.Colors.border))
	local railText = Theme:CreateText(railToggle, 16)
	railText:SetPoint("CENTER")
	railToggle:SetFontString(railText)
	railToggle:SetScript("OnClick", function() Bank:SetRailCollapsed(not SlickUI.DB.bags.bankRailCollapsed) end)
	self.RailToggle = railToggle

	local overlay = CreateFrame("Frame", nil, frame, "BackdropTemplate")
	overlay:SetPoint("TOPLEFT", scroll)
	overlay:SetPoint("BOTTOMRIGHT", scroll)
	overlay:SetFrameLevel(scroll:GetFrameLevel() + 10)
	overlay:SetBackdrop({ bgFile = Theme.Texture })
	overlay:SetBackdropColor(0.025, 0.03, 0.045, 0.92)
	overlay:Hide()
	local sortingText = Theme:CreateText(overlay, 16)
	sortingText:SetPoint("CENTER", 0, 20)
	sortingText:SetText("Sorting bank...")
	sortingText:SetTextColor(unpack(Theme.Colors.goldText))
	local progress = CreateFrame("StatusBar", nil, overlay, "BackdropTemplate")
	progress:SetSize(280, 16)
	progress:SetPoint("TOP", sortingText, "BOTTOM", 0, -12)
	progress:SetMinMaxValues(0, 100)
	progress:SetStatusBarTexture(Theme.Texture)
	progress:SetStatusBarColor(unpack(Theme.Colors.cast))
	progress:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
	progress:SetBackdropColor(0.08, 0.09, 0.12, 1)
	progress:SetBackdropBorderColor(unpack(Theme.Colors.border))
	overlay.Progress = progress
	self.SortOverlay = overlay
	self:SetRailCollapsed(SlickUI.DB.bags.bankRailCollapsed)
end

function Bank:Initialize()
	self:CreateFrame()
	local events = CreateFrame("Frame")
	events:RegisterEvent("BANKFRAME_OPENED")
	events:RegisterEvent("BANKFRAME_CLOSED")
	events:RegisterEvent("PLAYER_INTERACTION_MANAGER_FRAME_SHOW")
	events:RegisterEvent("PLAYER_INTERACTION_MANAGER_FRAME_HIDE")
	events:RegisterEvent("BANK_TABS_CHANGED")
	events:RegisterEvent("BAG_UPDATE_DELAYED")
	events:RegisterEvent("ITEM_LOCK_CHANGED")
	events:SetScript("OnEvent", function(_, event, interactionType)
		local isBank = interactionType == Enum.PlayerInteractionType.CharacterBanker or interactionType == Enum.PlayerInteractionType.AccountBanker
		if event == "PLAYER_INTERACTION_MANAGER_FRAME_SHOW" and isBank then C_Timer.After(0, function() Bank:Show() end) return end
		if event == "PLAYER_INTERACTION_MANAGER_FRAME_HIDE" and isBank then
			if Bank.NativeBankManagementStarted then
				SlickUI.DB.bags.nativeBankManagement = false
				Bank.NativeBankManagementStarted = false
			end
			Bank:Hide(false)
			return
		end
		if event == "BANKFRAME_OPENED" then C_Timer.After(0, function() Bank:Show() end) return end
		if event == "BANKFRAME_CLOSED" then
			if Bank.NativeBankManagementStarted then
				SlickUI.DB.bags.nativeBankManagement = false
				Bank.NativeBankManagementStarted = false
			end
			Bank:Hide(false)
			return
		end
		if event == "BAG_UPDATE_DELAYED" and Bank.Sorting then Bank:FinishSorting() return end
		Bank:Refresh()
	end)
end

SlickUI:RegisterModule("Bank", Bank)
