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

local Planner = {
	RecipeByItem = {},
	Queue = {},
	Rows = {},
	SelectedRecipeID = nil,
	SelectedQuantity = 1,
}
SlickUI.CraftingPlanner = Planner

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 ItemCount(itemID)
	return tonumber(SafeCall(C_Item.GetItemCount, itemID, true, false, true)) or 0
end

local function ItemName(itemID)
	local name = C_Item.GetItemNameByID(itemID)
	return name or ("Item " .. tostring(itemID))
end

local function ShoppingList()
	SlickUI.DB.crafting = SlickUI.DB.crafting or {}
	SlickUI.DB.crafting.shoppingList = SlickUI.DB.crafting.shoppingList or {}
	return SlickUI.DB.crafting.shoppingList
end

local function VendorItems()
	SlickUI.DB.crafting.vendorItems = SlickUI.DB.crafting.vendorItems or {}
	return SlickUI.DB.crafting.vendorItems
end

local function SourceText(itemID)
	return VendorItems()[itemID] and "|cff55bfffVendor|r" or "|cffffd24aAuction House / Other Source|r"
end

local ShoppingProgress

function Planner:LearnCurrentMerchant()
	local catalog = VendorItems()
	for index = 1, (GetMerchantNumItems() or 0) do
		local itemID = GetMerchantItemID(index)
		if itemID then catalog[itemID] = true end
	end
	self:RefreshRows()
	self:RefreshShoppingRows()
end

local function MerchantItemInfo(index)
	if C_MerchantFrame and C_MerchantFrame.GetItemInfo then
		return C_MerchantFrame.GetItemInfo(index)
	end
	local name, texture, price, stackCount, numAvailable, isPurchasable, _, extendedCost = GetMerchantItemInfo(index)
	return { name = name, texture = texture, price = price, stackCount = stackCount, numAvailable = numAvailable, isPurchasable = isPurchasable, extendedCost = extendedCost }
end

