-- FS25-Compatible RotationAnimationSpikesExtended.lua
-- This script extends a base animation to control windrower spikes.

-- Load the base class if not already loaded.
-- if RotationAnimation == nil then
    -- local success, err = pcall(function()
        -- source("$dataS/scripts/vehicles/animation/RotationAnimation.lua")
    -- end)
    -- if not success then
        -- print("[RotationAnimationSpikesExtended] Error loading RotationAnimation.lua: " .. tostring(err))
    -- end
-- end

RotationAnimationSpikesExtended = {}
local RotationAnimationSpikesExtended_mt = Class(RotationAnimationSpikesExtended, RotationAnimation)

function RotationAnimationSpikesExtended.new(customMt)
    if RotationAnimation == nil then
        print("[RotationAnimationSpikesExtended] RotationAnimation is nil. Cannot initialize.")
        return nil
    end
    return RotationAnimation.new(customMt or RotationAnimationSpikesExtended_mt)
end

function RotationAnimationSpikesExtended:load(xmlFile, key, rootNodes, owner, i3dMapping)
    if RotationAnimationSpikesExtended:superClass().load(self, xmlFile, key, rootNodes, owner, i3dMapping) == nil then
        return nil
    end

    self.spikeRotAxis = tonumber(xmlFile:getValue(key .. ".spikes#rotAxis", "3")) or 3
    self.spikeMaxRot = tonumber(xmlFile:getValue(key .. ".spikes#maxRot", "0")) or 0
    self.spikeTransAxis = xmlFile:getValue(key .. ".spikes#transAxis")
    self.spikeMaxTrans = tonumber(xmlFile:getValue(key .. ".spikes#maxTrans", "0")) or 0
    self.spikeInverted = xmlFile:getValue(key .. ".spikes#inverted", false)
    self.moveUpStart, self.moveUpEnd = xmlFile:getValue(key .. ".spikes#moveUpRange")
    self.moveDownStart, self.moveDownEnd = xmlFile:getValue(key .. ".spikes#moveDownRange")

    if not (self.moveUpStart and self.moveUpEnd and self.moveDownStart and self.moveDownEnd) then
        print("[RotationAnimationSpikesExtended] Incomplete moveUp/moveDown range for key: " .. tostring(key))
        return nil
    end

    self.rotOffset = self.currentRot or 0
    self.rotDirection = MathUtil.sign(self.rotSpeed or 0)

    if self.rotDirection < 0 then
        self.moveUpEnd = -self.moveUpEnd
        self.moveUpStart = -self.moveUpStart
        self.moveDownEnd = -self.moveDownEnd
        self.moveDownStart = -self.moveDownStart
    end

    self.moveUpEnd = self.moveUpEnd + self.rotOffset
    self.moveUpStart = self.moveUpStart + self.rotOffset
    self.moveDownEnd = self.moveDownEnd + self.rotOffset
    self.moveDownStart = self.moveDownStart + self.rotOffset

    self.spikeOffset = 1
    self.spikes = {}

    xmlFile:iterate(key .. ".spikes.spike", function(index, spikeKey)
        local spike = { node = xmlFile:getValue(spikeKey .. "#node", nil, rootNodes, i3dMapping) }
        if spike.node ~= nil then
            spike.initialRot = { getRotation(spike.node) }
            local dx, dy
            if self.rotAxis == 1 then
                _, dx, dy = localToLocal(spike.node, self.node, 0, 0, 0)
            elseif self.rotAxis == 2 then
                dx, _, dy = localToLocal(spike.node, self.node, 0, 0, 0)
            else
                dx, dy, _ = localToLocal(spike.node, self.node, 0, 0, 0)
            end

            self.spikeOffset = MathUtil.vector2Length(dx, dy)
            if self.spikeOffset ~= 0 then
                dx = dx / self.spikeOffset
                dy = dy / self.spikeOffset
            else
                dx, dy = 0, 0
            end
            spike.rotationOffset = MathUtil.getYRotationFromDirection(dx, dy)
            if self.rotAxis == 1 then
                spike.rotationOffset = -spike.rotationOffset
            end
            if spike.rotationOffset < 0 then
                spike.rotationOffset = 2 * math.pi + spike.rotationOffset
            end

            if self.spikeTransAxis ~= nil then
                spike.startTrans = { getTranslation(spike.node) }
                if self.spikeTransAxis == 1 then
                    spike.endTrans = { localToLocal(spike.node, getParent(spike.node), self.spikeMaxTrans, 0, 0) }
                elseif self.spikeTransAxis == 2 then
                    spike.endTrans = { localToLocal(spike.node, getParent(spike.node), 0, self.spikeMaxTrans, 0) }
                else
                    spike.endTrans = { localToLocal(spike.node, getParent(spike.node), 0, 0, self.spikeMaxTrans) }
                end
            end

            table.insert(self.spikes, spike)
        end
    end)

    self:updateSpikes()
    return self
