local PT = AhUI
if not PT then return end

PT.ProfessionGuides = PT.ProfessionGuides or {}
local G = PT.ProfessionGuides

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
}

G.state = {
    selectedSkillLineID = nil,
    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

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)

    if step and step.useEffectiveSkill then
        return effective
    end

    return real
end

G.guides = {}

G.PROFESSION_ICONS = {
    [171] = 4620669, -- Alchemy
    [164] = 4620670, -- Blacksmithing
    [333] = 4620672, -- Enchanting
    [202] = 4620673, -- Engineering
    [773] = 4620676, -- Inscription
    [755] = 4620677, -- Jewelcrafting
    [165] = 4620678, -- Leatherworking
    [197] = 4620681, -- Tailoring
    [185] = 4620671, -- Cooking
}

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.recipeSource == "quest" then
            return nil
        end

        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.recipes or not step.recipes[1] then
        return 0
    end

    local recipe = step.recipes[1]
    local totalCrafts = recipe.crafts or 0
    if totalCrafts <= 0 then
        return 0
    end

    local skill = self:GetSkillForStep(skillLineID, step)
    if type(skill) ~= "number" then
        return totalCrafts
    end

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

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

function G:GetPlannedCraftsForRecipe(skillLineID, step, recipe)
    if not step or not recipe then
        return 0
    end

    if step.onlyStepShopping
       and step.recipes
       and #step.recipes > 1
    then
        return math.max(0, recipe.crafts or 0)
    end

    local remaining = self:GetRemainingCraftsForStep(skillLineID, step) or 0
    if remaining <= 0 then
        return 0
    end

    if recipe.crafts and recipe.crafts > 0 then
        return math.min(recipe.crafts, remaining)
    end

    return remaining
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

    if not startIndex then
        local step = self:GetCurrentStepBySkillLine(skillLineID)
        if step then
            local steps = self:GetStepsBySkillLine(skillLineID)
            for i, s in ipairs(steps or {}) do
                if s == step then
                    startIndex = i
                    break
                end
            end
        end
    end

    startIndex = startIndex 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
                    if idx >= startIndex
                       and not step.disablesShopping
                       and step.recipes
                    then
                        for _, recipe in ipairs(step.recipes) do
                            if recipe.reagents then

								local crafts = self:GetPlannedCraftsForRecipe(skillLineID, step, recipe)

                                if crafts > 0 then
                                    for _, r in ipairs(recipe.reagents) do
                                        if not r.crafted then

                                            if r.anyOf then
                                                local need = (r.count or 0) * crafts

                                                if r.isMilling then
                                                    for _, opt in ipairs(r.anyOf) do
                                                        totals[opt.itemID] =
                                                            (totals[opt.itemID] or 0) + need
                                                    end
                                                else
                                                    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
                                                        local first = r.anyOf[1]
                                                        if first and first.itemID then
                                                            totals[first.itemID] =
                                                                (totals[first.itemID] or 0) + missing
                                                        end
                                                    end
                                                end

                                            else
                                                totals[r.itemID] =
                                                    (totals[r.itemID] or 0)
                                                    + ((r.count or 0) * crafts)
                                            end
                                        end
                                    end
                                end
                            end
                        end
                    end
                end

            end
        end
    end

    return totals
end

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

    local step = self:GetCurrentStepBySkillLine(skillLineID)
    if not step or not step.recipes then
        return totals
    end

    for _, recipe in ipairs(step.recipes) do
        local crafts = self:GetPlannedCraftsForRecipe(skillLineID, step, recipe)

        if crafts > 0 and recipe.reagents then
            for _, r in ipairs(recipe.reagents) do
                if not r.crafted then
                    if r.anyOf then
                        local need = (r.count or 0) * crafts

                        if r.isMilling then
                            for _, opt in ipairs(r.anyOf) do
                                totals[opt.itemID] =
                                    (totals[opt.itemID] or 0) + need
                            end
                        else
                            local first = r.anyOf[1]
                            if first then
                                totals[first.itemID] =
                                    (totals[first.itemID] or 0) + need
                            end
                        end
                    else
                        totals[r.itemID] =
                            (totals[r.itemID] or 0)
                            + ((r.count or 0) * crafts)
                    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 = {}

    if not skillLineID then return end

    local step = self:GetCurrentStepBySkillLine(skillLineID)

    local materials

    if step and step.onlyStepShopping then
        materials = self:GetMaterialsForCurrentStep(skillLineID)
    else
        materials = self:GetTotalMaterials(skillLineID)
    end

    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

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

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)
    return info ~= nil
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)
    if not skillLineID then return end

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

    return self.PROFESSION_ICONS[parentID]
end

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,
            })

        elseif ref.type == "npc"
           and ref.mapID
           and ref.x
           and ref.y
        then
            table.insert(refs, {
                type  = "npc",
                npcID = ref.npcID,
                name  = ref.name,
                mapID = ref.mapID,
                x     = ref.x,
                y     = ref.y,
            })
        end
    end

    return refs
end
