Supose you call stepper.move(6000). You then need to call stepper.run() at least 6000 times and you have to repeat calling it for as long as it takes to make the 6000 steps, taking into account the maximum speed and acceleration/decceleration that the library will calculate.
When you make two calls to move() in succession (or before the previous move has been completed) the effect is additive. So if you do:
stepper.move(1000);
stepper.move(-1000);
you are in effect telling your motor to not move. While this:
stepper.move(1000);
stepper.move(-500);
will move the motor 500 steps forward.
If you want to issue moves in succession you have to wait until the previous move is finished (repeatedly calling run() while you wait) and then do the next move. The best way to do this is to implement a queue. Just queue the moves and in the loop check if the current move has finished. If it has, take the next move from the queue and issue it to the motor.
You can check if the motor is running using stepper.isRunning(). Note however that this way the motor will first deccelerate to a full stop before accelerating again for the next move. If you don't want this you may use stepper.runSpeed() to disable acceleration/decceleration and run at constant speed, or you may issue the next move a little earlier (i.e before a full stop) by checking stepper.distanceToGo().
This later method however will skip some steps if the next move is in the opposite direction. You can avoid that by checking the previous direction against the new direction: if in the same direction, you can issue the move early, if in opposite direction, wait for a full stop and then issue the next move.