-- @author DoomTero 2026

require "TimedActions/ISBaseTimedAction"

local AnimationHandler = {EventMarkerModules = {}, ActionEvents = {Perform = {}, WaitToStart = {}, Start = {}, Stop = {}, Update = {}}}

local ZomboRutConfig = ZomboRutConfig
local ISBaseTimedAction = ISBaseTimedAction
local ISTimedActionQueue = ISTimedActionQueue

local luautils = luautils
local ignoredKeyframeNames = {}

local ipairs = ipairs

ISAnimationAction = ISBaseTimedAction:derive("ISZomboRutAnimationAction")

function AnimationHandler.Play(worldobjects, actors, animationData, disableCancel, disableWalk, callbacks, prefixActions)
	disableWalk = disableWalk or true

	if #actors < 1 then return end
	if not disableWalk then
		for _, actor in ipairs(actors) do
			if actor ~= actors[1] then
				if not luautils.walkAdj(actor, actors[1]:getSquare(), true) then
					return
				end
			end
		end
	end

	disableCancel = disableCancel or false

	local x, y, z = actors[1]:getX(), actors[1]:getY(), actors[1]:getZ()
	local facing = actors[1]:getDir()
	local otherActions = {}

	local availablePositions = {} do
		for _, actor in ipairs(animationData.actors) do
			table.insert(availablePositions, actor)
		end
	end

	for i, actor in ipairs(actors) do
		local isFemale = actor:isFemale()
		local job
		
		for i = #availablePositions, 1, -1 do
			local canUsePosition = true

			if isFemale and availablePositions[i].gender == "Male" then
				canUsePosition = false
			end

			if canUsePosition then
				job = table.remove(availablePositions, i)
				break
			end
		end

		local action = ISAnimationAction:new(actor, job.stages[1].perform, job.stages[1].duration)
		action.currentStage = 1
		action.originalPosition = {x = actor:getX(), y = actor:getY(), z = actor:getZ()}
		action.originalActor = actors[1]
		action.position = {x = x, y = y, z = z}
		action.facing = #actors > 1 and facing
		action.waitingStarted = false
		action.callbacks = callbacks
		action.otherActions = #actors > 1 and otherActions

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

		table.insert(otherActions, action)
	end

	for i = 1, #otherActions do
		local otherAction = otherActions[i]

		if disableWalk then
			ISTimedActionQueue.clear(otherAction.character)
		end

		if prefixActions and i == 1 then
			for _, pre in ipairs(prefixActions) do
				ISTimedActionQueue.add(pre)
			end
		end

		ISTimedActionQueue.add(otherAction)
	end

	return otherActions
end

function ISAnimationAction:isValid()
	return true
end

function ISAnimationAction:waitToStart()

	local continueWaiting = self.character:shouldBeTurning()

	if self.otherActions then
		local otherActionLength = #self.otherActions

		if otherActionLength > 1 then
			for i = 1, otherActionLength do
				local otherAction = self.otherActions[i]
				if otherAction.character ~= self.character then
					self.character:faceThisObject(otherAction.character)

					if(otherAction.waitingStarted == false or otherAction.character:shouldBeTurning() == true) then
						continueWaiting = true;
					end
				end
			end
		end
	end

	
	if self.callbacks then
		if self.callbacks.WaitToStart then
			self.callbacks.WaitToStart(self)
		end
	end

	for i = 1, #AnimationHandler.ActionEvents.WaitToStart do
		AnimationHandler.ActionEvents.WaitToStart[i](self)
	end

	self.waitingStarted = true

	return continueWaiting
end

function ISAnimationAction:update()
	if self.facing then
		self.character:setDir(self.facing)
	end

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

	if self.callbacks then
		if self.callbacks.Update then
			self.callbacks.Update(self)
		end
	end

	for i = 1, #AnimationHandler.ActionEvents.Update do
		AnimationHandler.ActionEvents.Update[i](self)
	end

	if self.otherActions then
		for i = 1, #self.otherActions do
			local otherAction = self.otherActions[i]
			if otherAction.character ~= self.character and not ISTimedActionQueue.hasAction(otherAction) then
				self:forceStop()
			end
		end
	end

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

function ISAnimationAction:perform()
	if self.otherActions then
		for i = 1, #self.otherActions do
			if self.otherActions[i].character ~= self.character then
				self.otherActions[i]:forceComplete()
			end
		end
	end

	if self.originalPosition then
		self.character:setX(self.originalPosition.x)
		self.character:setY(self.originalPosition.y)
		self.character:setZ(self.originalPosition.z)
	end
	
	self.character:getModData().ZomboRutSexScene = nil

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

	for i = 1, #AnimationHandler.ActionEvents.Perform do
		AnimationHandler.ActionEvents.Perform[i](self)
	end

	if not self.character:getModData().banditStart then	
		self.character:getModData().banditStart = false
	end
		
	ISBaseTimedAction.perform(self)
	
end

function ISAnimationAction:stop()
	if self.otherActions then
		for i = 1, #self.otherActions do
			if self.otherActions[i].character ~= self.character then
				self.otherActions[i]:forceStop()
			end
		end
	end

	self.character:getModData().ZomboRutSexScene = nil

	if self.callbacks then
		if self.callbacks.Stop then
			self.callbacks.Stop(self)
		end
	end

	for i = 1, #AnimationHandler.ActionEvents.Stop do
		AnimationHandler.ActionEvents.Stop[i](self)
	end

	if not self.character:getModData().banditStart then	
		self.character:getModData().banditStart = false
	end

	ISBaseTimedAction.stop(self)
end

function ISAnimationAction:start()
	if self.callbacks then
		if self.callbacks.Start then
			self.callbacks.Start(self)
		end
	end

	self.maxTime = self.duration
	self.action:setTime(self.maxTime)
	self:setActionAnim(self.animation)
	self.character:getModData().ZomboRutSexScene = true
end

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

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

function ISAnimationAction:new(character, animation, duration)
	local object = {
		character = character,
		animation = animation,
		overrideAnimation = true,
		blockMovementEtc = true,
		stopOnWalk = false,
		stopOnRun = false,
		ignoreHandsWounds = true,
		maxTime = -1,
		duration = duration or ZomboRutConfig.DefaultSexDuration,
		ended = false,
	}
	setmetatable(object, self)
	self.__index = self
	return object
end

return AnimationHandler