What is the best way to define constant options for a theme?

Generally you should try to keep the amount of global variables/constants to a minimum. The third (constant) option might be a good idea if you don’t have too many options, but it can get messy with time as you add more and more options.

What I usually do, is scope the constant to the corresponding classes as const or methods returning the settings values. For example, if I have a custom post type called Book, I would create a class that takes care of all the related functionality, such as registering the post type. Additionally all theme classes can be namespaced to avoid class name collisions, but I’ll show the example without namespacing for simplicity:

class BookPost{
  const POST_TYPE = 'book';
  
  public static function register(){
    register_post_type(self::POST_TYPE, array('labels' => self::get_labels()));
  }

  public static function get_labels(){
    return array(
      'name' => __('Book', 'MyTheme'),
      //...
    );
  }
}

Then you can call register from outside or inside of the class without worrying about the parameters, for example:

add_action( 'init', 'BookPost::register' );

and if you need to access the post type (or other args) from anywhere else in the code, you can just use:

BookPost::POST_TYPE;
BookPost::get_labels();

There are a couple of things to mention here:

  1. Since you can’t use expressions (such as calling the __() method) in constants, you will have to use methods for retrieving the more dynamic values like label.
  2. Here I’m using static methods because this particular example doesn’t require storing state, and also it’s easier to call the methods statically. But of course, based on your requirements you might prefer instantiating an object and using standard non-static methods.

I’m not saying that this is the best approach, but this is what I usually do and it’s been working well for me. The additional advantage is that you can add more helper methods to this class as your code grows and you won’t need to worry about passing the post type, etc. For example, you can have something like:

BookPost::get_latest();
BookPost::get_all();
BookPost::get_by_genre();

Also in terms of performance, registering a constant in a class vs using an array constant with options shouldn’t make any meaningful difference in scenarios like this one.