ZO_CraftAdvisorManager = ZO_CallbackObject:Subclass()

local DEFAULT_SELECTED_QUEST_INDEX = 1
function ZO_CraftAdvisorManager:New(...)
    local manager = ZO_CallbackObject.New(self)
    manager:Initialize(...)
    return manager
end

function ZO_CraftAdvisorManager:Initialize()
    self.craftingInteractionType = CRAFTING_TYPE_INVALID
    self.questMasterList = {}
    self.selectedMasterListIndex = DEFAULT_SELECTED_QUEST_INDEX
    EVENT_MANAGER:RegisterForEvent("ZO_CraftAdvisorManager", EVENT_CRAFTING_STATION_INTERACT, function(eventCode, craftingType, sameStation, craftingMode)
        --If we are at a different type of crafting station, we need to update the list
        --Otherwise, if anything has changed, we will have already attempted to refresh via the quest events
        --We also need to refresh if the player goes to a new smithing station, since they might be changing to a new set station
        --We always need to refresh the list if we are interacting with a consolidated station, as the available sets at the station could have changed
        if craftingMode == CRAFTING_INTERACTION_MODE_CONSOLIDATED_STATION or craftingType ~= self.craftingInteractionType or (not sameStation and IsSmithingCraftingType(craftingType)) then
            self.craftingInteractionType = craftingType
            self:RefreshQuestMasterList()
        end
    end)

    local function UpdateQuestConditions(questIndex, mainStepChanged)
        if mainStepChanged then
            self:RefreshQuestMasterList()
        end
        local currentlySelectedQuest = self.questMasterList[self.selectedMasterListIndex]
        if currentlySelectedQuest and currentlySelectedQuest.questIndex == questIndex then
            self:UpdateQuestConditionInfo()
            --Tell the craft advisor that it needs to refresh the quest display
            self:FireCallbacks("SelectedQuestConditionsUpdated")
        end
    end

    --Register for the various quest change events
    EVENT_MANAGER:RegisterForEvent("ZO_CraftAdvisorManager", EVENT_QUEST_CONDITION_COUNTER_CHANGED, function(_, questIndex) UpdateQuestConditions(questIndex) end)
    EVENT_MANAGER:RegisterForEvent("ZO_CraftAdvisorManager", EVENT_QUEST_ADVANCED, function(_, questIndex, questName, isPushed, isComplete, mainStepChanged) UpdateQuestConditions(questIndex, mainStepChanged) end)
    EVENT_MANAGER:RegisterForEvent("ZO_CraftAdvisorManager", EVENT_ACHIEVEMENT_UPDATED, function()
        --We need to register for this event to detect if a new translation has been discovered
        --Presumably, this can only happen while enchanting, so if the enchanting scene is not showing, we shouldn't care
        if ZO_Enchanting_IsSceneShowing() then
            self:UpdateQuestConditionInfo()
            self:FireCallbacks("SelectedQuestConditionsUpdated")
        end 
    end)

    --We need to rebuild when the inventory changes so we can make sure we display the proper messaging if there are missing materials/runes/reagents/etc
    EVENT_MANAGER:RegisterForEvent("ZO_CraftAdvisorManager", EVENT_INVENTORY_FULL_UPDATE, function() self:FireCallbacks("SelectedQuestConditionsUpdated") end)
    EVENT_MANAGER:RegisterForEvent("ZO_CraftAdvisorManager", EVENT_INVENTORY_SINGLE_SLOT_UPDATE, function() self:FireCallbacks("SelectedQuestConditionsUpdated") end)

    --We need to rebuild when we learn a new recipe so we can make sure we display the proper messaging for missing recipes
    EVENT_MANAGER:RegisterForEvent("ZO_CraftAdvisorManager", EVENT_RECIPE_LEARNED, function() self:FireCallbacks("SelectedQuestConditionsUpdated") end)
    EVENT_MANAGER:RegisterForEvent("ZO_CraftAdvisorManager", EVENT_MULTIPLE_RECIPES_LEARNED, function() self:FireCallbacks("SelectedQuestConditionsUpdated") end)

    --We need to rebuild if the player's solvent proficiency changed or they got the Laboratory Use skill, to ensure the error messaging refreshes
    EVENT_MANAGER:RegisterForEvent("ZO_CraftAdvisorManager", EVENT_NON_COMBAT_BONUS_CHANGED, function(eventCode, nonCombatBonusType)
        if nonCombatBonusType == NON_COMBAT_BONUS_ALCHEMY_THIRD_SLOT or nonCombatBonusType == NON_COMBAT_BONUS_ALCHEMY_LEVEL then
            self:FireCallbacks("SelectedQuestConditionsUpdated")
        end
    end)

    --We need to rebuild when the active set changes since it will result in different patterns being available
    EVENT_MANAGER:RegisterForEvent("ZO_CraftAdvisorManager", EVENT_CONSOLIDATED_STATION_ACTIVE_SET_UPDATED, function()
        self:UpdateQuestConditionInfo()
        self:FireCallbacks("SelectedQuestConditionsUpdated")
    end)

    --We need to rebuild the whole list when the unlocked consolidated sets change, as it could impact what quests are valid at this station
    EVENT_MANAGER:RegisterForEvent("ZO_CraftAdvisorManager", EVENT_CONSOLIDATED_STATION_SETS_UPDATED, function() self:RefreshQuestMasterList() end)

    QUEST_JOURNAL_MANAGER:RegisterCallback("QuestListUpdated", function() self:RefreshQuestMasterList() end)
