--ProfessionGuides_Core.lua
local PT = AhUI
if not PT then return end

PT.ProfessionGuides = PT.ProfessionGuides or {}
local G = PT.ProfessionGuides
-- =====================================================
-- Racial profession bonuses
-- =====================================================

PT.ProfessionGuides_RacialBonuses = {
    [6]  = { [182] = 5 },        -- Tauren - Herbalism
    [7]  = { [202] = 5 },        -- Gnome - Engineering
    [9]  = { [171] = 5 },        -- Goblin - Alchemy
    [10] = { [333] = 5 },        -- Blood Elf - Enchanting
    [11] = { [755] = 5 },        -- Draenei - Jewelcrafting
    [22] = { [393] = 5 },        -- Worgen - Skinning
    [24] = { [185] = 5 },        -- Pandaren (A) - Cooking
    [25] = { [185] = 5 },        -- Pandaren (H) - Cooking
    [27] = { [773] = 5 },        -- Nightborne - Inscription
    [28] = { [186] = 5 },        -- Highmountain Tauren - Mining
    [30] = { [164] = 5 },        -- Lightforged Draenei - Blacksmithing
    [32] = { ALL = 2 },          -- Kul Tiran - ALL professions
    [34] = { [164] = 5 },        -- Dark Iron Dwarf - Blacksmithing
}

-- =====================================================
-- State
-- =====================================================

G.state = {
    selectedSkillLineID = nil,
    --shoppingList = {},
    manualStepIndex = nil, -- индекс шага, если выбран вручную
	selectedProfession = nil,
}

function G:SetSkillLine(skillLineID)
    self.state.selectedSkillLineID = skillLineID
    self.state.manualStepIndex = nil
end

function G:SetProfession(prof)
    self.state.selectedProfession = prof
    self.state.selectedSkillLineID = nil -- сбрасываем эпоху, т.к. профа сменилась
    self.state.manualStepIndex = nil
end

function G:GetSelectedProfession()
    return self.state.selectedProfession
end

function G:GetSelectedSkillLine()
    return self.state.selectedSkillLineID
end
-- =====================================================
-- Skill helpers (racial bonuses)
-- =====================================================

function G:GetRacialBonusForSkillLine(skillLineID)
    if not skillLineID then return 0 end

    local parentID = self:GetParentProfessionID(skillLineID)
    if not parentID then return 0 end

    local raceID = select(3, UnitRace("player"))
    if not raceID then return 0 end

	local raceBonuses = PT.ProfessionGuides_RacialBonuses[raceID]
	if not raceBonuses then return 0 end

	-- култирасец: бонус ко всем профессиям
	if raceBonuses.ALL then
		return raceBonuses.ALL
	end

	return raceBonuses[parentID] or 0

end

function G:GetEffectiveSkillLevel(skillLineID)
    if not skillLineID then
        return 0, 0, 0
    end

    if not C_TradeSkillUI
       or not C_TradeSkillUI.GetProfessionInfoBySkillLineID
    then
        return 0, 0, 0
    end

    local info = C_TradeSkillUI.GetProfessionInfoBySkillLineID(skillLineID)
    if not info then
        return 0, 0, 0
    end

    local realSkill = info.skillLevel or 0
    local bonus = self:GetRacialBonusForSkillLine(skillLineID) or 0

    return math.max(0, realSkill - bonus), realSkill, bonus
end


function G:GetSkillForStep(skillLineID, step)
    local effective, real = self:GetEffectiveSkillLevel(skillLineID)

    -- по умолчанию ВСЕГДА реальный skill (с расой)
    if step and step.useEffectiveSkill then
        return effective
    end

    return real
end

-- =====================================================
-- Guides storage
-- =====================================================

G.guides = {}

-- =====================================================
-- Core logic
-- =====================================================

function G:GetCurrentStepBySkillLine(skillLineID)
self:ValidateManualStep(skillLineID)

	if self.state.manualStepIndex then
		local steps, guide = self:GetStepsBySkillLine(skillLineID)
		local idx = self.state.manualStepIndex

		if steps and steps[idx] then
			return steps[idx], false, guide
		end
	end

		if not skillLineID then return end

	local effectiveSkill, realSkill =
		self:GetEffectiveSkillLevel(skillLineID)

	for _, expansion in pairs(self.guides) do
		for _, guide in pairs(expansion) do
			if guide.skillLineID == skillLineID then

				for _, step in ipairs(guide.steps) do
					local skillForStep = self:GetSkillForStep(skillLineID, step)

					if skillForStep >= step.min and skillForStep < step.max then
						return step, skillForStep, guide
					end
				end


			end
		end
	end
