-- Dependencies
require "TimedActions/ISBaseTimedAction"
require "TimedActions/ISTimedActionQueue"
-- подключение api самого мода, чтобы видеть всякое типо связывания
local ZomboLewd = require("ZomboLewd/ZomboLewd")

-- Optional zombie-handler if the related mod is active
if getActivatedMods():contains("ZombolewdDefeat") then
    require "Zombolewd/ZomboLewdZombieHandler"
end

-- AnimationHandler: collects event modules and registered callbacks
-- В 42.13 добавлен complete, куда переехала большая часть функций из perform
local AnimationHandler = {
    EventMarkerModules = {},
    ActionEvents = {
        Perform      = {},
        Complete      = {},
        WaitToStart  = {},
        Start        = {},
        Stop         = {},
        Update       = {}
    }
}

-- Aliases for global tables
local ISBaseTimedAction    = ISBaseTimedAction
local ISTimedActionQueue   = ISTimedActionQueue
local luautils             = luautils
local ignoredKeyframeNames = {}

-- Derive our custom action
local ISAnimationAction = ISBaseTimedAction:derive("ISZomboLewdAnimationAction")

--------------------------------------------------------------------------------
-- Global helper: temporarily disable player inputs (direction, aiming, movement)
-- Change: attached to AnimationHandler instead of a stray global function.
--------------------------------------------------------------------------------
function AnimationHandler.selfDisable(player, disable)
    DebugLog.log("[Zombolewd] /AnimationHandler.selfDisable/")
    player:setIgnoreInputsForDirection(disable)
    player:setAuthorizeMeleeAction(not disable)
    player:setIgnoreAimingInput(disable)
    player:setCanShout(not disable)
    player:setBlockMovement(disable)
end