end

function ZO_CraftAdvisorManager:HasActiveWrits()
    return self.questMasterList and #self.questMasterList > 0
end

do
    --Only bother with quest types that might have crafting quests in them
    local QUEST_TYPES_WITH_CRAFTING_QUESTS =
    {
        [QUEST_TYPE_CRAFTING] = true,
        [QUEST_TYPE_HOLIDAY_EVENT] = true,
    }

    function ZO_CraftAdvisorManager:RefreshQuestMasterList()
        --Grab the current quest information from the journal
        local quests = QUEST_JOURNAL_MANAGER:GetQuestList()

        --Clear out the current quest pin data
        self:FireCallbacks("QuestInformationUpdated", {patternIndices = {}, materialIndex = nil, traitId = nil, styleId = nil, recipeItemIds = {}, runeIds = {}, alchemyInfo = {}, improvementInfo = {}})
        self.selectedMasterListIndex = DEFAULT_SELECTED_QUEST_INDEX 
        ZO_ClearTable(self.questMasterList)

        --Filter out any non-crafting quests from the list
        for i, questInfo in ipairs(quests) do
            if QUEST_TYPES_WITH_CRAFTING_QUESTS[questInfo.questType] then
                local conditionCount = select(5, GetJournalQuestStepInfo(questInfo.questIndex, QUEST_MAIN_STEP_INDEX))
                local conditionInfo = {}
                for conditionIndex = 1, conditionCount do
                    local conditionType = select(8, GetJournalQuestConditionInfo(questInfo.questIndex, QUEST_MAIN_STEP_INDEX, conditionIndex))

                    if conditionType == QUEST_CONDITION_TYPE_GATHER_ITEM or conditionType == QUEST_CONDITION_TYPE_CRAFT_ITEM then
                        local itemId, materialItemId, craftingType, itemFunctionalQuality = GetQuestConditionItemInfo(questInfo.questIndex, QUEST_MAIN_STEP_INDEX, conditionIndex)
                        --If any of the condition crafting types match the current interaction type, we want to include this quest
                        local shouldShowQuest = (craftingType == self.craftingInteractionType)

                        --Provisioning is special in that in can be done at any crafting station type, so we need to do a slightly different check for it
                        if craftingType == CRAFTING_TYPE_PROVISIONING then
                            local craftingStationType = GetRecipeInfoFromItemId(itemId)
                            shouldShowQuest = (craftingStationType == self.craftingInteractionType)
                        end

                        if shouldShowQuest then
                            local data = 
                            {
                                conditionIndex = conditionIndex,
                                itemId = itemId,
                                materialItemId = materialItemId,
                                craftingType = craftingType,
                                itemFunctionalQuality = itemFunctionalQuality,
                                isMasterWrit = false,
                            }
                            table.insert(conditionInfo, data)
                        end
                    elseif conditionType == QUEST_CONDITION_TYPE_CRAFT_RANDOM_WRIT_ITEM then
                        local itemId, materialItemId, craftingType, itemFunctionalQuality, itemTemplateId, itemSetId, itemTraitType, itemStyleId, encodedAlchemyTraits = GetQuestConditionMasterWritInfo(questInfo.questIndex, QUEST_MAIN_STEP_INDEX, conditionIndex)
                        --If any of the condition crafting types match the current interaction type, we want to include this quest
                        local shouldShowQuest = (craftingType == self.craftingInteractionType)

                        --Provisioning is special in that in can be done at any crafting station type, so we need to do a slightly different check for it
                        if craftingType == CRAFTING_TYPE_PROVISIONING then
                            local craftingStationType = GetRecipeInfoFromItemId(itemId)
                            shouldShowQuest = (craftingStationType == self.craftingInteractionType)
                        end

                        if shouldShowQuest then
                            local data = 
                            {
                                conditionIndex = conditionIndex,
                                itemId = itemId,
                                materialItemId = materialItemId,
                                craftingType = craftingType,
                                itemFunctionalQuality = itemFunctionalQuality,
                                itemTemplateId = itemTemplateId,
                                itemSetId = itemSetId,
                                itemTraitType = itemTraitType,
                                itemStyleId = itemStyleId,
                                encodedAlchemyTraits = encodedAlchemyTraits,
                                isMasterWrit = true,
                            }

                            --Smithing master writs should only show up at their specific set station
                            if IsSmithingCraftingType(craftingType) then
                                if CanSpecificSmithingItemSetPatternBeCraftedHere(itemSetId) then
                                    table.insert(conditionInfo, data)
                                end
                            else
                                table.insert(conditionInfo, data)
                            end
                        end
                    end
                end

                if #conditionInfo > 0 then
                    questInfo.conditionData = conditionInfo
                    table.insert(self.questMasterList, questInfo)
                end
            end
        end
        self:FireCallbacks("QuestMasterListUpdated", self.questMasterList)
    end