end	

function G:GetRequiredRecipeItemForStep(step)
    if not step or not step.recipes then return nil end

    for _, recipe in ipairs(step.recipes) do
        if recipe.requires then
            for _, req in ipairs(recipe.requires) do
                if req.type == "recipe_item" then
                    return req.itemID
                end
            end
        end
    end

    return nil
end

function G:GetRecipeVendor(skillLineID, recipeItemID)
    local guide = G:GetGuideBySkillLine(skillLineID)
    if not guide or not guide.vendors then return nil end

    local faction =
        UnitFactionGroup("player") == "Alliance"
        and "alliance"
        or "horde"

    local vendors = guide.vendors[faction]
    if not vendors then return nil end

    for _, vendor in ipairs(vendors) do
        if vendor.sells then
            for _, item in ipairs(vendor.sells) do
                if item.itemID == recipeItemID then
                    return vendor
                end
            end
        end
    end

    return nil
end

function G:GetRecipeVendorForStep(skillLineID, step)
    if not step or not step.recipes then return nil end

    local guide = self:GetGuideBySkillLine(skillLineID)
    if not guide or not guide.vendors then return nil end

    local faction =
        UnitFactionGroup("player") == "Alliance"
        and "alliance"
        or "horde"

    local vendors = guide.vendors[faction]
    if not vendors then return nil end

    for _, recipe in ipairs(step.recipes) do
        if recipe.requires then
            for _, req in ipairs(recipe.requires) do

                -- 📜 РЕЦЕПТ-ПРЕДМЕТ
                if req.type == "recipe_item" and req.itemID then
                    if recipe.spellID and self:IsRecipeLearned(recipe.spellID) then
                        return nil
                    end
                    return self:GetRecipeVendor(skillLineID, req.itemID)
                end

                -- 🎓 ОБУЧЕНИЕ У ТРЕНЕРА (НАЗЖАТАР И Т.П.)
                if req.type == "recipe_spell" and req.spellID then
                    if self:IsRecipeLearned(req.spellID) then
                        return nil
                    end

                    for _, vendor in ipairs(vendors) do
                        if vendor.teaches then
                            for _, t in ipairs(vendor.teaches) do
                                if t.spellID == req.spellID then
                                    return vendor
                                end
                            end
                        end
                    end
                end

            end
        end
    end

    return nil
end

function G:GetRemainingCraftsForStep(skillLineID, step)
    if not step or not step.min or not step.max then
        return step and step.crafts or 0
    end

    local skill = self:GetSkillForStep(skillLineID, step)
    if type(skill) ~= "number" then
        return step.crafts or 0
    end

    local done  = math.max(0, skill - step.min)
    local total = step.max - step.min

    return math.max(0, total - done)
end

function G:GetStepsBySkillLine(skillLineID)
    if not skillLineID then return end

    for _, expansion in pairs(self.guides) do
        for _, guide in pairs(expansion) do
            if guide.skillLineID == skillLineID then
                return guide.steps, guide
            end
        end
    end
end

function G:GetGuideBySkillLine(skillLineID)
    if not skillLineID then return end

    for _, expansion in pairs(self.guides) do
        for _, guide in pairs(expansion) do
            if guide.skillLineID == skillLineID then
                return guide
            end
        end
    end
end

