local _, ns = ...
local L = ns.L or {}

local DATA = ns.Data or {}
local PROFESSION_SKILL_LINE_TO_KEY = DATA.PROFESSION_SKILL_LINE_TO_KEY or {}

local CONCENTRATION_REGEN_SECONDS = 360

local CRAFTING_PROFESSION_KEYS = {
    alchemy = true,
    blacksmithing = true,
    enchanting = true,
    engineering = true,
    inscription = true,
    jewelcrafting = true,
    leatherworking = true,
    tailoring = true,
}

local PROFESSION_NAME_PATTERNS = {
    alchemy = { "alchemy", "alchemie" },
    blacksmithing = { "blacksmith", "schmied" },
    enchanting = { "enchant", "verzauber" },
    engineering = { "engineer", "ingenieur" },
    inscription = { "inscription", "inschrift" },
    jewelcrafting = { "jewel", "juwel" },
    leatherworking = { "leather", "leder" },
    tailoring = { "tailor", "schneider", "schneid" },
}

local function plainNumber(value)
    local numberValue = tonumber(value)
    if not numberValue then
        return nil
    end

    return numberValue
end

local function normalizedName(name)
    if type(name) ~= "string" then
        return ""
    end

    return string.lower(name)
end

local function keyFromProfessionName(name)
    local lowered = normalizedName(name)
    if lowered == "" then
        return nil
    end

    for key, patterns in pairs(PROFESSION_NAME_PATTERNS) do
        for _, pattern in ipairs(patterns) do
            if string.find(lowered, pattern, 1, true) then
                return key
            end
        end
    end

    return nil
end

local function getSpellName(spellID)
    spellID = tonumber(spellID)
    if not spellID then
        return nil
    end

    if C_Spell and type(C_Spell.GetSpellName) == "function" then
        local ok, name = pcall(C_Spell.GetSpellName, spellID)
        if ok and type(name) == "string" and name ~= "" then
            return name
        end
    end

    if C_Spell and type(C_Spell.GetSpellInfo) == "function" then
        local ok, info = pcall(C_Spell.GetSpellInfo, spellID)
        if ok then
            if type(info) == "table" and type(info.name) == "string" and info.name ~= "" then
                return info.name
            elseif type(info) == "string" and info ~= "" then
                return info
            end
        end
    end

    return nil
end

local function formatElapsed(seconds)
    seconds = math.max(0, math.floor(tonumber(seconds) or 0))

    local days = math.floor(seconds / 86400)
    local hours = math.floor((seconds % 86400) / 3600)
    local minutes = math.floor((seconds % 3600) / 60)

    if days > 0 then
        return string.format(L.TIME_DAY_HOUR or "%d d %d h", days, hours)
    end

    if hours > 0 then
        return string.format(L.TIME_HOUR_MIN or "%d h %d m", hours, minutes)
    end

    return string.format(L.TIME_MIN or "%d m", minutes)
end

local function formatLastSeen(timestamp)
    timestamp = tonumber(timestamp)
    if not timestamp or timestamp <= 0 then
        return L.TIME_NEVER or "never"
    end

    local delta = time() - timestamp
    if delta < 60 then
        return L.TIME_JUST_NOW or "just now"
    end

    return string.format(L.TIME_AGO or "%s ago", formatElapsed(delta))
end

local ProfessionCooldowns = ns.ProfessionCooldowns or {}
ns.ProfessionCooldowns = ProfessionCooldowns
ProfessionCooldowns.ProfessionSkillLineToKey = PROFESSION_SKILL_LINE_TO_KEY
ProfessionCooldowns.CraftingProfessionKeys = CRAFTING_PROFESSION_KEYS
ProfessionCooldowns.ConcentrationRegenSeconds = CONCENTRATION_REGEN_SECONDS
ProfessionCooldowns.PlainNumber = plainNumber
ProfessionCooldowns.KeyFromProfessionName = keyFromProfessionName
ProfessionCooldowns.FormatElapsed = formatElapsed
ProfessionCooldowns.FormatLastSeen = formatLastSeen
local parseRecipeCooldown = ProfessionCooldowns.ParseRecipeCooldown or function()
    return nil
end

