Best way to organize book-page structure

I don’t know if this is the optimal, it’s one suggestion.

For me I’d created the qa post type as hierachical.

Then for every page in the book I’d created a child qa post using menu_order field to handle the page number.

This child has a 3rd level child for problems.

Hooking intro pre_get_posts for qa archive, you can set the post_parent to 0, so in the archive view you get only books, and not the big list of pages and problems.

Last thing to is create a rewrite rule that convert the url into some query vars.

E.g.

<domain>/qa/<book-name>/<page-number>/<problem-number> should be rewritten in

 <domain>/index.php?name=<book-name>&post_type=qa&number=<page-number>&problem=<problem-number>

That url will end in a very simple query where post id = <problem-number>.

Example code:

function my_book_rules() {
  add_rewrite_rule( 'qa/([^/]+)/([0-9]+)/([0-9]+)$', 'index.php?&post_type=qa&name=$matches[1]&number=$matches[2]&problem=$matches[3]', 'top' );
  add_rewrite_rule( 'qa/([^/]+)/([0-9]+)$', 'index.php?&post_type=qa&name=$matches[1]&number=$matches[2]', 'top');
}
add_action('init', 'my_book_rules');

function my_book_query_vars($vars) {
  $vars[] = 'problem';
  $vars[] = 'number';   
  return $vars;
}

add_filter('query_vars', 'my_book_query_vars'); 

function my_book_query( $query ) {
  if ( is_admin() || ! is_main_query() ) return;
  if ( is_archive() && $query->get('post_type') == 'qa') {
     $query->set('post_parent', 0);
  } elseif ( is_single() && get_post_type() == 'qa') {
    $post = get_queried_object();
    if ( $post->post_parent == 0 && $query->get('number') && ! $query->get('problem') ) {
      // a page number is requested
      $query->set('name', '');
      global $wpdb;
      $n = intval($query->get('number'));
      $p = (int) $wpdb->get_var("SELECT ID FROM $wpdb->posts WHERE post_status="publish" AND post_type="qa" AND post_parent = $post->ID AND menu_order = $n");
      $query->set('p', $n);
    } elseif( $query->get('problem') ) {
      // a problem is required
      $query->set('name', '');
      $n = intval($query->get('problem'));
      $query->set('p', $n);
    }
  }
}
add_action('pre_get_posts', 'my_book_query');

In the single view template single-qa.php you can load sub tempalte for books, pages or problems, something like:

if ( $post->post_parent == 0 ) {
   get_template_part('book', 'content');
} else {
  $parent = get_post( $post->post_parent );
  if ( $parent->post_parent == 0 ) {
    get_template_part('page-number', 'content');
  } else {
    get_template_part('problem', 'content');
  }
}

Another snippet is for display the book title and the page number inside the problem template (problem-content.php according to previous snippet),

$thepage = get_post( $post->post_parent);
$pagenumber = $thepage->menu_order
$the_book = get_post( $thepage->post_parent )->post_title

echo "The problem " . get_the_title() . 
  " is in the book " . $the_book->post_title .
  " at the page number " . $pagenumber;

Note that the code is a proof of concept and untested.