end

function ZO_CraftAdvisorManager:GetMissingMessage(conditionInfo, currentCount, maxCount)
    --If we have already met the condition requirements, we no longer care about what components we have
    if currentCount < maxCount then
        if conditionInfo.craftingType == CRAFTING_TYPE_ENCHANTING then
            local potencyRune, essenceRune, aspectRune = GetRunesForItemIdIfKnown(conditionInfo.itemId, conditionInfo.materialItemId, conditionInfo.itemFunctionalQuality)
            --GetRunesForItemIdIfKnown will return nil for all values if any of the runes are unknown
            --Therefore, checking any of them for nil would be sufficient, it doesn't have to be potency
            if potencyRune == nil then
                return GetString(SI_ENCHANTING_UNKNOWN_RUNES), GetString(SI_CRAFT_ADVISOR_UNKNOWN_RUNES_TOOLTIP)
            elseif not DoesPlayerHaveRunesForEnchanting(aspectRune, essenceRune, potencyRune) then
                return GetString(SI_CRAFTING_MISSING_ITEMS), GetString(SI_CRAFT_ADVISOR_ENCHANTING_MISSING_ITEMS_TOOLTIP)
            end
        elseif conditionInfo.craftingType == CRAFTING_TYPE_PROVISIONING then
            local recipeLists = PROVISIONER_MANAGER:GetRecipeListData(self.craftingInteractionType)
            --Look for a matching recipe
            for listIndex, recipeList in pairs(recipeLists) do
                for _, recipe in ipairs(recipeList.recipes) do
                    --If we have a match, then we're done, return early
                    if recipe.resultItemId == conditionInfo.itemId then
                        return
                    end
                end
            end

            --If we get here, that means we are missing the recipe
            return GetString(SI_PROVISIONER_MISSING_RECIPE), GetString(SI_CRAFT_ADVISOR_PROVISIONING_MISSING_RECIPE_TOOLTIP)
        elseif conditionInfo.craftingType == CRAFTING_TYPE_ALCHEMY then
            local validCombinationFound = false
            local needsThirdSlot = conditionInfo.isMasterWrit and GetNonCombatBonus(NON_COMBAT_BONUS_ALCHEMY_THIRD_SLOT) == 0

            --Check and see if the alchemy logic has found any valid combinations
            if IsInGamepadPreferredMode() then   
                validCombinationFound = GAMEPAD_ALCHEMY:HasValidCombinationForQuest()
            else
                validCombinationFound = ALCHEMY:HasValidCombinationForQuest()
            end

            if needsThirdSlot then
                return GetString(SI_ALCHEMY_REQUIRES_THIRD_SLOT), GetString(SI_CRAFT_ADVISOR_ALCHEMY_REQUIRES_THIRD_SLOT_TOOLTIP)
            elseif not validCombinationFound then
                return GetString(SI_ALCHEMY_MISSING_OR_UNKNOWN), GetString(SI_CRAFT_ADVISOR_ALCHEMY_MISSING_OR_UNKNOWN_TOOLTIP)
            end
        end
    end
