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 GetWarbandMoney()
	if not (C_Bank and type(C_Bank.FetchDepositedMoney) == "function" and Enum and Enum.BankType) then return 0 end
	local ok, amount = pcall(C_Bank.FetchDepositedMoney, Enum.BankType.Account)
	if not ok or type(amount) ~= "number" then return 0 end
	return amount
end

local function FormatWarbandMoney(amount)
	if SlickUI.GoldLedger and SlickUI.GoldLedger.FormatCoinMoney then
		return SlickUI.GoldLedger:FormatCoinMoney(amount)
	end
	local copper = math.max(0, math.floor(tonumber(amount) or 0))
	local gold = math.floor(copper / 10000)
	local silver = math.floor((copper % 10000) / 100)
	local coins = copper % 100
	local text = tostring(gold):reverse():gsub("(%d%d%d)", "%1,"):reverse():gsub("^,", "")
	return string.format("|cffffd24a%sg|r |cffc7c7cf%02ds|r |cffc87533%02dc|r", text, silver, coins)
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)
	if not ok or type(tabs) ~= "table" then return {} end

	-- Keep Blizzard's purchased-tab order, but present a tab explicitly named
	-- for reagents after the regular bank tabs so the rail matches the content.
	local ordered, reagentTabs = {}, {}
	for _, tab in ipairs(tabs) do
		local name = type(tab.name) == "string" and tab.name:lower() or ""
		table.insert(name:find("reagent", 1, true) and reagentTabs or ordered, tab)
	end
	for _, tab in ipairs(reagentTabs) do table.insert(ordered, tab) end
	return ordered
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:RegisterForClicks("LeftButtonUp", "RightButtonUp")
			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, mouseButton)
				if mouseButton == "RightButton" then
					Bank:OpenTabSettings(self.TabID)
				else
					Bank:SelectTab(self.TabID)
				end
			end)
			button:SetScript("OnEnter", function(self)
				GameTooltip:SetOwner(self, "ANCHOR_LEFT")
				GameTooltip:SetText(self.TabName or "Bank Tab")
				GameTooltip:AddLine("Left-click to select", 0.82, 0.82, 0.82)
				GameTooltip:AddLine("Right-click to change icon and name", 0.82, 0.82, 0.82)
				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:OpenTabSettings(tabID)
	local settingsMenu = BankPanel and BankPanel.TabSettingsMenu
	local events = BankPanelTabSettingsMenuMixin and BankPanelTabSettingsMenuMixin.Event
	local openEvent = events and events.OpenTabSettingsRequested
	if not settingsMenu or not openEvent or type(settingsMenu.TriggerEvent) ~= "function" then
		SlickUI:Print("Bank tab settings are not available yet. Close and reopen the banker, then try again.")
		return
	end

	if settingsMenu.SetIgnoreParentAlpha then settingsMenu:SetIgnoreParentAlpha(true) end
	settingsMenu:TriggerEvent(openEvent, tabID)
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,
					tabName = tab.name or "Bank Tab",
					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
	elseif mode == "single" then
		local groups = {}
		for _, slot in ipairs(slots) do
			local group = groups[#groups]
			if not group or group.tabID ~= slot.tabID then
				group = { tabID = slot.tabID, name = slot.tabName, slots = {} }
				table.insert(groups, group)
			end
			table.insert(group.slots, slot)
		end
		local buttonIndex = 0
		for labelIndex, group in ipairs(groups) do
			local label = self:GetSectionLabel(labelIndex)
			label:ClearAllPoints()
			label:SetPoint("TOPLEFT", self.Content, "TOPLEFT", 0, -height)
			label:SetText((group.name or "Bank Tab") .. "  |cff8f98a8" .. #group.slots .. "|r")
			label:Show()
			height = height + 22
			for index, slot in ipairs(group.slots) do
				buttonIndex = buttonIndex + 1
				self:PlaceButton(self:GetButton(buttonIndex), slot, index, columns, size, spacing, height)
			end
			height = height + math.ceil(#group.slots / columns) * (size + spacing) + 12
		end
		height = math.max(100, height)
	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:RefreshDepositButton()
	self:RefreshWarbandMoney()
	self:UpdateSortControl()
	self:Refresh()
end

function Bank:UpdateSortControl()
	if not self.SortButton or self.Sorting then return end
	local sortFunction = self.BankType == Enum.BankType.Account
		and C_Container and C_Container.SortAccountBankBags
		or C_Container and C_Container.SortBankBags
	local available = type(sortFunction) == "function" and not InCombatLockdown()
	self.SortButton:SetEnabled(available)
	self.SortButton:SetText(available and "Sort Bank" or (InCombatLockdown() and "Sort Locked" or "Sort Unavailable"))
end

function Bank:RefreshWarbandMoney()
	if not self.WarbandMoney then return end
	local shown = self.BankType == Enum.BankType.Account
	self.WarbandMoney:SetShown(shown)
	self.DepositMoneyButton:SetShown(shown)
	self.WithdrawMoneyButton:SetShown(shown)
	if not shown then return end
	self.WarbandMoney:SetText("Warband Gold: " .. FormatWarbandMoney(GetWarbandMoney()))
	local transferSupported = C_Bank and C_Bank.DoesBankTypeSupportMoneyTransfer and C_Bank.DoesBankTypeSupportMoneyTransfer(Enum.BankType.Account)
	self.DepositMoneyButton:SetEnabled(transferSupported and C_Bank.CanDepositMoney(Enum.BankType.Account) and not InCombatLockdown())
	self.WithdrawMoneyButton:SetEnabled(transferSupported and C_Bank.CanWithdrawMoney(Enum.BankType.Account) and not InCombatLockdown())
end

function Bank:OpenMoneyTransfer(dialogName)
	if self.BankType ~= Enum.BankType.Account or InCombatLockdown() then return end
	StaticPopup_Hide(dialogName == "BANK_MONEY_DEPOSIT" and "BANK_MONEY_WITHDRAW" or "BANK_MONEY_DEPOSIT")
	StaticPopup_Show(dialogName, nil, nil, { bankType = Enum.BankType.Account })
end

function Bank:RefreshDepositButton()
	if not self.DepositReagentsButton then return end
	local isCharacterBank = self.BankType == Enum.BankType.Character
	local isAccountBank = self.BankType == Enum.BankType.Account
	local characterSupported = type(DepositReagentBank) == "function"
	local nativeDepositSupported = C_Bank and type(C_Bank.AutoDepositItemsIntoBank) == "function"
	local unlocked = not IsReagentBankUnlocked or IsReagentBankUnlocked()
	local supported = (isCharacterBank and ((characterSupported and unlocked) or nativeDepositSupported))
		or (isAccountBank and nativeDepositSupported)
	self.DepositReagentsButton:SetShown(supported)
	self.DepositReagentsButton:SetEnabled(supported and not InCombatLockdown())
	self.DepositReagentsButton:SetText(isAccountBank and "Deposit All" or "Deposit Reagents")
end

function Bank:DepositAllReagents()
	if InCombatLockdown() then
		SlickUI:Print("Bank items cannot be deposited during combat.")
		return
	end
	if self.BankType == Enum.BankType.Account then
		if C_Bank and type(C_Bank.AutoDepositItemsIntoBank) == "function" then
			C_Bank.AutoDepositItemsIntoBank(Enum.BankType.Account)
		end
	elseif self.BankType == Enum.BankType.Character then
		if type(DepositReagentBank) == "function" then
			if IsReagentBankUnlocked and not IsReagentBankUnlocked() then
				SlickUI:Print("Unlock the reagent bank before depositing reagents.")
				return
			end
			DepositReagentBank()
		elseif C_Bank and type(C_Bank.AutoDepositItemsIntoBank) == "function" then
			C_Bank.AutoDepositItemsIntoBank(Enum.BankType.Character)
		end
	end
end

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

function Bank:StartSorting()
	if self.Sorting or not self.BankType or InCombatLockdown() then return end
	local sortFunction = self.BankType == Enum.BankType.Account
		and C_Container and C_Container.SortAccountBankBags
		or C_Container and C_Container.SortBankBags
	if type(sortFunction) ~= "function" 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()
	SlickUI:DiagnosticLog("Bank", "sort-started", self.BankType == Enum.BankType.Account and "account" or "character")
	local ok, errorMessage = pcall(sortFunction)
	if not ok then
		SlickUI:DiagnosticLog("Bank", "sort-failed", errorMessage)
		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 SlickUI.Bags then SlickUI.Bags:UpdateCombatControls() end
	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 SlickUI.Bags then SlickUI.Bags:UpdateCombatControls() 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

	Theme:CreateHeaderIcon(frame, 36, 14, -9)
	local title = Theme:CreateText(frame, 18)
	title:SetPoint("TOPLEFT", 56, -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 warbandMoney = Theme:CreateText(frame, 10)
	warbandMoney:SetPoint("TOP", frame, "TOP", 70, -34)
	warbandMoney:SetWidth(260)
	warbandMoney:SetJustifyH("CENTER")
	self.WarbandMoney = warbandMoney
	local withdrawMoney = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	withdrawMoney:SetSize(78, 22)
	withdrawMoney:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -128, -48)
	withdrawMoney:SetText("Withdraw")
	withdrawMoney:SetScript("OnClick", function() Bank:OpenMoneyTransfer("BANK_MONEY_WITHDRAW") end)
	self.WithdrawMoneyButton = withdrawMoney
	local depositMoney = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	depositMoney:SetSize(78, 22)
	depositMoney:SetPoint("LEFT", withdrawMoney, "RIGHT", 4, 0)
	depositMoney:SetText("Deposit")
	depositMoney:SetScript("OnClick", function() Bank:OpenMoneyTransfer("BANK_MONEY_DEPOSIT") end)
	self.DepositMoneyButton = depositMoney
	local depositReagents = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	depositReagents:SetSize(125, 22)
	depositReagents:SetPoint("LEFT", account, "RIGHT", 8, 0)
	depositReagents:SetText("Deposit Reagents")
	depositReagents:SetScript("OnClick", function() Bank:DepositAllReagents() end)
	depositReagents:SetScript("OnEnter", function(self)
		GameTooltip:SetOwner(self, "ANCHOR_BOTTOM")
		if Bank.BankType == Enum.BankType.Account then
			GameTooltip:SetText("Deposit All")
			GameTooltip:AddLine("Moves all eligible carried items into matching Warband Bank tabs.", 0.68, 0.71, 0.78, true)
		elseif type(DepositReagentBank) ~= "function" then
			GameTooltip:SetText("Deposit Reagents")
			GameTooltip:AddLine("Uses Blizzard's native deposit action to move eligible carried reagents into matching character-bank tabs.", 0.68, 0.71, 0.78, true)
		else
			GameTooltip:SetText("Deposit All Reagents")
			GameTooltip:AddLine("Moves eligible crafting reagents from your carried bags into the character reagent bank.", 0.68, 0.71, 0.78, true)
		end
		GameTooltip:Show()
	end)
	depositReagents:SetScript("OnLeave", GameTooltip_Hide)
	self.DepositReagentsButton = depositReagents
	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:RegisterEvent("PLAYER_REGEN_DISABLED")
	events:RegisterEvent("PLAYER_REGEN_ENABLED")
	events:RegisterEvent("PLAYER_MONEY")
	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
		-- Native sorting locks and moves many items. Suppress intermediate
		-- ITEM_LOCK_CHANGED refreshes and rebuild the combined view only once.
		if Bank.Sorting then
			if event == "BAG_UPDATE_DELAYED" then Bank:FinishSorting() end
			return
		end
		Bank:RefreshDepositButton()
		Bank:RefreshWarbandMoney()
		Bank:UpdateSortControl()
		Bank:Refresh()
	end)
end

SlickUI:RegisterModule("Bank", Bank)
