---
-- FAPSupportFrame
--
-- @author      nilBrain
-- @date        2026-01-27
--
-- Copyright (C) nilBrain | Philipp Jaeger, Confidential, All Rights Reserved.

FAPSupportFrame = {}

local modName = g_currentModName

FAPSupportFrame.SPEC_NAME = modName .. ".fapSupportFrame"
FAPSupportFrame.SPEC_TABLE_NAME = "spec_" .. modName .. ".fapSupportFrame"

FAPSupportFrame.REF_FRAME_NODE_NAME = "fapRefFrameNode"
FAPSupportFrame.BACK_JOINT_NAME_TOKEN = "back"

---Called while initializing the specialization
function FAPSupportFrame.initSpecialization()
    local schema = Vehicle.xmlSchema
    local xmlPath = "vehicle.fapSupportFrame"
    schema:register(XMLValueType.NODE_INDEX, xmlPath .. "#node", "Fap support frame node")
    schema:register(XMLValueType.NODE_INDEX, xmlPath .. "#referenceNode", "Fap support frame reference node")
    schema:register(XMLValueType.VECTOR_3, xmlPath .. "#refNodeOffset", "Reference node offset", "0 0 0")
end

---Register all functions from the specialization that can be called on vehicle level
-- @param table vehicleType vehicle type
function FAPSupportFrame.registerFunctions(vehicleType)
    SpecializationUtil.registerFunction(vehicleType, "getFAPSupportFrame", FAPSupportFrame.getFAPSupportFrame)
    SpecializationUtil.registerFunction(vehicleType, "setFAPSupportFramePosition", FAPSupportFrame.setFAPSupportFramePosition)
end

---Register all events that should be called for this specialization
-- @param table vehicleType vehicle type
function FAPSupportFrame.registerEventListeners(vehicleType)
    SpecializationUtil.registerEventListener(vehicleType, "onLoad", FAPSupportFrame)
    SpecializationUtil.registerEventListener(vehicleType, "onUpdate", FAPSupportFrame)
    SpecializationUtil.registerEventListener(vehicleType, "onPostAttach", FAPSupportFrame)
    SpecializationUtil.registerEventListener(vehicleType, "onPostDetach", FAPSupportFrame)
end

---Checks if all prerequisite specializations are loaded
-- @param table specializations specializations
-- @return boolean hasPrerequisite true if all prerequisite specializations are loaded
function FAPSupportFrame.prerequisitesPresent(specializations)
    local hasPrerequisite = SpecializationUtil.hasSpecialization(Attachable, specializations)
    return hasPrerequisite
end

---Called on load
-- @param table savegame savegame
function FAPSupportFrame:onLoad(savegame)
    local spec = self[FAPSupportFrame.SPEC_TABLE_NAME]

    spec.isFrameDirty = true

    local xmlPath = "vehicle.fapSupportFrame"

    local node = self.xmlFile:getNode(xmlPath .. "#node", nil, self.components, self.i3dMappings)
    if node == nil then
        Logging.xmlError(self.xmlFile, "Missing Node in '%s'", xmlPath)
        SpecializationUtil.removeEventListener(self, "onUpdate", FAPSupportFrame)
        SpecializationUtil.removeEventListener(self, "onPostAttach", FAPSupportFrame)
        SpecializationUtil.removeEventListener(self, "onPostDetach", FAPSupportFrame)
        return
    end

    local refNode = self.xmlFile:getNode(xmlPath .. "#referenceNode", nil, self.components, self.i3dMappings)
    if refNode == nil then
        refNode = node
    end

    spec.node = node
    spec.refNode = refNode
    spec.refNodeOffset = self.xmlFile:getVector(xmlPath .. "#refNodeOffset", nil, 3) or {0, 0, 0}
    spec.baseTranslation = {getTranslation(node)}

    spec.isValid = false
    spec.lowerRootVehicleBackAttacherTrailer = nil
    spec.lowerRootVehicleBackAttacherImplement = nil
end

---Called on update
function FAPSupportFrame:onUpdate()
    local spec = self[FAPSupportFrame.SPEC_TABLE_NAME]

    if spec.isFrameDirty and self:getIsReadyToFinishDetachProcess() then
        self:setFAPSupportFramePosition()
    end
end

---Called after attach
function FAPSupportFrame:onPostAttach()
    local spec = self[FAPSupportFrame.SPEC_TABLE_NAME]
    spec.isValid = false
    spec.isFrameDirty = true
end

