ScaniaDoorControl = {}
ScaniaDoorControl.MOD_NAME = g_currentModName

-- Recursive function to find a node by name in the I3D hierarchy
local function findNodeByName(node, name)
    if getName(node) == name then
        return node
    end
    local numChildren = getNumOfChildren(node)
    for i = 0, numChildren-1 do
        local child = getChildAt(node, i)
        local found = findNodeByName(child, name)
        if found ~= nil then
            return found
        end
    end
    return nil
end

function ScaniaDoorControl.prerequisitesPresent(specializations)
    return true
end

function ScaniaDoorControl.registerEventListeners(vehicleType)
    SpecializationUtil.registerEventListener(vehicleType, "onLoad", ScaniaDoorControl)
    SpecializationUtil.registerEventListener(vehicleType, "onRegisterActionEvents", ScaniaDoorControl)
end

function ScaniaDoorControl:onLoad(savegame)
    self.spec_scaniaDoorControl = {
        animLeft = "LEFT_SIDE",
        animRight = "RIGHT_SIDE",
        animBack = "BACK_SIDE",
        actionEvents = {},
        collisionInfo = {} -- store original parent & translation
    }

    local root = self.rootNode
    local nodes = {"Left_Col", "Right_Col", "Back_Col"}
    for _, name in ipairs(nodes) do
        local node = findNodeByName(root, name)
        if node ~= nil then
            local x, y, z = getTranslation(node)
            local parent = getParent(node)
            self.spec_scaniaDoorControl.collisionInfo[name] = {
                parent = parent,
                x = x, y = y, z = z
            }
        end
    end
end

function ScaniaDoorControl:onRegisterActionEvents(isActiveForInput, isActiveForInputIgnoreSelection)
    if not self.isClient then return end
    local spec = self.spec_scaniaDoorControl

    if isActiveForInput then
        local _, id1 = self:addActionEvent(spec.actionEvents, InputAction.LEFT_SIDE, self, ScaniaDoorControl.toggleLeft, false, true, false, true)
        g_inputBinding:setActionEventText(id1, "Venstre sidepanel")

        local _, id2 = self:addActionEvent(spec.actionEvents, InputAction.RIGHT_SIDE, self, ScaniaDoorControl.toggleRight, false, true, false, true)
        g_inputBinding:setActionEventText(id2, "Højre sidepanel")

        local _, id3 = self:addActionEvent(spec.actionEvents, InputAction.BACK_SIDE, self, ScaniaDoorControl.toggleBack, false, true, false, true)
        g_inputBinding:setActionEventText(id3, "Bagpanel")
    end
end

function ScaniaDoorControl.toggleLeft(self, ...)
    ScaniaDoorControl.toggleAnimation(self, self.spec_scaniaDoorControl.animLeft)
end

function ScaniaDoorControl.toggleRight(self, ...)
    ScaniaDoorControl.toggleAnimation(self, self.spec_scaniaDoorControl.animRight)
end

function ScaniaDoorControl.toggleBack(self, ...)
    ScaniaDoorControl.toggleAnimation(self, self.spec_scaniaDoorControl.animBack)
end

-- Move collision nodes based on door state, reparenting for accurate positioning
function ScaniaDoorControl.setCollisionState(vehicle, animName, isOpen)
    if vehicle == nil or vehicle.rootNode == nil then return end

    local nodeName = nil
    if animName == "LEFT_SIDE" then nodeName = "Left_Col"
    elseif animName == "RIGHT_SIDE" then nodeName = "Right_Col"
    elseif animName == "BACK_SIDE" then nodeName = "Back_Col"
    else return end

    local node = findNodeByName(vehicle.rootNode, nodeName)
    if node == nil then
        return
    end

    local spec = vehicle.spec_scaniaDoorControl
    local info = spec.collisionInfo[nodeName]
    if info == nil then return end

    if isOpen then
        -- Detach collision from door and move far away
        link(vehicle.rootNode, node)
        setTranslation(node, info.x, info.y - 100, info.z)
    else
        -- Reattach collision to original door and restore translation
        link(info.parent, node)
        setTranslation(node, info.x, info.y, info.z)
    end
end

-- Toggle animation and collision
function ScaniaDoorControl.toggleAnimation(vehicle, animName)
    if vehicle == nil or animName == nil then return end

    local time = vehicle:getAnimationTime(animName) or 0
    local isOpen = time <= 0.5

    if isOpen then
        vehicle:playAnimation(animName, 1, 0.5, true)
    else
        vehicle:playAnimation(animName, -1, 0.5, true)
    end

    -- Update collision immediately
    ScaniaDoorControl.setCollisionState(vehicle, animName, isOpen)

    -- Sync with clients
    if g_server ~= nil then
        g_server:broadcastEvent(ScaniaDoorControlEvent.new(vehicle, animName))
    elseif g_client ~= nil and g_client:getServerConnection() ~= nil then
        g_client:getServerConnection():sendEvent(ScaniaDoorControlEvent.new(vehicle, animName))
    end
end

-- Multiplayer event
ScaniaDoorControlEvent = {}
ScaniaDoorControlEvent_mt = Class(ScaniaDoorControlEvent, Event)
InitEventClass(ScaniaDoorControlEvent, "ScaniaDoorControlEvent")

function ScaniaDoorControlEvent.emptyNew()
    return Event.new(ScaniaDoorControlEvent_mt)
end

function ScaniaDoorControlEvent.new(vehicle, animName)
    local self = ScaniaDoorControlEvent.emptyNew()
    self.vehicle = vehicle
    self.animName = animName
    return self
end

function ScaniaDoorControlEvent:readStream(streamId, connection)
    self.vehicle = NetworkUtil.readNodeObject(streamId)
    self.animName = streamReadString(streamId)
    self:run(connection)
end

function ScaniaDoorControlEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.vehicle)
    streamWriteString(streamId, self.animName)
end

function ScaniaDoorControlEvent:run(connection)
    if not connection:getIsServer() then
        g_server:broadcastEvent(ScaniaDoorControlEvent.new(self.vehicle, self.animName), nil, connection, self.vehicle)
    end

    if self.vehicle ~= nil then
        local time = self.vehicle:getAnimationTime(self.animName) or 0
        local isOpen = time <= 0.5

        if isOpen then
            self.vehicle:setAnimationTime(self.animName, 1, true)
        else
            self.vehicle:setAnimationTime(self.animName, 0, true)
        end

        -- Sync collision across clients
        ScaniaDoorControl.setCollisionState(self.vehicle, self.animName, isOpen)
    end
end