-- DST_Helpers.lua
-- Build 42.x — Shared helpers for Skill Tooltip mods
-- Provides global melee proficiency stats (damage, speed, crit, block, injury, strain).
require "API/DST_Options"

local DST = rawget(_G, "DST") or {}
DST.SkillTooltips = DST.SkillTooltips or {}
local ST = DST.SkillTooltips
DST.Helpers = DST.Helpers or {}
local HELP = DST.Helpers

-----------------------------------------------------------------------
-- COLORS
-----------------------------------------------------------------------
ST.COLORS = {
    greyedout   = { r = 0.6, g = 0.6, b = 0.6 },
    leafgreen   = { r = 0.25, g = 0.75, b = 0.25 },
    orange      = { r = 1.0, g = 0.55, b = 0.15 },
    yellow      = { r = 1.0, g = 1.0, b = 0.0 },
    white       = { r = 1.0, g = 1.0, b = 1.0 },
}

-----------------------------------------------------------------------
-- Clamp / small utils
-----------------------------------------------------------------------
function HELP.clampLevel(level)
    if type(level) ~= "number" then return 1 end
    local n = math.floor(level)
    if n < 1 then return 1 end
    if n > 10 then return 10 end
    return n
end

--- Format seconds nicely (e.g., "0.25", "1.5", "2").
--- Uses two decimals then trims trailing zeros and dot.
---@param seconds number
---@return string
function HELP.formatSeconds(seconds)
    local s = string.format("%.2f", seconds or 0)
    s = s:gsub("(%..-)0+$", "%1"):gsub("%.$", "")
    return s
end

local function _fmtMult(mult) return string.format("%.1f", tonumber(mult) or 0) end
local function _abs(n) return math.abs(tonumber(n) or 0) end
local function _sgn(n)
    n = tonumber(n) or 0
    if n > 0 then return 1 elseif n < 0 then return -1 else return 0 end
end

-- Map actual skill level -> proficiency table row
-- Axe uses level as-is (rows 1..10).
-- All non-Axe melee skills use (level - 1), clamped to 0..9 per wiki note.
local function _profLevel(skillKey, level)
    local L = tonumber(level) or 1
    if skillKey == "Axe" then
        if L < 1 then return 1 elseif L > 10 then return 10 else return L end
    else
        local p = L - 1
        if p < 0 then return 0 elseif p > 9 then return 9 else return p end
    end
end


-----------------------------------------------------------------------
-- Weapon core stats (global defaults from Proficiency table)
-----------------------------------------------------------------------

-- Base damage multiplier (all melee skills)
-- Formula (empirical B42): 0.3 + 0.1 * level → L1=0.4 … L10=1.3
function ST.getWeaponSkillMult(level)
    local idx = DST.Helpers.clampLevel(level)
    return 0.3 + (0.1 * idx)
end

-- Positive muscle strain reduction per level (%). Minus sign is handled in localization.
local _WEAPON_STRAIN_POS = {7.5, 15, 22.5, 30, 37.5, 45, 52.5, 60, 67.5, 75}

-- Global defaults from Proficiency level table (L1..L10):
local GLOBAL_DEFAULTS = {
    speed  = { 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 },
    crit   = { 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 },
    block  = { 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 },
    injury = { -2, 0, 1, 2, 3, 4, 5, 5, 6, 7 }, -- signed
}
ST._weaponStatOverrides = ST._weaponStatOverrides or {}

--- Per-skill overrides (use to replace defaults for a specific skill)
function ST.setWeaponStatOverrides(skillKey, spec)
    if type(skillKey) ~= "string" or skillKey == "" or type(spec) ~= "table" then return end
    ST._weaponStatOverrides[skillKey] = spec
end

--- Resolve stats for a given skill+level (overrides > global defaults)
function ST.getWeaponStats(skillKey, level)
    -- Actual player level for STRAIN (no shift)
    local idxActual = DST.Helpers.clampLevel(level)

    -- Proficiency row for tables:
    -- Axe: 1..10; others: (level-1) clamped to 0..9
    local pLevel = _profLevel(skillKey, level)

    -- Choose source: per-skill overrides > global defaults
    local spec = ST._weaponStatOverrides[skillKey]
    local src  = (type(spec) == "table") and spec or GLOBAL_DEFAULTS

    -- Base damage multiplier uses proficiency row (shifted for non-Axe)
    local mult = 0.3 + (0.1 * pLevel)

    local out = {
        mult   = mult,
        strain = _WEAPON_STRAIN_POS[idxActual],
    }

    -- Speed/Crit/Block from table: if prof row is 0, treat as 0% (omit the line)
    if pLevel > 0 then
        if src.speed  then out.speed  = src.speed[pLevel]  end
        if src.crit   then out.crit   = src.crit[pLevel]   end
        if src.block  then out.block  = src.block[pLevel]  end
        if src.damage then out.damage = src.damage[pLevel] end -- only if you explicitly provided one
    end

    -- Injury: special handling at row 0 is -5% per the table
    --[[
    if src.injury then
        local val = (pLevel == 0) and -5 or (src.injury[pLevel] or 0)
        local mag = _abs(val)
        if mag > 0 then
            out.injury_mag = mag
            out.injury_sgn = _sgn(val)
        end
        -- exactly zero → omit the injury line
    end]]

    return out
end