---Called after detach
function FAPSupportFrame:onPostDetach()
    local spec = self[FAPSupportFrame.SPEC_TABLE_NAME]
    spec.isValid = false
    spec.isFrameDirty = true

    if spec.lowerRootVehicleBackAttacherImplement ~= nil then
        if spec.lowerRootVehicleBackAttacherImplement.topArm ~= nil then
            setVisibility(spec.lowerRootVehicleBackAttacherImplement.topArm.node, true)
            spec.lowerRootVehicleBackAttacherImplement.topArm:setIsActive(false)
        end

        spec.lowerRootVehicleBackAttacherImplement = nil
    end

    if spec.lowerRootVehicleBackAttacherTrailer ~= nil then
        if spec.lowerRootVehicleBackAttacherTrailer.transNode ~= nil then
            setVisibility(spec.lowerRootVehicleBackAttacherTrailer.transNode, true)
        end

        spec.lowerRootVehicleBackAttacherTrailer = nil
    end

    setVisibility(spec.node, false)
end

---Returns the support frame node and whether it is positioned against a valid reference
-- @return entityId frameNode support frame node
-- @return boolean isFrameValid whether a reference could be resolved and the frame was positioned
function FAPSupportFrame:getFAPSupportFrame()
    local spec = self[FAPSupportFrame.SPEC_TABLE_NAME]
    return spec.node, spec.isValid == true
end

---
function FAPSupportFrame:setFAPSupportFramePosition()
    local spec = self[FAPSupportFrame.SPEC_TABLE_NAME]
    local rootVehicle = self:getRootVehicle()

    if rootVehicle == self or rootVehicle.spec_attacherJoints == nil then
        spec.isValid = false
        setVisibility(spec.node, false)
        return
    end

    spec.lowerRootVehicleBackAttacherImplement = nil
    spec.lowerRootVehicleBackAttacherTrailer = nil

    setTranslation(spec.node, spec.baseTranslation[1], spec.baseTranslation[2], spec.baseTranslation[3])

    local refPosition = nil
    local fixedRefNode = nil

    if rootVehicle.i3dMappings ~= nil and rootVehicle.i3dMappings[FAPSupportFrame.REF_FRAME_NODE_NAME] ~= nil then
        fixedRefNode = rootVehicle.i3dMappings[FAPSupportFrame.REF_FRAME_NODE_NAME].node
    end

    if fixedRefNode == nil then
        I3DUtil.iterateRecursively(rootVehicle.rootNode, function(node)
            if fixedRefNode == nil and getName(node) == FAPSupportFrame.REF_FRAME_NODE_NAME then
                fixedRefNode = node
            end
        end)
    end

    if fixedRefNode ~= nil then
        refPosition = {localToLocal(fixedRefNode, spec.node, spec.refNodeOffset[1], spec.refNodeOffset[2], spec.refNodeOffset[3])}
    end

    for _, attacherJoint in ipairs(rootVehicle.spec_attacherJoints.attacherJoints) do
        local isBackJoint = string.find(string.lower(getName(attacherJoint.jointTransform)), FAPSupportFrame.BACK_JOINT_NAME_TOKEN) ~= nil

        if isBackJoint then
            if attacherJoint.jointType == AttacherJoints.JOINTTYPE_IMPLEMENT and spec.lowerRootVehicleBackAttacherImplement == nil then
                spec.lowerRootVehicleBackAttacherImplement = attacherJoint
                if attacherJoint.topArm ~= nil then
                    setVisibility(attacherJoint.topArm.node, false)
                    attacherJoint.topArm:setIsActive(true)
                end
            elseif attacherJoint.jointType == AttacherJoints.JOINTTYPE_TRAILER and spec.lowerRootVehicleBackAttacherTrailer == nil then
                spec.lowerRootVehicleBackAttacherTrailer = attacherJoint
                if attacherJoint.transNode ~= nil then
                    setVisibility(attacherJoint.transNode, false)
                    if refPosition == nil then
                        refPosition = {localToLocal(attacherJoint.transNode, spec.node, table.unpack(spec.refNodeOffset))}
                    end
                end
            end
        end
    end

    if refPosition == nil then
        spec.isValid = false
        setVisibility(spec.node, false)
        return
    end

    local x, y, z = getTranslation(spec.node)
    local nX = x + spec.refNodeOffset[1]
    local nY = y + spec.refNodeOffset[2]
    local nZ = z + refPosition[3] + spec.refNodeOffset[3]
    setTranslation(spec.node, nX, nY, nZ)

    spec.isValid = true
    spec.isFrameDirty = false

    self:setPipeSupportFramePosition()
end
