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

require "TimedActions/ISBaseTimedAction"

ISZomboRutSceneAction = ISBaseTimedAction:derive("ISZomboRutSceneAction")

local DEBUG = true
local function log(fmt, ...)
	if DEBUG then DebugLog.log(string.format("[ZR SceneAction] " .. fmt, ...)) end
end

local function snapshotState(player, label)
	if not DEBUG then return end
	local ok, snap = pcall(function()
		return string.format(
			"%s: state=%s isDragging=%s isGrappling=%s sgt=%q sgan=%q ZR_SceneId=%q",
			label,
			player.getCurrentStateName and player:getCurrentStateName() or "?",
			tostring(player:isDraggingCorpse()),
			tostring(player.isGrappling and player:isGrappling() or false),
			player:getVariableString("sharedGrappleType") or "",
			player:getVariableString("sharedGrappleAnimNode") or "",
			player:getVariableString("ZR_SceneId") or "")
	end)
	if ok then log("%s", snap) end
end

local function faceIntro(self)
	if self.sceneCfg and self.sceneCfg.keepPlayerFacing then
		local ox = tonumber(self.character:getVariableString("ZR_OrigFaceX") or "")
		local oy = tonumber(self.character:getVariableString("ZR_OrigFaceY") or "")
		if ox and oy and (math.abs(ox) > 1e-3 or math.abs(oy) > 1e-3) then
			pcall(function() self.character:setForwardDirection(ox, oy) end)
			return
		end
	end
	self.character:faceThisObject(self.corpseBody)
end

function ISZomboRutSceneAction:isValid()
	return true
end

function ISZomboRutSceneAction:waitToStart()
	faceIntro(self)
	if self.sceneCfg and self.sceneCfg.keepPlayerFacing then return false end
	return self.character:shouldBeTurning()
end

function ISZomboRutSceneAction:update()
	faceIntro(self)

	if self.character.getCharacterActions
		and self.action
		and not self.character:getCharacterActions():contains(self.action)
	then
		log("update: native action dropped by engine — forceStop")
		self:forceStop()
	end
end

function ISZomboRutSceneAction:start()
	log("start: scene=%s pickup=%s actionAnim=%s",
		self.sceneCfg.id, self.sceneCfg.pickup,
		tostring(self.sceneCfg.actionAnim))
	snapshotState(self.character, "start[pre]")

	self.character:setVariable("ZR_SceneId", self.sceneCfg.id)

	if self.sceneCfg.actionAnim then
		self:setActionAnim(self.sceneCfg.actionAnim)
	end
end

function ISZomboRutSceneAction:stop()
	log("stop: action interrupted before complete")
	ISBaseTimedAction.stop(self)
end

function ISZomboRutSceneAction:perform()
	ISBaseTimedAction.perform(self)
end

local VANILLA_PICKUP_NAMES = {
	HeadEnd_onBack  = "GrappledBwdPickUpFromOnBack",
	HeadEnd_onFront = "GrappledBwdPickUpFromOnFront",
	LegsEnd_onBack  = "GrappledBwdPickUpLegsFromOnBack",
	LegsEnd_onFront = "GrappledBwdPickUpLegsFromOnFront",
}

local POST_PICKUP_SGT = {
	HeadEnd_onBack  = "BwdDragHead",
	HeadEnd_onFront = "BwdDragHeadOnFront",
	LegsEnd_onBack  = "BwdDragLegs",
	LegsEnd_onFront = "BwdDragLegsOnFront",
}

local function pickVanillaPickupName(player, corpseBody, sceneCfg)
	if sceneCfg and sceneCfg.forcedOrientation then
		local key = sceneCfg.forcedOrientation
		local pickupName = VANILLA_PICKUP_NAMES[key]
		if pickupName then
			local fof = key:find("onFront") ~= nil
			return pickupName, key, 0, fof
		end
		log("WARN forcedOrientation=%s invalid; falling back to bearing math",
			tostring(key))
	end

	local bearing = 0
	pcall(function() bearing = player:getBearingFromGrappledTarget() end)
	local fallOnFront = false
	pcall(function() fallOnFront = corpseBody:isFallOnFront() end)

	local end_ = (math.abs(bearing) >= 90.0) and "HeadEnd" or "LegsEnd"
	local side = fallOnFront and "onFront" or "onBack"
	local key = end_ .. "_" .. side
	return VANILLA_PICKUP_NAMES[key], key, bearing, fallOnFront
end