function Planner:BuildMerchantPurchaseQueue()
	local wanted = {}
	for _, entry in ipairs(ShoppingList()) do
		local _, remaining = ShoppingProgress(entry)
		if remaining > 0 then wanted[entry.itemID] = { entry = entry, remaining = remaining } end
	end
	local queue, total = {}, 0
	for index = 1, (GetMerchantNumItems() or 0) do
		local itemID = GetMerchantItemID(index)
		local match = itemID and wanted[itemID]
		if match then
			local info = MerchantItemInfo(index)
			local alternateCosts = (GetMerchantItemCostInfo and GetMerchantItemCostInfo(index) or 0) > 0
			local price = tonumber(info and info.price) or 0
			local stackCount = math.max(1, tonumber(info and info.stackCount) or 1)
			local available = tonumber(info and info.numAvailable) or -1
			local quantity = match.remaining
			if available >= 0 then quantity = math.min(quantity, available * stackCount) end
			if info and info.isPurchasable ~= false and price > 0 and not info.extendedCost and not alternateCosts and quantity > 0 then
				local batches = math.ceil(quantity / stackCount)
				queue[#queue + 1] = { index = index, itemID = itemID, name = match.entry.name or ItemName(itemID), quantity = quantity, batches = batches, cost = price * batches }
				total = total + (price * batches)
			end
		end
	end
	return queue, total
end

function Planner:BuyMerchantQueue(queue)
	if self.BuyingVendor or #queue == 0 then return end
	self.BuyingVendor = true
	local position = 1
	local function Next()
		if not MerchantFrame or not MerchantFrame:IsShown() then Planner.BuyingVendor = false; return end
		local purchase = queue[position]
		if not purchase then
			Planner.BuyingVendor = false
			Planner:RefreshShoppingRows()
			return
		end
		if (GetMoney() or 0) < purchase.cost then
			SlickUI:Print("Shopping-list purchase stopped: not enough gold for " .. purchase.name .. ".")
			Planner.BuyingVendor = false
			return
		end
		local maxStack = GetMerchantItemMaxStack and GetMerchantItemMaxStack(purchase.index) or purchase.quantity
		local amount = math.min(purchase.quantity, math.max(1, tonumber(maxStack) or purchase.quantity))
		local ok = pcall(BuyMerchantItem, purchase.index, amount)
		if not ok then
			SlickUI:Print("Could not purchase " .. purchase.name .. ".")
			Planner.BuyingVendor = false
			return
		end
		purchase.quantity = purchase.quantity - amount
		if purchase.quantity <= 0 then position = position + 1 end
		C_Timer.After(0.15, Next)
	end
	Next()
end

function Planner:CreateVendorPrompt()
	if self.VendorPrompt then return end
	local frame = CreateFrame("Frame", "SlickUICraftingVendorPrompt", UIParent, "BackdropTemplate")
	frame:SetSize(430, 205); frame:SetPoint("CENTER"); frame:SetFrameStrata("DIALOG")
	frame:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 2 })
	frame:SetBackdropColor(0.015, 0.018, 0.025, 0.98); frame:SetBackdropBorderColor(1, 0.58, 0, 1); frame:Hide()
	local title = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge"); title:SetPoint("TOPLEFT", 18, -17); title:SetText("Shopping List Vendor Items")
	local close = CreateFrame("Button", nil, frame, "UIPanelCloseButton"); close:SetPoint("TOPRIGHT", -3, -3)
	local text = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlight"); text:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -12); text:SetPoint("RIGHT", -18, 0); text:SetJustifyH("LEFT"); text:SetJustifyV("TOP"); frame.Text = text
	local auto = CreateFrame("CheckButton", nil, frame, "UICheckButtonTemplate"); auto:SetPoint("BOTTOMLEFT", 14, 48); auto.Text:SetText("Automatically buy shopping-list vendor items"); auto:SetChecked(SlickUI.DB.crafting.autoBuyVendor == true)
	auto:SetScript("OnClick", function(self) SlickUI.DB.crafting.autoBuyVendor = self:GetChecked() == true end)
	local buy = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate"); buy:SetSize(155, 25); buy:SetPoint("BOTTOMRIGHT", -18, 16); buy:SetText("Buy Required Items")
	buy:SetScript("OnClick", function() frame:Hide(); Planner:BuyMerchantQueue(frame.Queue or {}) end)
	local later = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate"); later:SetSize(100, 25); later:SetPoint("RIGHT", buy, "LEFT", -8, 0); later:SetText("Not Now"); later:SetScript("OnClick", function() frame:Hide() end)
	frame.Auto = auto; self.VendorPrompt = frame
end