end

function RotationAnimationSpikesExtended:update(dt)
    RotationAnimationSpikesExtended:superClass().update(self, dt)
    if self.currentAlpha and self.currentAlpha > 0 then
        self:updateSpikes()
    end
end

-- Helper functions for range calculations
local function getInRange(value, s, e)
    local invValue = value - 2 * math.pi
    if e < s then
        if value <= s and e <= value then
            return true, value
        elseif invValue <= s and e <= invValue then
            return true, invValue
        else
            return false, value
        end
    elseif s <= value and value <= e then
        return true, value
    elseif s <= invValue and invValue <= e then
        return true, invValue
    else
        return false, value
    end
end

local function getInRangeAndLerp(value, s, e)
    local inRange, adjValue = getInRange(value, s, e)
    if inRange then
        if e < s then
            local alpha = (adjValue - e) / (s - e)
            return true, alpha
        else
            local alpha = (adjValue - s) / (e - s)
            return true, alpha
        end
    end
    return false, 0
end

function RotationAnimationSpikesExtended:updateSpikes()
    if not self.spikes then
        return
    end
    for i = 1, #self.spikes do
        local spike = self.spikes[i]
        local yRot = (self.currentRot + spike.rotationOffset) % (2 * math.pi)
        local alpha = nil
        local isUp, _ = getInRange(yRot, self.moveUpEnd, self.moveDownStart)
        if isUp then
            alpha = 1
        else
            local isMovingUp, moveUpAlpha = getInRangeAndLerp(yRot, self.moveUpStart, self.moveUpEnd)
            if isMovingUp then
                alpha = moveUpAlpha
            else
                local isMovingDown, moveDownAlpha = getInRangeAndLerp(yRot, self.moveDownStart, self.moveDownEnd)
                if isMovingDown then
                    alpha = 1 - moveDownAlpha
                end
            end
            if alpha and self.rotDirection < 0 then
                alpha = 1 - alpha
            end
        end
        alpha = alpha or 0
        if self.spikeInverted then
            alpha = 1 - alpha
        end
        if self.spikeTransAxis ~= nil then
            local x, y, z = MathUtil.vector3ArrayLerp(spike.startTrans, spike.endTrans, alpha)
            setTranslation(spike.node, x, y, z)
        else
            local rot = self.spikeMaxRot * alpha
            if self.spikeRotAxis == 1 then
                setRotation(spike.node, spike.initialRot[1] + rot, spike.initialRot[2], spike.initialRot[3])
            elseif self.spikeRotAxis == 2 then
                setRotation(spike.node, spike.initialRot[1], spike.initialRot[2] + rot, spike.initialRot[3])
            else
                setRotation(spike.node, spike.initialRot[1], spike.initialRot[2], spike.initialRot[3] + rot)
            end
        end
    end
end

function RotationAnimationSpikesExtended:drawDebug()
    local ox, oy, oz = getTranslation(self.node)
    local steps = 20
    local rangeUp = self.moveUpEnd - self.moveUpStart
    for i = 1, steps do
        local a1 = self.moveUpStart + (i-1)/steps * rangeUp
        local a2 = self.moveUpStart + i/steps * rangeUp
        local x1 = ox + math.cos(a1) * self.spikeOffset
        local z1 = oz + math.sin(a1) * self.spikeOffset
        local x2 = ox + math.cos(a2) * self.spikeOffset
        local z2 = oz + math.sin(a2)