local function addUnique(list, seen, value)
    value = tonumber(value)
    if value and value > 0 and not seen[value] then
        seen[value] = true
        list[#list + 1] = value
    end
end

local function applyTradeSkillInfo(info, raw)
    if type(raw) ~= "table" then
        if type(raw) == "string" and raw ~= "" and not info.name then
            info.name = raw
        end
        return
    end

    if not info.name then
        info.name = raw.professionName
            or raw.parentProfessionName
            or raw.name
            or raw.displayName
    end
    if not info.skillLineID then
        info.skillLineID = plainNumber(raw.skillLineID or raw.parentSkillLineID or raw.professionSkillLineID)
    end

    addUnique(info.professionIDs, info.seenProfessionIDs, raw.professionID)
    addUnique(info.professionIDs, info.seenProfessionIDs, raw.parentProfessionID)
    addUnique(info.professionIDs, info.seenProfessionIDs, raw.skillLineID)
    addUnique(info.professionIDs, info.seenProfessionIDs, raw.parentSkillLineID)
end

local function readOpenTradeSkillInfo()
    local info = {
        professionIDs = {},
        seenProfessionIDs = {},
    }

    if C_TradeSkillUI and type(C_TradeSkillUI.GetTradeSkillLine) == "function" then
        local ok, first = pcall(C_TradeSkillUI.GetTradeSkillLine)
        if ok then
            applyTradeSkillInfo(info, first)
        end
    end

    if C_TradeSkillUI and type(C_TradeSkillUI.GetBaseProfessionInfo) == "function" then
        local ok, baseInfo = pcall(C_TradeSkillUI.GetBaseProfessionInfo)
        if ok then
            applyTradeSkillInfo(info, baseInfo)
        end
    end

    if C_TradeSkillUI and type(C_TradeSkillUI.GetChildProfessionInfo) == "function" then
        local ok, childInfo = pcall(C_TradeSkillUI.GetChildProfessionInfo)
        if ok then
            applyTradeSkillInfo(info, childInfo)
        end
    end

    if (not info.name or info.name == "") and type(GetTradeSkillLine) == "function" then
        local ok, legacyName = pcall(GetTradeSkillLine)
        if ok and type(legacyName) == "string" and legacyName ~= "" then
            info.name = legacyName
        end
    end

    info.seenProfessionIDs = nil
    return info
end

local function resolveProfessionKey(name, skillLineID, record)
    skillLineID = tonumber(skillLineID)
    if skillLineID and PROFESSION_SKILL_LINE_TO_KEY[skillLineID] then
        return PROFESSION_SKILL_LINE_TO_KEY[skillLineID]
    end

    local nameKey = keyFromProfessionName(name)
    if nameKey then
        return nameKey
    end

    if type(record) == "table" and type(record.professions) == "table" then
        local lowered = normalizedName(name)
        for _, profession in ipairs(record.professions) do
            local professionSkillLineID = tonumber(profession and profession.skillLineID)
            local professionKey = professionSkillLineID and PROFESSION_SKILL_LINE_TO_KEY[professionSkillLineID] or nil
            local professionName = profession and profession.name
            local professionLowered = normalizedName(professionName)
            if professionKey
                and lowered ~= ""
                and professionLowered ~= ""
                and (string.find(lowered, professionLowered, 1, true)
                    or string.find(professionLowered, lowered, 1, true))
            then
                return professionKey
            end
        end
    end

    return nil
end

local function resolveProfessionName(professionKey, fallbackName, record)
    if type(record) == "table" and type(record.professions) == "table" then
        for _, profession in ipairs(record.professions) do
            local skillLineID = tonumber(profession and profession.skillLineID)
            if skillLineID
                and PROFESSION_SKILL_LINE_TO_KEY[skillLineID] == professionKey
                and type(profession.name) == "string"
                and profession.name ~= ""
            then
                return profession.name
            end
        end
    end

    if type(fallbackName) == "string" and fallbackName ~= "" then
        return fallbackName
    end

    return professionKey or (L.UNKNOWN or "Unknown")
end

local function readConcentration(info)
    if not (C_TradeSkillUI
        and type(C_TradeSkillUI.GetConcentrationCurrencyID) == "function"
        and C_CurrencyInfo
        and type(C_CurrencyInfo.GetCurrencyInfo) == "function")
    then
        return nil
    end

    for _, professionID in ipairs(info and info.professionIDs or {}) do
        local okCurrency, currencyID = pcall(C_TradeSkillUI.GetConcentrationCurrencyID, professionID)
        currencyID = okCurrency and tonumber(currencyID) or nil
        if currencyID and currencyID > 0 then
            local okInfo, currencyInfo = pcall(C_CurrencyInfo.GetCurrencyInfo, currencyID)
            if okInfo and type(currencyInfo) == "table" then
                local current = plainNumber(currencyInfo.quantity)
                local maximum = plainNumber(currencyInfo.maxQuantity or currencyInfo.maxWeeklyQuantity)
                if current then
                    maximum = maximum or math.max(current, 1000)
                    maximum = math.max(maximum, current)
                    return {
                        currencyID = currencyID,
                        current = math.max(0, math.floor(current + 0.5)),
                        maximum = math.max(0, math.floor(maximum + 0.5)),
                        scanTime = time(),
                    }
                end
            end
        end
    end

    return nil
end

local function getRecipeExpansionInfo(recipeInfo)
    if type(recipeInfo) ~= "table" then
        return nil, nil
    end

    return plainNumber(recipeInfo.expansionID), recipeInfo.expansionName
end

local function ensureCooldownState(record)
    if type(record) ~= "table" then
        return nil
    end

    record.professionCooldowns = type(record.professionCooldowns) == "table" and record.professionCooldowns or {}
    record.professionCooldowns.professions = type(record.professionCooldowns.professions) == "table" and record.professionCooldowns.professions or {}
    return record.professionCooldowns
end

local function scanOpenProfessionCooldowns(record)
    if not (C_TradeSkillUI and type(C_TradeSkillUI.GetAllRecipeIDs) == "function") then
        return type(record) == "table" and record.professionCooldowns or nil
    end

    local info = readOpenTradeSkillInfo()
    local professionKey = resolveProfessionKey(info.name, info.skillLineID, record)
    if not professionKey or not CRAFTING_PROFESSION_KEYS[professionKey] then
        return type(record) == "table" and record.professionCooldowns or nil
    end

    local now = time()
    local state = ensureCooldownState(record)
    if not state then
        return nil
    end

    local professionState = type(state.professions[professionKey]) == "table" and state.professions[professionKey] or {}
    local previousCooldowns = type(professionState.cooldowns) == "table" and professionState.cooldowns or {}
    local concentration = readConcentration(info)
    local professionName = resolveProfessionName(professionKey, info.name, record)

    professionState.professionKey = professionKey
    professionState.professionName = professionName
    professionState.skillLineID = tonumber(info.skillLineID) or professionState.skillLineID
    professionState.updatedAt = now
    if concentration then
        professionState.concentration = concentration
    end

    local okRecipes, recipeIDs = pcall(C_TradeSkillUI.GetAllRecipeIDs)
    if not okRecipes or type(recipeIDs) ~= "table" then
        state.professions[professionKey] = professionState
        state.updatedAt = now
        return state
    end

    local nextCooldowns = {}
    for _, rawRecipeID in ipairs(recipeIDs) do
        local recipeID = tonumber(rawRecipeID)
        if recipeID then
            local okRecipeInfo, recipeInfo = false, nil
            if type(C_TradeSkillUI.GetRecipeInfo) == "function" then
                okRecipeInfo, recipeInfo = pcall(C_TradeSkillUI.GetRecipeInfo, recipeID)
            end
            recipeInfo = okRecipeInfo and recipeInfo or nil

            local learned = true
            if type(recipeInfo) == "table" and recipeInfo.learned == false then
                learned = false
            end

            if learned then
                local cooldownInfo = parseRecipeCooldown(recipeID) or {}
                local previous = previousCooldowns[recipeID] or previousCooldowns[tostring(recipeID)]
                local recipeName = type(recipeInfo) == "table" and recipeInfo.name or nil
                recipeName = recipeName or (type(previous) == "table" and previous.name) or getSpellName(recipeID)

                if recipeName and recipeName ~= "" and (cooldownInfo.hasCooldownCapability or type(previous) == "table") then
                    local expansionID, expansionName = getRecipeExpansionInfo(recipeInfo)
                    nextCooldowns[recipeID] = {
                        recipeID = recipeID,
                        name = recipeName,
                        icon = type(recipeInfo) == "table" and (recipeInfo.iconFileID or recipeInfo.icon) or (type(previous) == "table" and previous.icon) or nil,
                        expansionID = expansionID or (type(previous) == "table" and previous.expansionID) or nil,
                        expansionName = expansionName or (type(previous) == "table" and previous.expansionName) or nil,
                        currentCharges = cooldownInfo.currentCharges,
                        maxCharges = cooldownInfo.maxCharges,
                        durationSeconds = cooldownInfo.durationSeconds or (type(previous) == "table" and previous.durationSeconds) or 0,
                        startTime = cooldownInfo.startTime or 0,
                        readyTime = cooldownInfo.readyTime or 0,
                        isDayCooldown = cooldownInfo.isDayCooldown == true,
                        lastSeen = now,
                    }
                end
            end
        end
    end

    professionState.cooldowns = nextCooldowns
    state.professions[professionKey] = professionState
    state.updatedAt = now
    return state
end

function ns:GetStoredProfessionCooldownState(keyOrCharacter)
    local key = keyOrCharacter
    if type(keyOrCharacter) == "table" then
        key = keyOrCharacter.key
    end

    local record = self:GetStoredCharacterRecord(key, false)
    return type(record) == "table" and type(record.professionCooldowns) == "table" and record.professionCooldowns or nil
end

function ns:RefreshCurrentCharacterProfessionCooldownData()
    local record = self:RefreshCurrentCharacterRecord()
    if not record then
        return nil
    end

    return scanOpenProfessionCooldowns(record) or record.professionCooldowns
end

function ns:GetLiveProfessionCooldownState(keyOrCharacter, fallbackState)
    local key = keyOrCharacter
    if type(keyOrCharacter) == "table" then
        key = keyOrCharacter.key
    end

    if self:IsCurrentCharacterKey(key) then
        local liveState = self:RefreshCurrentCharacterProfessionCooldownData()
        if liveState then
            return liveState
        end
    end

    return self:GetStoredProfessionCooldownState(key) or fallbackState
end
