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

local DATA = ns.Data or {}
local Shared = ns.ProfessionCooldowns or {}
local PROFESSION_SKILL_LINE_TO_KEY = Shared.ProfessionSkillLineToKey or DATA.PROFESSION_SKILL_LINE_TO_KEY or {}
local CRAFTING_PROFESSION_KEYS = Shared.CraftingProfessionKeys or {}
local CONCENTRATION_REGEN_SECONDS = tonumber(Shared.ConcentrationRegenSeconds) or 360

local plainNumber = Shared.PlainNumber or function(value)
    return tonumber(value)
end

local keyFromProfessionName = Shared.KeyFromProfessionName or function()
    return nil
end

local formatElapsed = Shared.FormatElapsed or function(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 formatLastSeen = Shared.FormatLastSeen or function(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 function simulateConcentration(concentration, now)
    if type(concentration) ~= "table" then
        return nil
    end

    local current = plainNumber(concentration.current)
    local maximum = plainNumber(concentration.maximum)
    local scanTime = plainNumber(concentration.scanTime)
    if not current or not maximum or maximum <= 0 then
        return nil
    end

    local gained = 0
    if scanTime and scanTime > 0 then
        gained = math.floor(math.max(0, now - scanTime) / CONCENTRATION_REGEN_SECONDS)
    end

    local estimated = math.min(maximum, math.max(0, math.floor(current + gained + 0.5)))
    return {
        current = math.max(0, math.floor(current + 0.5)),
        estimated = estimated,
        maximum = math.max(0, math.floor(maximum + 0.5)),
        scanTime = scanTime,
        ratio = maximum > 0 and (estimated / maximum) or 0,
        gained = gained,
    }
end

local function simulateCooldown(cooldown, now)
    if type(cooldown) ~= "table" then
        return nil
    end

    local maxCharges = plainNumber(cooldown.maxCharges)
    local currentCharges = plainNumber(cooldown.currentCharges)
    local durationSeconds = math.max(0, plainNumber(cooldown.durationSeconds) or 0)
    local readyTime = math.max(0, plainNumber(cooldown.readyTime) or 0)
    local remaining = math.max(0, readyTime - now)

    if maxCharges and maxCharges > 0 then
        maxCharges = math.floor(maxCharges + 0.5)
        currentCharges = currentCharges and math.floor(currentCharges + 0.5) or (remaining > 0 and math.max(0, maxCharges - 1) or maxCharges)
        currentCharges = math.max(0, math.min(maxCharges, currentCharges))

        if durationSeconds > 0 and readyTime > 0 and now >= readyTime and currentCharges < maxCharges then
            local gained = math.floor((now - readyTime) / durationSeconds) + 1
            currentCharges = math.min(maxCharges, currentCharges + gained)
            if currentCharges < maxCharges then
                readyTime = readyTime + (gained * durationSeconds)
                remaining = math.max(0, readyTime - now)
            else
                remaining = 0
            end
        end

        return {
            ready = currentCharges >= maxCharges,
            remainingSeconds = remaining,
            currentCharges = currentCharges,
            maxCharges = maxCharges,
        }
    end

    return {
        ready = remaining <= 0,
        remainingSeconds = remaining,
    }
end

local function addKnownProfessions(professions, byKey, record)
    if type(record) ~= "table" or type(record.professions) ~= "table" then
        return
    end

    for _, profession in ipairs(record.professions) do
        local skillLineID = tonumber(profession and profession.skillLineID)
        local professionKey = skillLineID and PROFESSION_SKILL_LINE_TO_KEY[skillLineID] or keyFromProfessionName(profession and profession.name)
        if professionKey and CRAFTING_PROFESSION_KEYS[professionKey] and not byKey[professionKey] then
            local entry = {
                professionKey = professionKey,
                professionName = profession.name or professionKey,
                skillLineID = skillLineID,
            }
            professions[#professions + 1] = entry
            byKey[professionKey] = entry
        end
    end
end

local function addStoredProfessions(professions, byKey, state)
    if type(state) ~= "table" or type(state.professions) ~= "table" then
        return
    end

    for professionKey, professionState in pairs(state.professions) do
        if CRAFTING_PROFESSION_KEYS[professionKey] and not byKey[professionKey] then
            local entry = {
                professionKey = professionKey,
                professionName = professionState.professionName or professionKey,
                skillLineID = professionState.skillLineID,
            }
            professions[#professions + 1] = entry
            byKey[professionKey] = entry
        elseif CRAFTING_PROFESSION_KEYS[professionKey] and byKey[professionKey] then
            byKey[professionKey].professionName = byKey[professionKey].professionName or professionState.professionName
            byKey[professionKey].skillLineID = byKey[professionKey].skillLineID or professionState.skillLineID
        end
    end
end

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

    local isCurrentCharacter = self:IsCurrentCharacterKey(key)
    local record = self:GetStoredCharacterRecord(key, false)
    if isCurrentCharacter and type(record) ~= "table" then
        record = self:RefreshCurrentCharacterRecord()
    end

    local state = self:GetLiveProfessionCooldownState(keyOrCharacter, nil)
    local professions = {}
    local professionsByKey = {}
    addKnownProfessions(professions, professionsByKey, record)
    addStoredProfessions(professions, professionsByKey, state)

    table.sort(professions, function(a, b)
        return (a.professionName or "") < (b.professionName or "")
    end)

    local now = time()
    local concentrationRows = {}
    local cooldownRows = {}
    local concentrationKnown = 0
    local cooldownReady = 0
    local cooldownTotal = 0
    local updatedAt = tonumber(type(state) == "table" and state.updatedAt or 0) or 0

    for _, profession in ipairs(professions) do
        local professionState = type(state) == "table"
            and type(state.professions) == "table"
            and state.professions[profession.professionKey]
            or nil
        local concentration = simulateConcentration(professionState and professionState.concentration, now)
        local concentrationText = L.PROFESSION_COOLDOWNS_UNKNOWN_CONCENTRATION or "No concentration data"
        local concentrationTooltip = {
            isCurrentCharacter
                and (L.PROFESSION_COOLDOWNS_OPEN_HINT or "Open a profession window on this character to update cooldowns and concentration.")
                or (L.PROFESSION_COOLDOWNS_ALT_HINT or "Alt values are estimated from that character's last profession scan."),
        }
        local concentrationStatus = "missing"

        if concentration then
            concentrationKnown = concentrationKnown + 1
            concentrationText = string.format("%d/%d", concentration.estimated, concentration.maximum)
            concentrationStatus = concentration.estimated >= concentration.maximum and "complete" or "open"
            concentrationTooltip = {
                string.format(
                    L.PROFESSION_COOLDOWNS_CONCENTRATION_TOOLTIP or "Last scan: %d/%d. Estimated now: %d/%d.",
                    concentration.current,
                    concentration.maximum,
                    concentration.estimated,
                    concentration.maximum
                ),
                string.format(L.HERO_SYNC or "Last sync %s", formatLastSeen(concentration.scanTime)),
            }
            if not isCurrentCharacter then
                concentrationTooltip[#concentrationTooltip + 1] = L.PROFESSION_COOLDOWNS_ALT_HINT or "Alt values are estimated from that character's last profession scan."
            end
        end

        concentrationRows[#concentrationRows + 1] = {
            professionKey = profession.professionKey,
            label = string.format("%s %s", profession.professionName or profession.professionKey, L.PROFESSION_COOLDOWNS_CONCENTRATION or "Concentration"),
            value = concentrationText,
            ratio = concentration and concentration.ratio or 0,
            status = concentrationStatus,
            tooltipTitle = profession.professionName or profession.professionKey,
            tooltipLines = concentrationTooltip,
        }

        if type(professionState) == "table" and type(professionState.cooldowns) == "table" then
            updatedAt = math.max(updatedAt, tonumber(professionState.updatedAt) or 0)
            for _, cooldown in pairs(professionState.cooldowns) do
                local simulated = simulateCooldown(cooldown, now)
                if simulated then
                    cooldownTotal = cooldownTotal + 1
                    if simulated.ready then
                        cooldownReady = cooldownReady + 1
                    end

                    local valueText
                    if simulated.maxCharges and simulated.maxCharges > 1 then
                        valueText = string.format("%d/%d", simulated.currentCharges or 0, simulated.maxCharges)
                    elseif simulated.ready then
                        valueText = L.PROFESSION_COOLDOWNS_READY or "Ready"
                    else
                        valueText = formatElapsed(simulated.remainingSeconds)
                    end

                    local status = simulated.ready and "complete" or "locked"
                    if not simulated.ready and (tonumber(simulated.remainingSeconds) or 0) <= 3600 then
                        status = "warning"
                    end

                    local tooltipLines = {
                        string.format("%s: %s", L.SUBTAB_SYSTEMS_PROFESSIONS or "Professions", profession.professionName or profession.professionKey),
                    }
                    if simulated.maxCharges then
                        tooltipLines[#tooltipLines + 1] = string.format("%d/%d %s", simulated.currentCharges or 0, simulated.maxCharges, L.PROFESSION_COOLDOWNS_CHARGES or "charges")
                    end
                    if not simulated.ready then
                        tooltipLines[#tooltipLines + 1] = string.format("%s: %s", L.PROFESSION_COOLDOWNS_READY_IN or "Ready in", formatElapsed(simulated.remainingSeconds))
                    end
                    tooltipLines[#tooltipLines + 1] = string.format(L.HERO_SYNC or "Last sync %s", formatLastSeen(cooldown.lastSeen or updatedAt))

                    cooldownRows[#cooldownRows + 1] = {
                        label = string.format("%s - %s", profession.professionName or profession.professionKey, cooldown.name or tostring(cooldown.recipeID)),
                        ready = simulated.ready == true,
                        remainingSeconds = simulated.remainingSeconds or 0,
                        entry = {
                            status = status,
                            text = valueText,
                            completed = simulated.ready == true,
                            seen = true,
                            tooltipTitle = cooldown.name or tostring(cooldown.recipeID),
                            tooltipLines = tooltipLines,
                        },
                    }
                end
            end
        end
    end

    table.sort(cooldownRows, function(a, b)
        if a.ready ~= b.ready then
            return a.ready == true
        end
        if (a.remainingSeconds or 0) ~= (b.remainingSeconds or 0) then
            return (a.remainingSeconds or 0) < (b.remainingSeconds or 0)
        end
        return (a.label or "") < (b.label or "")
    end)

    return {
        updatedAt = updatedAt,
        professions = professions,
        concentrationRows = concentrationRows,
        cooldownRows = cooldownRows,
        summary = {
            concentrationKnown = concentrationKnown,
            concentrationTotal = #concentrationRows,
            cooldownReady = cooldownReady,
            cooldownTotal = cooldownTotal,
            updatedAt = updatedAt,
        },
    }
end