function ISZomboRutSceneAction:complete()
	if not self.corpseBody then
		log("complete: ABORT (corpseBody nil)")
		return false
	end

	pcall(function()
		if IdleState and IdleState.instance and self.character.changeState then
			self.character:changeState(IdleState.instance())
		end
		if not (self.sceneCfg and self.sceneCfg.noMovementLock)
			and self.character.setIgnoreMovement
		then
			self.character:setIgnoreMovement(true)
		end
	end)

	pcall(function()
		if self.character.resetGrappleStateToDefault then
			self.character:resetGrappleStateToDefault("")
		end
	end)

	snapshotState(self.character, "complete[pre-pickUpCorpse]")

	if self.sceneCfg.forcedOrientation then
		local key = self.sceneCfg.forcedOrientation
		local wantFof = key:find("onFront") ~= nil
		local wantHeadEnd = key:find("^HeadEnd_") ~= nil
		pcall(function()
			if self.corpseBody.setFallOnFront then
				self.corpseBody:setFallOnFront(wantFof)
			end
		end)
		local px, py = self.character:getX(), self.character:getY()
		local cx, cy = self.corpseBody:getX(), self.corpseBody:getY()
		local dx, dy = cx - px, cy - py
		local faceAway = wantHeadEnd
		if wantFof then faceAway = not faceAway end
		if faceAway then
			local angle = math.rad(30)
			local c, s = math.cos(angle), math.sin(angle)
			dx, dy = dx*c - dy*s, dx*s + dy*c
		else
			dx, dy = -dx, -dy
		end
		if math.abs(dx) < 1e-4 and math.abs(dy) < 1e-4 then
			dx, dy = 1, 0
		end
		pcall(function()
			if self.corpseBody.setForwardDirection then
				self.corpseBody:setForwardDirection(dx, dy)
			end
		end)
		log("complete: forced corpse fallOnFront=%s + facing=(%.2f,%.2f) -> orientation=%s",
			tostring(wantFof), dx, dy, key)
	end

	log("complete: calling pickUpCorpse(body=%s, dragType=BwdDrag)",
		tostring(self.corpseBody))

	local ok, err = pcall(function()
		self.character:pickUpCorpse(self.corpseBody, "BwdDrag")
	end)
	if not ok then
		log("complete: pickUpCorpse FAILED: %s", tostring(err))
		return false
	end

	snapshotState(self.character, "complete[post-pickUpCorpse]")

	local target = self.character:getGrapplingTarget()
	if target then
		pcall(function() if target.setAlpha       then target:setAlpha(0.031)       end end)
		pcall(function() if target.setTargetAlpha then target:setTargetAlpha(0.031) end end)

		local pickupName, key, bearing, fof = pickVanillaPickupName(self.character, self.corpseBody, self.sceneCfg)
		log("complete: orient bearing=%.1f fallOnFront=%s -> %s (%s)",
			bearing, tostring(fof), key, pickupName)
		pcall(function() target:setSharedGrappleAnimNode(pickupName) end)

		pcall(function() target:setVariable("ZR_SceneId", self.sceneCfg.id) end)

		local promotedSgt = POST_PICKUP_SGT[key]
		if promotedSgt then
			pcall(function() self.character:setVariable("ZR_PromotedSgt", promotedSgt) end)
			pcall(function() self.character:setVariable("ZR_BridgePending", "true") end)
			log("complete: queued bridge-then-promote (sgt=%s)", promotedSgt)
		end
	else
		log("complete: WARN no grappling target after pickUpCorpse; can't force pickup name")
	end

	if self.sceneCfg.pickup == "skip" then
		local promoted = self.sceneCfg.promotedType or "BwdDragHead"
		log("complete: pickup=skip, promoting sharedGrappleType=%s", promoted)
		pcall(function() self.character:setSharedGrappleType(promoted) end)
		snapshotState(self.character, "complete[post-promote]")
	end

	log("complete: done; engine should now transition to grapple state")
	return true
end

function ISZomboRutSceneAction:getDuration()
	if self.character:isTimedActionInstant() then return 1 end
	return self.sceneCfg.actionDuration or 1
end

function ISZomboRutSceneAction:new(character, corpseBody, sceneCfg)
	local o = ISBaseTimedAction.new(self, character) 
	o.corpseBody = corpseBody
	o.sceneCfg = sceneCfg
	o.maxTime = o:getDuration()
	o.forceProgressBar = sceneCfg.actionDuration and sceneCfg.actionDuration > 30

	o.stopOnRun  = false
	o.stopOnWalk = false
	o.stopOnAim  = false
	o.blockMovementEtc = true
	o.ignoreHandsWounds = true

	return o
end

return ISZomboRutSceneAction
