local addonName, addon = ...

addon.LootTracker = addon.LootTracker or {}
addon.LootTracker.EnglishDBBuilder = addon.LootTracker.EnglishDBBuilder or {}

local LT = addon.LootTracker
local Builder = LT.EnglishDBBuilder
local EnglishNames = LT.EnglishNames
local Data = LT.Data
local Crafting = LT.Crafting
local CraftDatabase = LT.CraftDatabase

Builder.BATCH_SIZE = 30
Builder.BATCH_DELAY = 0.05

local function Print(message)
    DEFAULT_CHAT_FRAME:AddMessage("|cff7fd5ffWhat's Next EN DB:|r " .. tostring(message))
end

local function IsEnglishClient()
    local locale = GetLocale and GetLocale() or ""
    return locale == "enUS" or locale == "enGB", locale
end

local function EnsureSavedState()
    WhatsNextCache = type(WhatsNextCache) == "table" and WhatsNextCache or {}
    WhatsNextCache.englishNames = WhatsNextCache.englishNames or {}
    WhatsNextCache.englishBuilder = WhatsNextCache.englishBuilder or {}
    return WhatsNextCache.englishBuilder
end

local function AddID(ids, seen, value)
    local itemID = tonumber(value)
    if not itemID or itemID <= 0 or seen[itemID] then
        return
    end
    seen[itemID] = true
    ids[#ids + 1] = itemID
end

local function CollectStaticLoot(ids, seen)
    if Data and type(Data.Items) == "table" then
        for itemID in pairs(Data.Items) do
            AddID(ids, seen, itemID)
        end
    end
end

local function CollectCraftedLoot(ids, seen)
    -- The bundled community craft database is the primary source for crafted
    -- equipment. It must be included in the EN builder even when the local
    -- character has never opened or learned those professions.
    if CraftDatabase and type(CraftDatabase.recipes) == "table" then
        for itemID, recipe in pairs(CraftDatabase.recipes) do
            AddID(ids, seen, itemID)
            if type(recipe) == "table" then
                AddID(ids, seen, recipe.itemID)
                if type(recipe.qualityItemIDs) == "table" then
                    for _, qualityItemID in ipairs(recipe.qualityItemIDs) do
                        AddID(ids, seen, qualityItemID)
                    end
                end
            end
        end
    end

    -- Keep the local Blizzard cache only as an additional source.
    if not Crafting then return end

    if Crafting.EnsureBlizzardDatabase then
        Crafting:EnsureBlizzardDatabase()
    end

    if Crafting.GetRecipeCache then
        for _, recipe in pairs(Crafting:GetRecipeCache() or {}) do
            if type(recipe) == "table" then
                AddID(ids, seen, recipe.itemID)
                if type(recipe.qualityItemIDs) == "table" then
                    for _, qualityItemID in ipairs(recipe.qualityItemIDs) do
                        AddID(ids, seen, qualityItemID)
                    end
                end
            end
        end
    end
end

local function CollectTrackedLoot(ids, seen)
    local profiles = WhatsNextUserData
        and WhatsNextUserData.lootTracker
    if type(profiles) ~= "table" then return end

    for characterKey, specs in pairs(profiles) do
        if characterKey ~= "professionRecipes" and characterKey ~= "professionScan" and type(specs) == "table" then
            for _, profile in pairs(specs) do
                if type(profile) == "table" and type(profile.items) == "table" then
                    for _, item in pairs(profile.items) do
                        if type(item) == "table" then
                            AddID(ids, seen, item.itemID)
                        end
                    end
                end
            end
        end
    end
end

function Builder:CollectItemIDs()
    local ids, seen = {}, {}
    CollectStaticLoot(ids, seen)
    CollectCraftedLoot(ids, seen)
    CollectTrackedLoot(ids, seen)
    table.sort(ids)
    return ids
end

local function GetEnglishNameNow(itemID)
    if C_Item and C_Item.GetItemNameByID then
        local name = C_Item.GetItemNameByID(itemID)
        if type(name) == "string" and name ~= "" then
            return name
        end
    end

    if C_Item and C_Item.GetItemInfo then
        local name = C_Item.GetItemInfo(itemID)
        if type(name) == "string" and name ~= "" then
            return name
        end
    end

    if GetItemInfo then
        local name = GetItemInfo(itemID)
        if type(name) == "string" and name ~= "" then
            return name
        end
    end

    return nil
end

function Builder:Remember(itemID, name)
    if not EnglishNames or not EnglishNames.Remember then return false end
    if type(name) ~= "string" or name == "" then return false end
    EnglishNames:Remember(itemID, name)
    self.collected = (self.collected or 0) + 1
    return true
end

function Builder:Finish()
    if not self.running then return end
    self.running = false

    local state = EnsureSavedState()
    local version, build, _, toc = GetBuildInfo()
    state.completedAt = time()
    state.locale = GetLocale and GetLocale() or ""
    state.version = version
    state.build = tonumber(build) or build
    state.interface = tonumber(toc) or toc
    state.total = self.total or 0
    state.collected = self.collected or 0
    state.missing = self.missing or 0

    Print(string.format(
        "готово: %d/%d названий сохранено, отсутствует %d. Теперь выйдите из игры обычным способом, чтобы SavedVariables записались на диск.",
        state.collected, state.total, state.missing
    ))
end

function Builder:ProcessNextBatch()
    if not self.running then return end

    local processed = 0
    while processed < self.BATCH_SIZE and self.index <= self.total do
        local itemID = self.ids[self.index]
        self.index = self.index + 1
        processed = processed + 1

        local name = GetEnglishNameNow(itemID)
        if name then
            self:Remember(itemID, name)
        else
            if C_Item and C_Item.RequestLoadItemDataByID then
                pcall(C_Item.RequestLoadItemDataByID, itemID)
            end
            self.pending[itemID] = true
        end
    end

    local doneInitialPass = self.index > self.total
    if not doneInitialPass then
        C_Timer.After(self.BATCH_DELAY, function()
            Builder:ProcessNextBatch()
        end)
        return
    end

    -- Give the client a short window to finish requested item loads, then retry
    -- every pending ID exactly once. This keeps the builder finite and prevents
    -- any background polling after the explicit build command finishes.
    C_Timer.After(1.0, function()
        if not Builder.running then return end
        local pendingCount = 0
        for itemID in pairs(Builder.pending) do
            local name = GetEnglishNameNow(itemID)
            if name then
                Builder:Remember(itemID, name)
            else
                pendingCount = pendingCount + 1
            end
        end
        Builder.missing = pendingCount
        Builder:Finish()
    end)
end

function Builder:Start()
    if self.running then
        Print("сборка уже выполняется.")
        return
    end

    local isEnglish, locale = IsEnglishClient()
    if not isEnglish then
        Print("Builder работает только на enUS/enGB клиенте. Сейчас клиент: " .. tostring(locale) .. ".")
        return
    end

    local ids = self:CollectItemIDs()
    if #ids == 0 then
        Print("не найдено ни одного itemID для сборки.")
        return
    end

    self.ids = ids
    self.pending = {}
    self.index = 1
    self.total = #ids
    self.collected = 0
    self.missing = 0
    self.running = true

    local state = EnsureSavedState()
    local version, build, _, toc = GetBuildInfo()
    state.startedAt = time()
    state.locale = locale
    state.version = version
    state.build = tonumber(build) or build
    state.interface = tonumber(toc) or toc
    state.total = #ids
    state.completedAt = nil

    Print(string.format("начинаю сбор %d itemID. Это выполняется только по вашей команде.", #ids))
    self:ProcessNextBatch()
end

function Builder:Status()
    local state = EnsureSavedState()
    if self.running then
        local done = math.min((self.index or 1) - 1, self.total or 0)
        Print(string.format("в процессе: %d/%d, сохранено %d.", done, self.total or 0, self.collected or 0))
        return
    end

    if not state.completedAt then
        Print("готовой сборки ещё нет. На enUS/enGB выполните /wn endb build")
        return
    end

    Print(string.format(
        "последняя сборка: locale=%s, WoW=%s, build=%s, сохранено=%s/%s, отсутствует=%s.",
        tostring(state.locale or "?"),
        tostring(state.version or "?"),
        tostring(state.build or "?"),
        tostring(state.collected or 0),
        tostring(state.total or 0),
        tostring(state.missing or 0)
    ))
end

function Builder:ResetSavedEnglishNames()
    WhatsNextUserData = WhatsNextUserData or {}
    WhatsNextCache.englishNames = {}
    WhatsNextCache.englishBuilder = {}
    Print("сохранённая EN-база очищена. Встроенная EnglishNames.Data не изменена.")
end

function Builder:HandleSlash(rest)
    rest = string.lower((rest or ""):match("^%s*(.-)%s*$") or "")
    if rest == "build" or rest == "start" or rest == "" then
        self:Start()
    elseif rest == "status" then
        self:Status()
    elseif rest == "reset" then
        self:ResetSavedEnglishNames()
    else
        Print("команды: /wn endb build, /wn endb status, /wn endb reset")
    end
end