--- Append standardized core lines to any melee skill tooltip.
function ST.addWeaponCoreLines(skillKey)
    if type(skillKey) ~= "string" or skillKey == "" then return end

    ST.addContributor(skillKey, function(ctx)
        local lvl = DST.Helpers.clampLevel(ctx.level)
        local s   = ST.getWeaponStats(skillKey, lvl)

        -- Header
        ctx.addHeader(ST.getText("IGUI_DST_Core_hdr"))

        -- Base damage multiplier
        -- (keeps existing formatting helper; if desired we can move _fmtMult into DST.Helpers later)
        ctx.add(ST.getText("IGUI_DST_Weapon_val_BaseMult", _fmtMult(s.mult)))

        ctx.add(ST.getText("IGUI_DST_Weapon_val_MuscleStrain", tostring(s.strain)))

        if s.speed then
            ctx.add(ST.getText("IGUI_DST_Weapon_val_Speed", tostring(s.speed)))
        end
        if s.crit then
            ctx.add(ST.getText("IGUI_DST_Weapon_val_Crit", tostring(s.crit)))
        end
        if s.block then
            ctx.add(ST.getText("IGUI_DST_Weapon_val_Block", tostring(s.block)))
        end

        --[[  Injury lines (kept intact; still optional)
        if s.injury_mag and s.injury_mag > 0 then
            if s.injury_sgn < 0 then
                ctx.add(ST.getText("IGUI_DST_Weapon_val_InjuryNeg", tostring(s.injury_mag)))
            elseif s.injury_sgn > 0 then
                ctx.add(ST.getText("IGUI_DST_Weapon_val_InjuryPos", tostring(s.injury_mag)))
            end
        end
        ]]
    end)
end

function ST.addWeaponBruiserBonus(skillKey)
    ST.addContributor(skillKey, function(ctx)
        local lvl = DST.Helpers.clampLevel(ctx.level)

        -- Damage bonus thresholds:
        --   L3–L6: +10%
        --   L7–L10: +20%
        local dmg
        if lvl >= 7 then
            dmg = 20
        elseif lvl >= 3 then
            dmg = 10
        end
        if dmg then
            ctx.addHeader(ST.getText("IGUI_DST_Weapon_BruiserBonus"))
            ctx.add(ST.getText("IGUI_DST_Weapon_BruiserBonus_val_Damage", tostring(dmg)))
        end

        -- Attack range bonus:
        --   L7–L10: +20%
        if lvl >= 7 then
            ctx.add(ST.getText("IGUI_DST_Weapon_BruiserBonus_val_Range", tostring(20)))
        end

        -- Endurance cost reduction:
        --   L8–L10: -10% (minus sign handled in localization)
        if lvl >= 8 then
            ctx.add(ST.getText("IGUI_DST_Weapon_BruiserBonus_val_Endurance", tostring(10)))
        end
    end)
end

function ST.addAutoLearnRecipes(skillKey)
    ST.addContributor(skillKey, function(ctx)
        local OPS = DST and DST.Options
        if not (OPS and OPS.showAutoLearnRecipes and OPS.showAutoLearnRecipes:getValue()) then return end

        local cap = OPS.getEffectiveAutoLearnCap()
        if cap == 0 then cap = nil end
        -- DebugLog.log(DebugType.General, "[DST] Autolearn Cap: " .. tostring(cap))

        local headerKey, lines = DST.Unlocks.getAutoLearnedFor(skillKey, ctx.level, nil, {
            maxLines     = cap,
            namesPerLine = 1,
            cumulative   = ctx.cumulative == true,
        })

        if lines and #lines > 0 then
            ctx.addHeader(ST.getText(headerKey))
            ctx.addMany(lines)
        end
    end)
end

function ST.addUnlockCraftRecipes(skillKey)
    ST.addContributor(skillKey, function(ctx)
        local OPS = DST and DST.Options
        if not (OPS and OPS.showUnlockCraftRecipes and OPS.showUnlockCraftRecipes:getValue()) then return end

        local cap = OPS.getEffectiveCraftCap()
        if cap == 0 then cap = nil end
        -- DebugLog.log(DebugType.General, "[DST] Craft Cap: " .. tostring(cap))

        if limit == 0 then limit = nil end
        local headerKey, unlocks = DST.Unlocks.getCraftUnlocksBase(skillKey, ctx.level, {
            maxLines     = cap,
            namesPerLine = 1,
            includeCoreq = true,
            cumulative   = ctx.cumulative == true,
        })

        if unlocks and #unlocks > 0 then
            ctx.addHeader(ST.getText(headerKey))
            ctx.addMany(unlocks)
        end
    end)
end

function ST.addUnlockBuildRecipes(skillKey)
    ST.addContributor(skillKey, function(ctx)
        local OPS = DST and DST.Options
        if not (OPS and OPS.showUnlockBuildRecipes and OPS.showUnlockBuildRecipes:getValue()) then return end

        local cap = OPS.getEffectiveBuildCap()
        if cap == 0 then cap = nil end
        -- DebugLog.log(DebugType.General, "[DST] Build Cap: " .. tostring(cap))

        local headerKey, unlocks = DST.Unlocks.getBuildableUnlocksBase(skillKey, ctx.level, {
            maxLines     = cap,
            namesPerLine = 1,
            includeCoreq = true,
            cumulative   = ctx.cumulative == true,
        })

        if unlocks and #unlocks > 0 then
            ctx.addHeader(ST.getText(headerKey))
            ctx.addMany(unlocks)
        end
    end)
end

_G.DST = DST
return DST