function G:IsGuideCompleted(skillLineID)
    if not skillLineID then return false end

    local guide = self:GetGuideBySkillLine(skillLineID)
    if not guide or not guide.steps or #guide.steps == 0 then
        return false
    end

    local lastStep = guide.steps[#guide.steps]
    if not lastStep or not lastStep.max then
        return false
    end

    local profInfo =
        C_TradeSkillUI
        and C_TradeSkillUI.GetProfessionInfoBySkillLineID
        and C_TradeSkillUI.GetProfessionInfoBySkillLineID(skillLineID)

	local skill = self:GetSkillForStep(skillLineID, lastStep)

	if type(skill) ~= "number" then
		return false
	end

	return skill >= lastStep.max

end

function G:GetCurrentStepIndex(skillLineID)
    if self.state.manualStepIndex then
        return self.state.manualStepIndex
    end

    local steps = self:GetStepsBySkillLine(skillLineID)
    if not steps then return end

    for i, step in ipairs(steps) do
        local skill = self:GetSkillForStep(skillLineID, step)

        if skill >= step.min and skill < step.max then
            return i
        end
    end
end

function G:GetAutoStepIndex(skillLineID)
	if not skillLineID then return end

	local steps = self:GetStepsBySkillLine(skillLineID)
	if not steps then return end

	for i, step in ipairs(steps) do
		local skill = self:GetSkillForStep(skillLineID, step)

		if skill >= step.min and skill < step.max then
			return i
		end
	end
end

function G:ValidateManualStep(skillLineID)
    if not self.state.manualStepIndex then return end

    local steps = self:GetStepsBySkillLine(skillLineID)
    if not steps then
        self.state.manualStepIndex = nil
        return
    end

    -- ручной режим сбрасываем только если индекс вне диапазона
    if not steps[self.state.manualStepIndex] then
        self.state.manualStepIndex = nil
    end
end

function G:StepPrev()
    local skillLineID = self:GetSelectedSkillLine()
    if not skillLineID then return end

    local steps = self:GetStepsBySkillLine(skillLineID)
    if not steps then return end

    local idx = self.state.manualStepIndex
    or self:GetCurrentStepIndex(skillLineID)
    or 1

    if idx > 1 then
        self.state.manualStepIndex = idx - 1
    end
end

function G:StepNext()
    local skillLineID = self:GetSelectedSkillLine()
    if not skillLineID then return end

    local steps = self:GetStepsBySkillLine(skillLineID)
    if not steps then return end

    local idx = self.state.manualStepIndex
    or self:GetCurrentStepIndex(skillLineID)
    or 1

    if idx < #steps then
        self.state.manualStepIndex = idx + 1
    end
end

function G:ResetManualStep()
    self.state.manualStepIndex = nil
end

function G:GetTotalMaterials(skillLineID)
    local totals = {}

    local startIndex =
        self.state.manualStepIndex
        or self:GetCurrentStepIndex(skillLineID)
        or 1

    for _, expansion in pairs(self.guides) do
        for _, guide in pairs(expansion) do
            if guide.skillLineID == skillLineID then

                for idx, step in ipairs(guide.steps) do

                    -- ❌ пропускаем пройденные шаги
                    -- ❌ шаги без shopping
                    if idx >= startIndex
                       and not step.disablesShopping
                       and step.recipes
                    then
                        for _, recipe in ipairs(step.recipes) do
                            if recipe.reagents and recipe.crafts then
                                for _, r in ipairs(recipe.reagents) do
                                    if not r.crafted then

                                        -- ===============================
                                        -- anyOf → считаем все грейды
                                        -- ===============================
                                        if r.anyOf then
                                            local need = (r.count or 0) * recipe.crafts
                                            local have = 0

                                            for _, opt in ipairs(r.anyOf) do
                                                have = have + (GetItemCount(opt.itemID, true) or 0)
                                            end

                                            local missing = math.max(0, need - have)

                                            if missing > 0 then
                                                for _, opt in ipairs(r.anyOf) do
                                                    totals[opt.itemID] =
                                                        (totals[opt.itemID] or 0) + missing
                                                end
                                            end

                                        -- ===============================
                                        -- обычный реагент
                                        -- ===============================
                                        else
                                            totals[r.itemID] =
                                                (totals[r.itemID] or 0)
                                                + (r.count * recipe.crafts)
                                        end
                                    end
                                end
                            end
                        end
                    end

                end
            end
        end
    end

    return totals
end


function G:GetItemCountTotal(itemID)
    if not itemID then return 0 end
    return GetItemCount(itemID, true) or 0
end

function G:BuildShoppingList(skillLineID)
    local out = {}

    local materials = self:GetTotalMaterials(skillLineID)
    if not materials then return end

    for itemID, needCount in pairs(materials) do
        local haveCount = GetItemCount(itemID, true) or 0
        local toBuy = needCount - haveCount

        if toBuy > 0 then
            out[itemID] = toBuy
        end
    end

    return out
end
-- =====================================================
-- Persistence helpers
-- =====================================================

function G:ExportState()
    return {
        skillLineID     = self.state.selectedSkillLineID,
        manualStepIndex = self.state.manualStepIndex,
		profession = self.state.selectedProfession,

    }
end

function G:ImportState(data)
    if not data then return end

    self.state.selectedSkillLineID = data.skillLineID
    self.state.manualStepIndex     = data.manualStepIndex
	self.state.selectedProfession = data.profession

end
-- =====================================================
-- UI helpers (safe to call from UI)
-- =====================================================

function G:GetPlayerFaction()
    local f = UnitFactionGroup("player")
    return f and f:lower()
end

function G:IsProfessionOpenForSkillLine(skillLineID)
    if not skillLineID then return false end
    if not ProfessionsFrame or not ProfessionsFrame:IsShown() then
        return false
    end

    if not C_TradeSkillUI
       or not C_TradeSkillUI.GetProfessionInfoBySkillLineID
    then
        return false
    end

    local info = C_TradeSkillUI.GetProfessionInfoBySkillLineID(skillLineID)
    if not info or not info.parentProfessionID then
        return false
    end

    local p1, p2 = GetProfessions()
    for _, slot in ipairs({ p1, p2 }) do
        if slot then
            local _, _, _, _, _, _, baseSkillLine =
                GetProfessionInfo(slot)

            if baseSkillLine == info.parentProfessionID then
                return true
            end
        end
    end

    return false
end

function G:GetParentProfessionID(skillLineID)
    if not skillLineID then return end

    if not C_TradeSkillUI
       or not C_TradeSkillUI.GetProfessionInfoBySkillLineID
    then
        return
    end

    local info = C_TradeSkillUI.GetProfessionInfoBySkillLineID(skillLineID)
    return info and info.parentProfessionID
end

function G:IsProfessionWindowOpen(skillLineID)
    if not skillLineID then return false end
    return self:IsProfessionOpenForSkillLine(skillLineID)
end

function G:GetProfessionIcon(skillLineID)
    local parentID = self:GetParentProfessionID(skillLineID)
    if not parentID then return end

    local p1, p2 = GetProfessions()
    for _, slot in ipairs({ p1, p2 }) do
        if slot then
            local _, icon, _, _, _, _, baseSkillLine = GetProfessionInfo(slot)
            if baseSkillLine == parentID then
                return icon
            end
        end
    end
end

-- =====================================================
-- Recipe (trainer) helpers
-- =====================================================

function G:IsRecipeLearned(spellID)
    if not spellID then return false end

    if not C_TradeSkillUI
       or not C_TradeSkillUI.GetRecipeInfo
    then
        return false
    end

    local info = C_TradeSkillUI.GetRecipeInfo(spellID)
    if not info then
        return false
    end

    return info.learned == true
end

function G:GetMaxCraftsByMaterials(recipe)
    if not recipe or not recipe.reagents then
        return 0
    end
    -- ❗ для альтернативных реагентов нельзя считать
    if recipe.reagentsAny then
        return 0
    end

    local maxCrafts = math.huge

	for _, r in ipairs(recipe.reagents) do
		if r.anyOf then
			local maxHave = 0

			for _, opt in ipairs(r.anyOf) do
				local have = GetItemCount(opt.itemID, true) or 0
				maxHave = math.max(maxHave, have)
			end

			local can = math.floor(maxHave / (r.count or 1))
			maxCrafts = math.min(maxCrafts, can)
		else
			local have = GetItemCount(r.itemID, true) or 0
			local can = math.floor(have / (r.count or 1))
			maxCrafts = math.min(maxCrafts, can)
		end
	end

    if maxCrafts == math.huge then
        return 0
    end

    return maxCrafts
end

function G:GetStepNoteRefs(step)
    if not step or not step.noteRefs then
        return nil
    end

    local refs = {}

    for _, ref in ipairs(step.noteRefs) do
        if ref.type == "item" and ref.itemID then
            table.insert(refs, {
                type   = "item",
                itemID = ref.itemID,
                action = ref.action,
                count  = ref.count,
            })

        elseif ref.type == "spell" and ref.spellID then
            table.insert(refs, {
                type    = "spell",
                spellID = ref.spellID,
                action  = ref.action,
            })
        end
    end

    return refs
end
