-- @author DoomTero 2026
-- @copyright 2026 DoomTero - ZomboRut original work. ref:f4e645c1
-- @license Proprietary. Not licensed for reuse, redistribution or derivative works without written permission from DoomTero. Ask - permission is obtainable. See ZomboRut/LICENSE.txt

require "ZomboRut/ZomboRutScene"
local ZomboRutScene = require "ZomboRut/ZomboRutScene"

local hasTrait = function(p, name)
    return ZR_Trait and ZR_Trait.has and ZR_Trait.has(p, name)
end

local function patchWashSpeed()
    if ISWashYourself == nil or ISWashYourself.getDuration == nil then return end
    if ISWashYourself._zrLovesCleanPatched then return end
    ISWashYourself._zrLovesCleanPatched = true

    local origGetDuration = ISWashYourself.getDuration
    function ISWashYourself:getDuration()
        local d = origGetDuration(self)
        local c = self.character
        if c == nil then return d end
        if hasTrait(c, "lovesClean") then return d * 0.5  end
        if hasTrait(c, "neatTidy")   then return d * 0.75 end
        return d
    end

    if ISWashYourself.perform then
        local origPerform = ISWashYourself.perform
        function ISWashYourself:perform(...)
            origPerform(self, ...)
            pcall(function()
                local c = self.character
                if c and ZR_ZombieCloak and ZR_ZombieCloak.isActive
                   and ZR_ZombieCloak.isActive(c) then
                    ZR_ZombieCloak.deactivate(c)
                end
            end)
        end
    end
end

Events.OnGameStart.Add(patchWashSpeed)

local FILTH_CLEAN_MAX = 0.3
local FILTH_LIGHT_MAX = 2.0
local FILTH_DIRTY_MAX = 5.0

local DELTA = {
    unkempt = {
        clean   = {  0.5,  0.0 },
        lightly = {  0.0,  0.0 },
        dirty   = { -0.3,  0.0 },
        filthy  = { -0.5,  0.0 },
    },
    neatTidy = {
        clean   = { -0.5, -0.3 },
        lightly = {  0.3,  0.0 },
        dirty   = {  1.0,  0.3 },
        filthy  = {  1.5,  0.5 },
    },
    lovesClean = {
        clean   = { -1.0, -0.5 },
        lightly = {  0.5,  0.2 },
        dirty   = {  1.5,  0.5 },
        filthy  = {  2.5,  1.0 },
    },
}

local function computeFilth(player)
    if not player or not player.getHumanVisual then return 0 end
    local visual = player:getHumanVisual()
    if visual == nil then return 0 end
    local total = 0
    local maxIdx = BloodBodyPartType.MAX:index()
    for i = 1, maxIdx do
        local part = BloodBodyPartType.FromIndex(i - 1)
        if part then
            total = total + (visual:getBlood(part) or 0) + (visual:getDirt(part) or 0)
        end
    end
    return total
end

local function classifyFilth(score)
    if score < FILTH_CLEAN_MAX then return "clean"   end
    if score < FILTH_LIGHT_MAX then return "lightly" end
    if score < FILTH_DIRTY_MAX then return "dirty"   end
    return "filthy"
end

local function bumpStat(stats, statKey, delta)
    if delta == 0 then return end
    local stat = CharacterStat[statKey]; if stat == nil then return end
    local scaled = delta * (stat:getMaximumValue() / 100.0)
    stats:set(stat, stats:get(stat) + scaled)
end

local function hygieneMoodTick()
    local player = getPlayer()
    if player == nil or player:isDead() then return end
    if player.isNpc and player:isNpc() then return end

    local trait
    if     hasTrait(player, "unkempt")    then trait = "unkempt"
    elseif hasTrait(player, "neatTidy")   then trait = "neatTidy"
    elseif hasTrait(player, "lovesClean") then trait = "lovesClean"
    else return end

    local stats = player:getStats(); if stats == nil then return end

    local state = classifyFilth(computeFilth(player))
    local d     = DELTA[trait][state]
    if d == nil then return end

    bumpStat(stats, "STRESS",      d[1])
    bumpStat(stats, "UNHAPPINESS", d[2])
end

Events.EveryHours.Add(hygieneMoodTick)

local SEX_CONTACT_PARTS    = { "Groin", "Torso_Lower", "Hand_L", "Hand_R" }
local DEFEAT_GROUND_PARTS  = { "Head",  "Foot_L", "Foot_R", "Back", "Neck" }
local DEFEAT_BLOOD_PARTS   = { "Hand_L", "Hand_R" }
local CORPSE_BLOOD_PARTS   = { "Torso_Lower", "Groin", "Hand_L", "Hand_R" }

local DIRT_PER_SCENE       = 0.20
local DIRT_DEFEAT_GROUND   = 0.30
local BLOOD_DEFEAT_PARTNER = 0.20
local BLOOD_CORPSE_PARTNER = 0.40

local function addDirt(visual, partName, amount)
    local part = BloodBodyPartType[partName]; if part == nil then return end
    local cur = visual:getDirt(part) or 0
    local target = cur + amount
    if target > 1 then target = 1 end
    pcall(function() visual:setDirt(part, target) end)
end

local function addBlood(visual, partName, amount)
    local part = BloodBodyPartType[partName]; if part == nil then return end
    local cur = visual:getBlood(part) or 0
    local target = cur + amount
    if target > 1 then target = 1 end
    pcall(function() visual:setBlood(part, target) end)
end

local function dirtyAfterScene(player, active, result)
    if result ~= "Released" then return end
    if player == nil or not player.getHumanVisual then return end
    if active and active.cfg and active.cfg.isUtility then return end
    local visual = player:getHumanVisual(); if visual == nil then return end

    for _, p in ipairs(SEX_CONTACT_PARTS) do
        addDirt(visual, p, DIRT_PER_SCENE)
    end

    local isDefeat = (active and active.cfg and active.cfg.isDefeat) == true
    local isCorpse = active and not active.liveOriginal

    if isDefeat then
        for _, p in ipairs(DEFEAT_GROUND_PARTS) do
            addDirt(visual, p, DIRT_DEFEAT_GROUND)
        end
        for _, p in ipairs(DEFEAT_BLOOD_PARTS) do
            addBlood(visual, p, BLOOD_DEFEAT_PARTNER)
        end
        if (active.zrWasVirgin == true or hasTrait(player, "Virgin"))
            and not active.cfg.isOral
        then
            addBlood(visual, "Groin", BLOOD_DEFEAT_PARTNER)
        end
    end

    if isCorpse then
        for _, p in ipairs(CORPSE_BLOOD_PARTS) do
            addBlood(visual, p, BLOOD_CORPSE_PARTNER)
        end
    end

    pcall(function()
        if player.resetModelNextFrame then player:resetModelNextFrame() end
        if sendHumanVisual            then sendHumanVisual(player)      end
    end)
end

ZomboRutScene.registerSceneEndCallback(dirtyAfterScene)