function Planner:HandleMerchant()
	self:LearnCurrentMerchant()
	local queue, total = self:BuildMerchantPurchaseQueue()
	if #queue == 0 then return end
	if SlickUI.DB.crafting.autoBuyVendor then
		self:BuyMerchantQueue(queue)
		return
	end
	self:CreateVendorPrompt()
	local names = {}
	for _, purchase in ipairs(queue) do names[#names + 1] = string.format("%s x%d", purchase.name, purchase.quantity) end
	self.VendorPrompt.Text:SetText("This merchant has shopping-list materials:\n\n" .. table.concat(names, ", ") .. "\n\nEstimated cost: " .. GetCoinTextureString(total))
	self.VendorPrompt.Queue = queue
	self.VendorPrompt.Auto:SetChecked(SlickUI.DB.crafting.autoBuyVendor == true)
	self.VendorPrompt:Show()
end

local function RecipeName(recipeID)
	local info = SafeCall(C_TradeSkillUI.GetRecipeInfo, recipeID)
	return info and info.name or ("Recipe " .. tostring(recipeID))
end

local function RecipeOutput(recipeID)
	local schematic = SafeCall(C_TradeSkillUI.GetRecipeSchematic, recipeID, false)
	if not schematic then return nil end
	return schematic.outputItemID, math.max(1, tonumber(schematic.quantityMin) or 1), schematic
end

function Planner:BuildRecipeIndex()
	wipe(self.RecipeByItem)
	local recipes = SafeCall(C_TradeSkillUI.GetAllRecipeIDs)
	if type(recipes) ~= "table" then return end
	for _, recipeID in ipairs(recipes) do
		local info = SafeCall(C_TradeSkillUI.GetRecipeInfo, recipeID)
		if info and info.learned then
			local outputItemID = RecipeOutput(recipeID)
			if outputItemID and not self.RecipeByItem[outputItemID] then
				self.RecipeByItem[outputItemID] = recipeID
			end
		end
	end
end

local function ChooseReagent(slot)
	if type(slot.reagents) ~= "table" then return nil end
	local best
	for _, reagent in ipairs(slot.reagents) do
		local itemID = reagent.itemID
		if itemID then
			if not best or ItemCount(itemID) > ItemCount(best) then best = itemID end
		end
	end
	return best
end

function Planner:BuildPlan(recipeID, crafts)
	local plan, stock, visiting = {}, {}, {}
	local function GetStock(itemID)
		if stock[itemID] == nil then stock[itemID] = ItemCount(itemID) end
		return stock[itemID]
	end
	local function Consume(itemID, amount)
		local available = GetStock(itemID)
		local used = math.min(amount, available)
		stock[itemID] = available - used
		return amount - used
	end
	local function AddRecipe(id, count, depth)
		if depth > 8 or visiting[id] then return false end
		local outputItemID, yield, schematic = RecipeOutput(id)
		if not schematic then return false end
		visiting[id] = true
		for _, slot in ipairs(schematic.reagentSlotSchematics or {}) do
			if slot.required ~= false and not slot.hiddenInCraftingForm then
				local itemID = ChooseReagent(slot)
				local needed = itemID and ((tonumber(slot.quantityRequired) or 0) * count) or 0
				if itemID and needed > 0 then
					local missing = Consume(itemID, needed)
					local subRecipe = self.RecipeByItem[itemID]
					if missing > 0 and subRecipe and subRecipe ~= id then
						local _, subYield = RecipeOutput(subRecipe)
						local subCrafts = math.ceil(missing / (subYield or 1))
						AddRecipe(subRecipe, subCrafts, depth + 1)
						stock[itemID] = GetStock(itemID) + (subCrafts * (subYield or 1))
						missing = Consume(itemID, missing)
					end
					if missing > 0 then
						plan[#plan + 1] = { kind = "material", itemID = itemID, name = ItemName(itemID), count = missing, depth = depth + 1 }
					end
				end
			end
		end
		visiting[id] = nil
		plan[#plan + 1] = { kind = "recipe", recipeID = id, itemID = outputItemID, name = RecipeName(id), count = count, yield = yield, depth = depth }
		return true
	end
	AddRecipe(recipeID, crafts, 0)
	return plan
end

function Planner:GetSelectedRecipeID()
	local frame = _G.ProfessionsFrame
	local form = frame and frame.CraftingPage and frame.CraftingPage.SchematicForm
	if not form then return self.SelectedRecipeID end
	if type(form.GetRecipeInfo) == "function" then
		local info = SafeCall(form.GetRecipeInfo, form)
		if info and info.recipeID then return info.recipeID end
	end
	return form.recipeInfo and form.recipeInfo.recipeID or self.SelectedRecipeID
end

function Planner:RefreshSelection()
	local recipeID = self:GetSelectedRecipeID()
	if recipeID then self.SelectedRecipeID = recipeID end
	if self.Window then
		self.Window.Selected:SetText(self.SelectedRecipeID and ("Selected: " .. RecipeName(self.SelectedRecipeID)) or "Select a recipe")
	end
end

function Planner:QueueSelected()
	self:RefreshSelection()
	if not self.SelectedRecipeID then
		SlickUI:Print("Select a learned crafting recipe first.")
		return
	end
	self:BuildRecipeIndex()
	self.Queue = self:BuildPlan(self.SelectedRecipeID, self.SelectedQuantity)
	self:CreateShoppingList()
	self:RefreshRows()
end

function Planner:CreateShoppingList()
	local aggregate = {}
	for _, entry in ipairs(self.Queue) do
		if entry.kind == "material" and entry.itemID and entry.count > 0 then
			aggregate[entry.itemID] = (aggregate[entry.itemID] or 0) + entry.count
		end
	end
	local list = ShoppingList()
	wipe(list)
	for itemID, needed in pairs(aggregate) do
		list[#list + 1] = {
			itemID = itemID,
			name = ItemName(itemID),
			needed = needed,
			ownedAtCreation = ItemCount(itemID),
		}
	end
	table.sort(list, function(a, b) return a.name < b.name end)
end

ShoppingProgress = function(entry)
	local purchased = math.max(0, ItemCount(entry.itemID) - (entry.ownedAtCreation or 0))
	local credited = math.min(entry.needed or 0, purchased)
	return credited, math.max(0, (entry.needed or 0) - credited)
end

function Planner:RefreshShoppingRows()
	if not self.ShoppingWindow then return end
	local list = ShoppingList()
	for _, row in ipairs(self.ShoppingRows) do row:Hide() end
	local complete = 0
	for index, entry in ipairs(list) do
		local row = self.ShoppingRows[index]
		if not row then
			row = CreateFrame("Button", nil, self.ShoppingWindow.List)
			row:SetHeight(38)
			row:SetPoint("TOPLEFT", 0, -((index - 1) * 39))
			row:SetPoint("RIGHT", 0, 0)
			row.Icon = row:CreateTexture(nil, "ARTWORK")
			row.Icon:SetSize(34, 34); row.Icon:SetPoint("LEFT", 2, 0)
			row.Name = row:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
			row.Name:SetPoint("TOPLEFT", row.Icon, "TOPRIGHT", 8, -2)
			row.Status = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
			row.Status:SetPoint("BOTTOMLEFT", row.Icon, "BOTTOMRIGHT", 8, 2)
			row.Source = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
			row.Source:SetPoint("BOTTOMLEFT", row.Status, "BOTTOMRIGHT", 10, 0)
			row.Count = row:CreateFontString(nil, "ARTWORK", "GameFontNormal")
			row.Count:SetPoint("RIGHT", -8, 0)
			row:SetScript("OnEnter", function(self)
				if self.ItemID then GameTooltip:SetOwner(self, "ANCHOR_RIGHT"); GameTooltip:SetItemByID(self.ItemID); GameTooltip:Show() end
			end)
			row:SetScript("OnLeave", GameTooltip_Hide)
			self.ShoppingRows[index] = row
		end
		local purchased, remaining = ShoppingProgress(entry)
		row.ItemID = entry.itemID
		row.Icon:SetTexture(C_Item.GetItemIconByID(entry.itemID) or 134400)
		row.Name:SetText(entry.name or ItemName(entry.itemID))
		row.Source:SetText(SourceText(entry.itemID))
		if remaining == 0 then
			row.Status:SetText("|cff42df72Purchased|r")
			complete = complete + 1
		elseif purchased > 0 then
			row.Status:SetText(string.format("|cffffd24aPartially purchased: %d of %d|r", purchased, entry.needed))
		else
			row.Status:SetText("|cffff6666Needed|r")
		end
		row.Count:SetText(string.format("%d remaining", remaining))
		row:Show()
	end
	self.ShoppingWindow.Summary:SetText(string.format("Purchased: |cff42df72%d|r / %d", complete, #list))
	self.ShoppingWindow.Empty:SetShown(#list == 0)
	self.ShoppingWindow.List:SetHeight(math.max(1, #list * 39))
end

function Planner:CreateShoppingWindow()
	if self.ShoppingWindow then return end
	local window = CreateFrame("Frame", "SlickUICraftingShoppingList", UIParent, "BackdropTemplate")
	window:SetSize(390, 510); window:SetFrameStrata("HIGH"); window:SetClampedToScreen(true)
	window:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1 })
	window:SetBackdropColor(0.015, 0.018, 0.025, 0.96); window:SetBackdropBorderColor(1, 0.58, 0, 1); window:Hide()
	local title = window:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge"); title:SetPoint("TOPLEFT", 16, -15); title:SetText("SlickUI Shopping List")
	local summary = window:CreateFontString(nil, "ARTWORK", "GameFontHighlight"); summary:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -10); window.Summary = summary
	local close = CreateFrame("Button", nil, window, "UIPanelCloseButton"); close:SetPoint("TOPRIGHT", -3, -3)
	local scroll = CreateFrame("ScrollFrame", nil, window, "UIPanelScrollFrameTemplate")
	scroll:SetPoint("TOPLEFT", 16, -66); scroll:SetPoint("BOTTOMRIGHT", -32, 50)
	local list = CreateFrame("Frame", nil, scroll); list:SetWidth(336); list:SetHeight(1); scroll:SetScrollChild(list); window.List = list
	local empty = list:CreateFontString(nil, "ARTWORK", "GameFontDisable"); empty:SetPoint("TOP", 0, -35); empty:SetText("No missing materials are on the shopping list."); window.Empty = empty
	local clear = CreateFrame("Button", nil, window, "UIPanelButtonTemplate"); clear:SetSize(140, 25); clear:SetPoint("BOTTOMRIGHT", -16, 15); clear:SetText("Clear Purchased")
	clear:SetScript("OnClick", function()
		local source, kept = ShoppingList(), {}
		for _, entry in ipairs(source) do local _, remaining = ShoppingProgress(entry); if remaining > 0 then kept[#kept + 1] = entry end end
		wipe(source); for _, entry in ipairs(kept) do source[#source + 1] = entry end
		Planner:RefreshShoppingRows()
	end)
	self.ShoppingRows = {}; self.ShoppingWindow = window
end

function Planner:ShowShoppingList()
	self:CreateShoppingWindow()
	local parent = _G.AuctionHouseFrame
	self.ShoppingWindow:ClearAllPoints()
	if parent then self.ShoppingWindow:SetPoint("TOPLEFT", parent, "TOPRIGHT", 4, 0) else self.ShoppingWindow:SetPoint("CENTER") end
	self:RefreshShoppingRows(); self.ShoppingWindow:Show()
end

function Planner:LoadNext()
	for _, entry in ipairs(self.Queue) do
		if entry.kind == "recipe" then
			C_TradeSkillUI.OpenRecipe(entry.recipeID)
			SlickUI:Print(string.format("Loaded %s. Craft %d, then press Refresh Queue.", entry.name, entry.count))
			return
		end
	end
	SlickUI:Print("The queue has no craftable steps.")
end

function Planner:RefreshQueue()
	if self.SelectedRecipeID then
		self:BuildRecipeIndex()
		self.Queue = self:BuildPlan(self.SelectedRecipeID, self.SelectedQuantity)
	end
	self:RefreshRows()
end

function Planner:RefreshRows()
	if not self.Window then return end
	for _, row in ipairs(self.Rows) do row:Hide() end
	for index, entry in ipairs(self.Queue) do
		local row = self.Rows[index]
		if not row then
			row = CreateFrame("Button", nil, self.Window.List)
			row:SetHeight(30)
			row:SetPoint("TOPLEFT", 0, -((index - 1) * 31))
			row:SetPoint("RIGHT", 0, 0)
			row.Icon = row:CreateTexture(nil, "ARTWORK")
			row.Icon:SetSize(26, 26)
			row.Icon:SetPoint("LEFT", 2, 0)
			row.Text = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
			row.Text:SetPoint("TOPLEFT", row.Icon, "TOPRIGHT", 7, -1)
			row.Text:SetPoint("RIGHT", -4, 0)
			row.Text:SetJustifyH("LEFT")
			row.Source = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
			row.Source:SetPoint("BOTTOMLEFT", row.Icon, "BOTTOMRIGHT", 7, 1)
			self.Rows[index] = row
		end
		row.Icon:SetTexture(entry.itemID and C_Item.GetItemIconByID(entry.itemID) or 134400)
		local prefix = entry.kind == "recipe" and "CRAFT" or "NEED"
		local color = entry.kind == "recipe" and "|cff55dd88" or "|cffff6666"
		row.Text:SetText(string.rep("  ", entry.depth or 0) .. color .. prefix .. "|r  " .. entry.name .. " x" .. entry.count)
		row.Source:SetText(entry.kind == "material" and SourceText(entry.itemID) or "|cff55dd88Crafted subcomponent|r")
		row:Show()
	end
	self.Window.Empty:SetShown(#self.Queue == 0)
	self.Window.List:SetHeight(math.max(1, #self.Queue * 31))
end

function Planner:SetCollapsed(collapsed)
	if not self.Window then return end
	self.Collapsed = collapsed == true
	self.Window:SetSize(self.Collapsed and 36 or 450, 520)
	for _, region in ipairs(self.Window.CollapsibleRegions or {}) do
		region:SetShown(not self.Collapsed)
	end
	self.Window.CollapseButton:ClearAllPoints()
	if self.Collapsed then
		self.Window.CollapseButton:SetPoint("TOP", 0, -14)
	else
		self.Window.CollapseButton:SetPoint("RIGHT", self.Window.CloseButton, "LEFT", -2, 0)
	end
	self.Window.CollapseButton:SetText(self.Collapsed and ">" or "<")
	self.Window.CollapseButton:SetScript("OnEnter", function(button)
		GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
		GameTooltip:SetText(self.Collapsed and "Expand Crafting Queue" or "Collapse Crafting Queue")
		GameTooltip:Show()
	end)
end

function Planner:ClearQueue()
	wipe(self.Queue)
	self:RefreshRows()
end

function Planner:CreateWindow()
	if self.Window then return end
	local window = CreateFrame("Frame", "SlickUICraftingPlanner", UIParent, "BackdropTemplate")
	window:SetSize(450, 520)
	window:SetFrameStrata("HIGH")
	window:SetClampedToScreen(true)
	window:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1 })
	window:SetBackdropColor(0.015, 0.018, 0.025, 0.96)
	window:SetBackdropBorderColor(1, 0.58, 0, 1)
	window:Hide()

	local title = window:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
	title:SetPoint("TOPLEFT", 16, -15)
	title:SetText("SlickUI Crafting Queue")
	local close = CreateFrame("Button", nil, window, "UIPanelCloseButton")
	close:SetPoint("TOPRIGHT", -3, -3)
	window.CloseButton = close
	local collapse = CreateFrame("Button", nil, window, "UIPanelButtonTemplate")
	collapse:SetSize(26, 22); collapse:SetPoint("RIGHT", close, "LEFT", -2, 0); collapse:SetText("<")
	collapse:SetScript("OnClick", function() Planner:SetCollapsed(not Planner.Collapsed) end)
	collapse:SetScript("OnLeave", GameTooltip_Hide)
	window.CollapseButton = collapse
	local selected = window:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
	selected:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -13)
	selected:SetPoint("RIGHT", -16, 0)
	selected:SetJustifyH("LEFT")
	window.Selected = selected

	local minus = CreateFrame("Button", nil, window, "UIPanelButtonTemplate")
	minus:SetSize(26, 22); minus:SetPoint("TOPLEFT", selected, "BOTTOMLEFT", 0, -10); minus:SetText("-")
	local quantity = window:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
	quantity:SetPoint("LEFT", minus, "RIGHT", 10, 0); quantity:SetText("Quantity: 1")
	local plus = CreateFrame("Button", nil, window, "UIPanelButtonTemplate")
	plus:SetSize(26, 22); plus:SetPoint("LEFT", quantity, "RIGHT", 10, 0); plus:SetText("+")
	minus:SetScript("OnClick", function() Planner.SelectedQuantity = math.max(1, Planner.SelectedQuantity - 1); quantity:SetText("Quantity: " .. Planner.SelectedQuantity) end)
	plus:SetScript("OnClick", function() Planner.SelectedQuantity = math.min(999, Planner.SelectedQuantity + 1); quantity:SetText("Quantity: " .. Planner.SelectedQuantity) end)
	local queue = CreateFrame("Button", nil, window, "UIPanelButtonTemplate")
	queue:SetSize(105, 24); queue:SetPoint("TOPRIGHT", selected, "BOTTOMRIGHT", 0, -9); queue:SetText("Queue All")
	queue:SetScript("OnClick", function() Planner:QueueSelected() end)

	local scroll = CreateFrame("ScrollFrame", nil, window, "UIPanelScrollFrameTemplate")
	scroll:SetPoint("TOPLEFT", minus, "BOTTOMLEFT", 0, -12)
	scroll:SetPoint("BOTTOMRIGHT", -32, 52)
	local list = CreateFrame("Frame", nil, scroll)
	list:SetWidth(396); list:SetHeight(1); scroll:SetScrollChild(list); window.List = list
	local empty = list:CreateFontString(nil, "ARTWORK", "GameFontDisable")
	empty:SetPoint("TOP", 0, -35); empty:SetText("Select a recipe and click Queue All."); window.Empty = empty

	local load = CreateFrame("Button", nil, window, "UIPanelButtonTemplate")
	load:SetSize(120, 25); load:SetPoint("BOTTOMLEFT", 16, 16); load:SetText("Load Next")
	load:SetScript("OnClick", function() Planner:LoadNext() end)
	local refresh = CreateFrame("Button", nil, window, "UIPanelButtonTemplate")
	refresh:SetSize(100, 25); refresh:SetPoint("LEFT", load, "RIGHT", 7, 0); refresh:SetText("Refresh")
	refresh:SetScript("OnClick", function() Planner:RefreshQueue() end)
	local shop = CreateFrame("Button", nil, window, "UIPanelButtonTemplate")
	shop:SetSize(100, 25); shop:SetPoint("BOTTOMRIGHT", -16, 16); shop:SetText("Shopping List")
	shop:SetScript("OnClick", function() Planner:ShowShoppingList() end)
	local clear = CreateFrame("Button", nil, window, "UIPanelButtonTemplate")
	clear:SetSize(90, 25); clear:SetPoint("RIGHT", shop, "LEFT", -7, 0); clear:SetText("Clear Queue")
	clear:SetScript("OnClick", function() Planner:ClearQueue() end)
	refresh:ClearAllPoints(); refresh:SetPoint("RIGHT", clear, "LEFT", -7, 0)
	load:ClearAllPoints(); load:SetPoint("RIGHT", refresh, "LEFT", -7, 0)
	window.CollapsibleRegions = { title, close, selected, minus, quantity, plus, queue, scroll, load, refresh, clear, shop }
	self.Window = window
	self:RefreshRows()
	self:SetCollapsed(self.Collapsed)
end

function Planner:Attach()
	self:CreateWindow()
	local parent = _G.ProfessionsFrame
	self.Window:ClearAllPoints()
	if parent then
		self.Window:SetPoint("TOPLEFT", parent, "TOPRIGHT", 4, 0)
	else
		self.Window:SetPoint("CENTER")
	end
	self.Window:Show()
	self:RefreshSelection()
	self:BuildRecipeIndex()
	if parent and parent.CraftingPage and parent.CraftingPage.SchematicForm and not self.Hooked then
		local form = parent.CraftingPage.SchematicForm
		if type(form.Init) == "function" then
			hooksecurefunc(form, "Init", function(_, recipeInfo)
				if recipeInfo and recipeInfo.recipeID then Planner.SelectedRecipeID = recipeInfo.recipeID end
				Planner:RefreshSelection()
			end)
		end
		self.Hooked = true
	end
end

local events = CreateFrame("Frame")
events:RegisterEvent("TRADE_SKILL_SHOW")
events:RegisterEvent("TRADE_SKILL_CLOSE")
events:RegisterEvent("BAG_UPDATE_DELAYED")
events:RegisterEvent("AUCTION_HOUSE_SHOW")
events:RegisterEvent("AUCTION_HOUSE_CLOSED")
events:SetScript("OnEvent", function(_, event)
	if event == "TRADE_SKILL_SHOW" then
		C_Timer.After(0, function() Planner:Attach() end)
	elseif event == "TRADE_SKILL_CLOSE" then
		if Planner.Window then Planner.Window:Hide() end
	elseif event == "AUCTION_HOUSE_SHOW" then
		C_Timer.After(0, function() Planner:ShowShoppingList() end)
	elseif event == "AUCTION_HOUSE_CLOSED" then
		if Planner.ShoppingWindow then Planner.ShoppingWindow:Hide() end
	elseif event == "BAG_UPDATE_DELAYED" then
		if Planner.Window and Planner.Window:IsShown() then Planner:RefreshQueue() end
		if Planner.ShoppingWindow and Planner.ShoppingWindow:IsShown() then Planner:RefreshShoppingRows() end
	end
end)

if C_TradeSkillUI.IsTradeSkillReady and C_TradeSkillUI.IsTradeSkillReady() then
	C_Timer.After(0, function() Planner:Attach() end)
end

if AuctionHouseFrame and AuctionHouseFrame:IsShown() then
	C_Timer.After(0, function() Planner:ShowShoppingList() end)
end
