local _, ns = ...

ns.FishMetrics = {}
local FishMetrics = ns.FishMetrics

local function hashText(text)
    local h = 5381
    text = tostring(text or "")
    for i = 1, #text do
        h = (h * 33 + string.byte(text, i)) % 2147483647
    end
    return h
end

local function roll01(seed)
    seed = (seed * 1103515245 + 12345) % 2147483647
    return seed, (seed % 100000) / 100000
end

local function clamp(value, minVal, maxVal)
    if value < minVal then return minVal end
    if value > maxVal then return maxVal end
    return value
end

local function round1(value)
    value = tonumber(value) or 0
    return math.floor((value * 10) + 0.5) / 10
end

local function getItemIDFromLink(itemLink)
    if type(itemLink) ~= "string" then return 0 end
    local id = itemLink:match("item:(%d+)")
    return tonumber(id) or 0
end

local randomSeeded = false
local function ensureRandomSeed()
    if randomSeeded then return end
    local now = (GetServerTime and GetServerTime()) or time()
    local perf = (debugprofilestop and debugprofilestop()) or 0
    math.randomseed(math.floor(now + perf))
    math.random(); math.random(); math.random()
    randomSeeded = true
end

local function getFishRow(itemLink, displayName)
    local fishDB = ns.FishDB
    if not fishDB then return nil end

    local itemID = getItemIDFromLink(itemLink)
    if itemID > 0 and fishDB.byItemID and fishDB.byItemID[itemID] then
        return fishDB.byItemID[itemID]
    end

    local cleanName = ns.Utils and ns.Utils.CleanDisplayText and ns.Utils:CleanDisplayText(displayName) or tostring(displayName or "")
    local key = string.lower(cleanName or "")
    if fishDB.byName and fishDB.byName[key] then
        return fishDB.byName[key]
    end
    return nil
end

local function deriveRange(baseValue, explicitMin, explicitMax, minFactor, maxFactor, floorValue)
    local minVal = tonumber(explicitMin)
    local maxVal = tonumber(explicitMax)
    local base = tonumber(baseValue) or 0

    if minVal and maxVal and maxVal >= minVal then
        return round1(minVal), round1(maxVal)
    end

    if base <= 0 then
        return nil, nil
    end

    minVal = math.max(base * (minFactor or 0.75), floorValue or 0.1)
    maxVal = math.max(base * (maxFactor or 1.25), minVal + 0.1)
    return round1(minVal), round1(maxVal)
end

function FishMetrics:GetRangesForRow(row)
    if not row then return nil, nil, nil, nil end

    local minW, maxW = deriveRange(
        row.weightLb,
        row.minWeightLb,
        row.maxWeightLb,
        0.70,
        1.35,
        0.2
    )
    local minL, maxL = deriveRange(
        row.sizeIn,
        row.minSizeIn,
        row.maxSizeIn,
        0.75,
        1.25,
        4.0
    )

    return minW, maxW, minL, maxL
end

function FishMetrics:Roll(itemLink, displayName, catchNumber)
    local cleanName = ns.Utils and ns.Utils.CleanDisplayText and ns.Utils:CleanDisplayText(displayName) or tostring(displayName or "Unknown Catch")
    local itemID = getItemIDFromLink(itemLink)
    local catchN = tonumber(catchNumber) or 1

    local speciesSeed = hashText(string.format("%d:%s", itemID, cleanName))
    local rollSeed = speciesSeed + (catchN * 97)

    local seed1, r1 = roll01(rollSeed)
    local _, r2 = roll01(seed1)

    local fishRow = getFishRow(itemLink, cleanName)
    if fishRow then
        local minW, maxW, minL, maxL = self:GetRangesForRow(fishRow)
        if minW and maxW and minL and maxL then
            ensureRandomSeed()
            local rw = math.random()
            local rl = math.random()
            local weightLb = minW + (rw * (maxW - minW))
            local sizeIn = minL + (rl * (maxL - minL))
            return round1(weightLb), round1(sizeIn)
        end
    end

    local baseWeight = 0.8 + ((speciesSeed % 220) / 10)   -- 0.8 - 22.7 lb
    local variance = 0.72 + (r1 * 0.66)                  -- 0.72 - 1.38
    local weightLb = clamp(baseWeight * variance, 0.5, 60)

    local speciesLengthBias = 7 + ((speciesSeed % 45) / 10) -- +7.0 - +11.4 in
    local sizeIn = (math.pow(weightLb, 0.33) * 9.5) + speciesLengthBias + (r2 * 1.7)
    sizeIn = clamp(sizeIn, 6, 80)

    return round1(weightLb), round1(sizeIn)
end

function FishMetrics:Format(displayName, weightLb, sizeIn)
    local base = ns.Utils and ns.Utils.CleanDisplayText and ns.Utils:CleanDisplayText(displayName) or tostring(displayName or "Unknown Catch")
    return string.format("%s [%0.1f lb, %0.1f in]", base, weightLb, sizeIn)
end

function FishMetrics:GetLabel(itemLink, displayName, catchNumber)
    local w, s = self:Roll(itemLink, displayName, catchNumber)
    return self:Format(displayName, w, s), w, s
end