end

function ZO_CraftAdvisorManager:UpdateQuestConditionInfo()
    local questInfo = self.questMasterList[self.selectedMasterListIndex]
    local craftingQuestIndices = 
    {
        patternIndices = {},
        materialIndex = nil,
        traitId = nil,
        styleId = nil,
        recipeItemIds = {},
        runeIds = {},
        alchemyInfo = {},
        improvementInfo = {},
    }

    if questInfo then
        --Determine the pattern and material information for each relevant condition
        for _, conditionInfo in ipairs(questInfo.conditionData) do
            if IsSmithingCraftingType(conditionInfo.craftingType) then
                local _, curCount, maxCount = GetJournalQuestConditionInfo(questInfo.questIndex, QUEST_MAIN_STEP_INDEX, conditionInfo.conditionIndex)

                --If this is a master writ, we need different information from normal ones
                if conditionInfo.isMasterWrit then
                    if curCount < maxCount then
                        local patternIndex, materialIndex, desiredItemId = GetSmithingPatternInfoForItemSet(conditionInfo.itemTemplateId, conditionInfo.itemSetId, conditionInfo.materialItemId, conditionInfo.itemTraitType)
                        if patternIndex and materialIndex then
                            craftingQuestIndices.patternIndices[patternIndex] = true
                            craftingQuestIndices.materialIndex = materialIndex
                            craftingQuestIndices.traitId = conditionInfo.itemTraitType
                            craftingQuestIndices.styleId = conditionInfo.itemStyleId
                            craftingQuestIndices.hasPatterns = true
                        end

                        if desiredItemId and conditionInfo.itemFunctionalQuality and conditionInfo.materialItemId and conditionInfo.itemTraitType and conditionInfo.itemStyleId then
                            craftingQuestIndices.improvementInfo =
                            {
                                desiredItemId = desiredItemId,
                                desiredQuality = conditionInfo.itemFunctionalQuality,
                                desiredMaterial = conditionInfo.materialItemId,
                                desiredTrait = conditionInfo.itemTraitType,
                                desiredStyle = conditionInfo.itemStyleId,
                            }
                            craftingQuestIndices.hasItemToImproveForWrit = HasItemToImproveForWrit(desiredItemId, conditionInfo.materialItemId, conditionInfo.itemTraitType, conditionInfo.itemStyleId, conditionInfo.itemFunctionalQuality)
                        end

                        if IsConsolidatedSmithingItemSetIdUnlocked(conditionInfo.itemSetId) then
                            craftingQuestIndices.consolidatedItemSetId = conditionInfo.itemSetId
                        end
                    end
                else
                    local patternIndex, materialIndex = GetSmithingPatternInfoForItemId(conditionInfo.itemId, conditionInfo.materialItemId, conditionInfo.craftingType)
                    if patternIndex and materialIndex and curCount < maxCount then
                        craftingQuestIndices.patternIndices[patternIndex] = true
                        craftingQuestIndices.materialIndex = materialIndex
                        craftingQuestIndices.hasPatterns = true
                    elseif curCount < maxCount then
                        craftingQuestIndices.smithingItemId = conditionInfo.itemId
                    end
                end
            elseif conditionInfo.craftingType == CRAFTING_TYPE_PROVISIONING then
                local _, curCount, maxCount = GetJournalQuestConditionInfo(questInfo.questIndex, QUEST_MAIN_STEP_INDEX, conditionInfo.conditionIndex)
                if curCount < maxCount then
                    craftingQuestIndices.recipeItemIds[conditionInfo.itemId] = true
                    craftingQuestIndices.hasRecipesForQuest = true
                end
            elseif conditionInfo.craftingType == CRAFTING_TYPE_ENCHANTING then
                local _, curCount, maxCount = GetJournalQuestConditionInfo(questInfo.questIndex, QUEST_MAIN_STEP_INDEX, conditionInfo.conditionIndex)
                local potencyRune, essenceRune, aspectRune = GetRunesForItemIdIfKnown(conditionInfo.itemId, conditionInfo.materialItemId, conditionInfo.itemFunctionalQuality)
                if potencyRune and essenceRune and aspectRune and curCount < maxCount then
                    craftingQuestIndices.runeIds = 
                    {
                        potency = potencyRune,
                        essence = essenceRune,
                        aspect = aspectRune
                    }
                else
                    craftingQuestIndices.runeIds = 
                    {
                        hasRequiredGlyph = curCount >= maxCount
                    }
                end
            elseif conditionInfo.craftingType == CRAFTING_TYPE_ALCHEMY then
                local _, curCount, maxCount = GetJournalQuestConditionInfo(questInfo.questIndex, QUEST_MAIN_STEP_INDEX, conditionInfo.conditionIndex)

                --If this is a master writ, we need different alchemy information than for normal writs
                if conditionInfo.isMasterWrit then
                    if conditionInfo.encodedAlchemyTraits and curCount < maxCount then
                        craftingQuestIndices.alchemyInfo =
                        {
                            basePotionItemId = conditionInfo.itemId,
                            materialItemId = conditionInfo.materialItemId,
                            encodedTraits = conditionInfo.encodedAlchemyTraits,
                            isMasterWrit = true
                        }
                    else
                        craftingQuestIndices.alchemyInfo =
                        {
                            hasDesiredPotion = curCount >= maxCount
                        }
                    end
                else
                    local desiredTraitId = GetTraitIdFromBasePotion(conditionInfo.itemId)
                    if desiredTraitId ~= 0 and curCount < maxCount then
                        craftingQuestIndices.alchemyInfo =
                        {
                            basePotionItemId = conditionInfo.itemId,
                            materialItemId = conditionInfo.materialItemId,
                            desiredTrait = desiredTraitId
                        }
                    else
                        craftingQuestIndices.alchemyInfo =
                        {
                            hasDesiredPotion = curCount >= maxCount
                        }
                    end
                end
            end
        end
    end

    --Send out the updated data necessary for the quest pins
    self:FireCallbacks("QuestInformationUpdated", craftingQuestIndices)
end

function ZO_CraftAdvisorManager:OnSelectionChanged(questIndex)
    for i, questInfo in ipairs(self.questMasterList) do
        --Locate the newly selected quest
        if questInfo.questIndex == questIndex then
            self.selectedMasterListIndex = i
            self:UpdateQuestConditionInfo()
            return
        end
    end
end

function ZO_CraftAdvisorManager:ShouldDeferRefresh()
    return self.craftingInteractionType == CRAFTING_TYPE_ALCHEMY
end

CRAFT_ADVISOR_MANAGER = ZO_CraftAdvisorManager:New()
