if isServer() then return end

if not TowBarMod then TowBarMod = {} end
TowBarMod.Landtrain = TowBarMod.Landtrain or {}
if TowBarMod.Landtrain._towSyncClientLoaded then return end
TowBarMod.Landtrain._towSyncClientLoaded = true

local function log(msg)
    print("[Landtrain][TowSyncClient] " .. tostring(msg))
end

local function isTrailerScriptName(scriptName)
    if not scriptName then return false end
    return string.find(string.lower(scriptName), "trailer", 1, true) ~= nil
end

local function shouldForceNotTowingScript(vehicle)
    if not vehicle then return false end
    local scriptName = vehicle:getScriptName()
    return not isTrailerScriptName(scriptName)
end

local function restoreTrailerScriptIfNeeded(vehicle)
    if not vehicle then return end
    if vehicle:getScriptName() ~= "notTowingA_Trailer" then return end

    local md = vehicle:getModData()
    local originalScript = md and md.towBarOriginalScriptName or nil
    if originalScript and isTrailerScriptName(originalScript) then
        vehicle:setScriptName(originalScript)
    end
end

local function ensureAttachment(vehicle, attachmentId)
    if not vehicle or not attachmentId then return false end

    local script = vehicle:getScript()
    if not script then return false end
    if script:getAttachmentById(attachmentId) ~= nil then return true end

    local wheelCount = script:getWheelCount()
    local yOffset = -0.5
    if wheelCount > 0 then
        local wheel = script:getWheel(0)
        if wheel and wheel:getOffset() then
            yOffset = wheel:getOffset():y() + 0.1
        end
    end

    local chassis = script:getPhysicsChassisShape()
    if not chassis then return false end

    local attach = ModelAttachment.new(attachmentId)
    if attachmentId == "trailer" then
        attach:getOffset():set(0, yOffset, -chassis:z() / 2 - 0.1)
        attach:setZOffset(-1)
    else
        attach:getOffset():set(0, yOffset, chassis:z() / 2 + 0.1)
        attach:setZOffset(1)
    end
    script:addAttachment(attach)
    return true
end

local function resolveVehicle(id)
    if not id then return nil end
    return getVehicleById(id)
end

local function reconcilePairState(vehicleA, vehicleB, attachmentA, attachmentB)
    if not vehicleA or not vehicleB then return end
    restoreTrailerScriptIfNeeded(vehicleA)

    if TowBarMod.Utils and TowBarMod.Utils.updateAttachmentsForRigidTow then
        TowBarMod.Utils.updateAttachmentsForRigidTow(vehicleA, vehicleB, attachmentA, attachmentB)
    end

    local towingMd = vehicleA:getModData()
    local towedMd = vehicleB:getModData()
    local currentScript = vehicleB:getScriptName()

    if towingMd then
        towingMd["isTowingByTowBar"] = true
        vehicleA:transmitModData()
    end
    if towedMd then
        if towedMd.towBarOriginalScriptName == nil and currentScript ~= "notTowingA_Trailer" then
            towedMd.towBarOriginalScriptName = currentScript
        end
        if towedMd.towBarOriginalMass == nil then
            towedMd.towBarOriginalMass = vehicleB:getMass()
        end
        if towedMd.towBarOriginalBrakingForce == nil then
            towedMd.towBarOriginalBrakingForce = vehicleB:getBrakingForce()
        end
        towedMd["isTowingByTowBar"] = true
        towedMd["towed"] = true
        vehicleB:transmitModData()
    end

    vehicleB:setScriptName("notTowingA_Trailer")
    if TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then
        pcall(TowBarMod.Hook.setVehiclePostAttach, nil, vehicleB)
    end
    log("reconcilePairState A=" .. tostring(vehicleA:getId()) .. " B=" .. tostring(vehicleB:getId())
        .. " attachmentA=" .. tostring(attachmentA) .. " attachmentB=" .. tostring(attachmentB))
end

local function applyAttachSync(args)
    if not args then return end

    local vehicleA = resolveVehicle(args.vehicleA)
    local vehicleB = resolveVehicle(args.vehicleB)
    if not vehicleA or not vehicleB then
        log("applyAttachSync skipped: missing vehicle A=" .. tostring(args.vehicleA) .. " B=" .. tostring(args.vehicleB))
        return
    end

    local attachmentA = args.attachmentA or "trailer"
    local attachmentB = args.attachmentB or "trailerfront"
    if not ensureAttachment(vehicleA, attachmentA) or not ensureAttachment(vehicleB, attachmentB) then
        log("attach sync skipped: missing attachment A=" .. tostring(attachmentA) .. " B=" .. tostring(attachmentB))
        return
    end

    if vehicleA:getVehicleTowing() == vehicleB and vehicleB:getVehicleTowedBy() == vehicleA then
        log("applyAttachSync already linked A=" .. tostring(vehicleA:getId()) .. " B=" .. tostring(vehicleB:getId()))
    else
        log("applyAttachSync addPointConstraint A=" .. tostring(vehicleA:getId()) .. " B=" .. tostring(vehicleB:getId())
            .. " attachmentA=" .. tostring(attachmentA) .. " attachmentB=" .. tostring(attachmentB))
        vehicleA:addPointConstraint(nil, vehicleB, attachmentA, attachmentB)
    end

    reconcilePairState(vehicleA, vehicleB, attachmentA, attachmentB)
end

local function safeBreak(vehicle)
    if not vehicle then return end
    if vehicle:getVehicleTowing() == nil and vehicle:getVehicleTowedBy() == nil then return end
    vehicle:breakConstraint(true, true)
end

local function applyDetachSync(args)
    if not args then return end
    safeBreak(resolveVehicle(args.vehicleA))
    safeBreak(resolveVehicle(args.vehicleB))
end

local function onServerCommand(module, command, args)
    if module ~= "landtrain" then return end

    if command == "forceAttachSync" then
        applyAttachSync(args)
    elseif command == "forceDetachSync" then
        applyDetachSync(args)
    end
end

Events.OnServerCommand.Add(onServerCommand)

log("LandtrainTowSyncClient loaded")