--------------------------------------------------------------------------------
-- Play: orchestrates one scene across multiple actors
--------------------------------------------------------------------------------
function AnimationHandler.Play(worldObjects, actors, animationData, disableCancel, disableWalk, callbacks)
    DebugLog.log("[Zombolewd] /AnimationHandler.Play/")
    

    disableWalk   = disableWalk or false
    disableCancel = disableCancel or false

    -- Must have at least one actor
    if #actors < 1 then
        DebugLog.log("Error: AnimationHandler.Play requires ≥1 actor.")
        return
    end

    -- First actor cannot be a zombie
    if actors[1]:isZombie() then
        DebugLog.log("Error: 1st actor cannot be a zombie.")
        return
    end

    -- If second actor is a zombie, skip the walk-to-phase
    if actors[2] and actors[2]:isZombie() then
        disableWalk = true
        DebugLog.log("Actor 2 is zombie.")
    end

    -- Move non-primary actors into position
    if not disableWalk then
        for _, actor in ipairs(actors) do
            if actor ~= actors[1] then
                local success = luautils.walkAdj(actor, actors[1]:getSquare(), true)
                if not success then return end
            end
        end
    end

    -- Base coordinates & facing for scene
    local originX, originY, originZ = actors[1]:getX(), actors[1]:getY(), actors[1]:getZ()
    local originDir = actors[1]:getDir()
    local queuedActions = {}

    -- Clone animation position templates
    local availablePositions = {}
    for _, posData in ipairs(animationData.actors) do
        table.insert(availablePositions, posData)
    end

    -- Assign each actor to a valid position
    for idx, actor in ipairs(actors) do
        local gender     = actor:isFemale() and "Female" or "Male"
        local assignedPos

        -- Match position based on gender
        for p = #availablePositions, 1, -1 do
            local posInfo = availablePositions[p]
            if not (gender == "Female" and posInfo.gender == "Male") then
                assignedPos = table.remove(availablePositions, p)
                break
            end
        end

        -- Instantiate the timed action
        local action = ISAnimationAction:new(
            actor,
            assignedPos.stages[1].perform,
            assignedPos.stages[1].duration
        )
        -- [Change] Renamed .job → .assignedPos for clarity
        action.currentStage    = 1
        action.originalPos     = { x = actor:getX(), y = actor:getY(), z = actor:getZ() }
        action.originActor     = actors[1]
        action.scenePosition   = { x = originX, y = originY, z = originZ }
        action.sceneFacing     = (#actors > 1) and originDir or nil
        action.waitingStarted  = false
        action.callbacks       = callbacks
        action.otherActions    = queuedActions

        if disableCancel then
            action.stopOnRun  = false
            action.stopOnAim  = false
        end

        -- Debug printout of the action’s fields
        DebugLog.log(string.format("Actor[%d] Action Debug:", idx))
        for key, val in pairs(action) do
            print("  ", key, "=", val)
        end
        table.insert(queuedActions, action)
    end

    -- Activate all actions simultaneously
    for i, act in ipairs(queuedActions) do
        if disableWalk and not act.character:isZombie() then            
            ISTimedActionQueue.clear(act.character)
        end

        if act.character:isZombie() then
            DebugLog.log(string.format("Queueing [%d] zombie action via setBumpType anim %s", i, act.animation))
            act.character:setBumpType(act.animation)
        else
            ISTimedActionQueue.add(act)
        end
    end

    return queuedActions
end

--------------------------------------------------------------------------------
-- ISAnimationAction overrides
--------------------------------------------------------------------------------

function ISAnimationAction:isValid()
    return true  -- Always keep running
end

function ISAnimationAction:waitToStart()
    DebugLog.log("[Zombolewd] /ISAnimationAction:waitToStart/")    
    local continue = self.character:shouldBeTurning()
    if self.character:isZombie() then
        DebugLog.log("fix waitToStart for zombie 1st char")   
        return false
    end
    if self.otherActions then
        for _, other in ipairs(self.otherActions) do
            if other.character ~= self.character then
                self.character:faceThisObject(other.character)
                if not other.waitingStarted or other.character:shouldBeTurning() then
                    continue = true
                    if other.character:isZombie() then
                        DebugLog.log("fix waitToStart for zombie 2nd char")   
                        other.character:getEmitter():stopAll()
                        other.character:faceThisObject(self.character)
                        return false    
                    end
                end                    
            end
        end
    end

    --штука ниже в этой и аналогичных функциях позволяет встраивать в неё сторонний код по аналогии с файлом ApplyBasicTraits.lua 
    if self.callbacks and self.callbacks.WaitToStart then
        self.callbacks.WaitToStart(self)
    end
    for _, cb in ipairs(AnimationHandler.ActionEvents.WaitToStart) do
        cb(self)
    end

    self.waitingStarted = true
    return continue
end


function ISAnimationAction:update()
    --DebugLog.log("[Zombolewd] /ISAnimationAction:update/") --здесь и ниже отключено так как слишком часто спамит в логи   
    if self.sceneFacing then
        --DebugLog.log("Setting self sceneFacing")
        self.character:setDir(self.sceneFacing)
    end
    if self.scenePosition then
        --DebugLog.log("Setting self scenePosition")        
        self.character:setX(self.scenePosition.x)
        self.character:setY(self.scenePosition.y)
        self.character:setZ(self.scenePosition.z)
    end

    if self.callbacks and self.callbacks.Update then
        self.callbacks.Update(self)
    end
    for _, cb in ipairs(AnimationHandler.ActionEvents.Update) do
        cb(self)
    end

    -- Abort if any companion action is missing
    for _, other in ipairs(self.otherActions or {}) do   
        if other.character:isZombie() then
            --DebugLog.log("other character is Zombie. Setting facing and position")
            other.character:setDir(other.sceneFacing)
            other.character:setX(other.scenePosition.x)
            other.character:setY(other.scenePosition.y)
            other.character:setZ(other.scenePosition.z)        
        else              
            if other.character ~= self.character and not ISTimedActionQueue.hasAction(other) then
                self:forceStop()                
            end
        end    
    end

    if not self.character:getCharacterActions():contains(self.action) then
        if not self.character:isZombie() then
            self:forceStop()
        end
    end
end

function ISAnimationAction:perform()
    self.character:getModData().zomboLewdSexScene = nil

    if self.callbacks and self.callbacks.Perform then
        self.callbacks.Perform(self)
    end

    for _, cb in ipairs(AnimationHandler.ActionEvents.Perform) do
        cb(self)
    end

    if self.character:isZombie() then
        self.character:setBumpType(self.animation)        
    else
        ISBaseTimedAction.perform(self)
    end
end

function ISAnimationAction:complete()
    DebugLog.log("[Zombolewd] /ISAnimationAction:complete/")
    for _, other in ipairs(self.otherActions or {}) do
        if other.character ~= self.character then
            if other.character:isZombie() then
                if other.character:getModData().isSubdued then
                    other.character:setBumpType("onback")
                else
                    other.character:knockDown(false)
                end
            else
                other:forceComplete()
            end
        end
    end

    if self.originalPos then
        self.character:setX(self.originalPos.x)
        self.character:setY(self.originalPos.y)
        self.character:setZ(self.originalPos.z)
    end

    if self.callbacks and self.callbacks.Complete then
        self.callbacks.Complete(self)
    end
    for _, cb in ipairs(AnimationHandler.ActionEvents.Complete) do
        cb(self)
    end
end

function ISAnimationAction:stop()
    DebugLog.log("[Zombolewd] /ISAnimationAction:stop/")    
    for _, other in ipairs(self.otherActions or {}) do
        if other.character ~= self.character then
            DebugLog.log("fix stop for zombie 2nd char") 
            if other.character:isZombie() then
                -- other.character:setBumpDone() -- пишет ошибку
                other.character:getEmitter():stopAll()
                if other.character:getVariableBoolean("MustDie") then    
                    other.character:setFakeDead(true)
                    other.character:Kill(self.character)
                else
                    if other.character:getVariableBoolean("isSubdued") then
                        other.character:setBumpType("onback")
                    else
                        other.character:knockDown(false)
                    end
                end
            else
                other:forceStop()
            end
        end
    end

    self.character:getModData().zomboLewdSexScene = nil

    if self.callbacks and self.callbacks.Stop then
        self.callbacks.Stop(self)
    end
    for _, cb in ipairs(AnimationHandler.ActionEvents.Stop) do
        cb(self)
    end

    if self.character:isZombie() then
        DebugLog.log("fix stop for zombie 1st char") 
        -- self.character:setBumpDone() (пишет ошибку)
        self.character:getEmitter():stopAll()
        if self.character:getModData().isSubdued then
            self.character:setBumpType("onback")
        else
            self.character:knockDown(false)
        end
    else        
        ISBaseTimedAction.stop(self)
        if not self.character:isFemale() then
            local ZomboLewd = require("ZomboLewd/ZomboLewd")
            ZomboLewd.ClothHandler.RemovePenis(self.character)
        end
    end
end


function ISAnimationAction:start()
    DebugLog.log("[Zombolewd] /ISAnimationAction:start/")
    if self.callbacks and self.callbacks.Start then
        self.callbacks.Start(self)
    end
    for _, cb in ipairs(AnimationHandler.ActionEvents.Start) do
        cb(self)
    end

    self.maxTime = self.duration
    self.action:setTime(self.maxTime)
    self:setActionAnim(self.animation)
    self:setOverrideHandModels(nil, nil)

    self.character:getModData().zomboLewdSexScene = true
    if self.character:isZombie() then
        DebugLog.log("fix start for zombie")     
        self.character:setBumpType(self.animation)
        self.character:getModData().task.state = "WORKING"
    end
end

function ISAnimationAction:animEvent(event, parameter)
    DebugLog.log("[Zombolewd] /ISAnimationAction:animEvent/")
    if not AnimationHandler.EventMarkerModules[event] and not ignoredKeyframeNames[event] then
        AnimationHandler.EventMarkerModules[event] = 
            require(string.format("ZomboLewd/AnimationEvents/%s", event))
    end

    if AnimationHandler.EventMarkerModules[event] then
        AnimationHandler.EventMarkerModules[event](self, parameter)
    elseif not ignoredKeyframeNames[event] then
        ignoredKeyframeNames[event] = true
        DebugLog.log(string.format("ZomboLewd - Ignoring %s events from now on", event))
    end
end

function ISAnimationAction:new(character, animation, duration)
    DebugLog.log("[Zombolewd] /ISAnimationAction:new/")
    local obj = {
        character        = character,
        animation        = animation,
        stopOnWalk       = false,
        stopOnRun        = true,
        ignoreHandsWounds= true,
        maxTime          = -1,  -- set in :start()
        duration         = duration or ZomboLewdConfig.DefaultSexDuration,
        ended            = false
    }
    setmetatable(obj, self)
    self.__index = self
    return obj
end

return AnimationHandler