WP_Query | IF within $args array | Help me only add specific arguments if the argument is not blank

If you haven’t done so already, I would recommend you to turn on debug logging in wp-config.php as it makes it easier to trace the error in your code.

// added to wp-config.php
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true ); // find error.log in /wp-content, shows backtrace for errors
define( 'WP_DEBUG_DISPLAY', false ); // true if you want errors to be shown on screen

The second thing you could do is to check for the $_POST parameters with ternary operator ( $condition ? true : false; ) and set any required default values. As this prevents any invalid key errors, if for some reason the keys you’re expecting aren’t present in $_POST.

$paged              = ! empty( $_POST['page'] ) ? $_POST['page'] : '';
$display_count      = ! empty( $_POST['display_count'] ) ? $_POST['display_count'] : 5; // set default value as the "else" case
$direction          = ! empty( $_POST['direction'] ) ? $_POST['direction'] : '';
$search_term        = ! empty( $_POST['search_term'] ) ? $_POST['search_term'] : '';
$search_tags        = ! empty( $_POST['search_tags'] ) ? $_POST['search_tags'] : '';
$search_categories  = ! empty( $_POST['search_categories'] ) ? $_POST['search_categories'] : '';
$search_relation    = ! empty( $_POST['search_relation'] ) ? $_POST['search_relation'] : '';

Above empty() checks that the key exists in $_POST and there’s some value assigned to it. If so, the value is set to the variable, else the variable will get an empty string or some default value as its value.

Personally I don’t use the alternative if syntax unless I’m adding php into longer segments of html. This looks a little cleaner to me, but this is just a matter of preference.

$offset_modifier = 0;
if ( 'prev' === $direction ) {
  $offset_modifier = $paged - 2;
} else if ( 'prev' === $direction ) {
  $offset_modifier = $paged;
} else if ( 'last' === $direction ) {
  $offset_modifier = $paged - 1;
}
$offsetcalc = $offset_modifier * $display_count;

You can set your default $args before any of those if statements to reduce the number of repeated lines.

$args = array(
  'post_type'         => 'product',
  'post_status'       => 'publish',
  'orderby'           => 'menu_order',
  'order'             => 'ASC',
  'posts_per_page'    => $display_count,
  'page'              => $paged, // maybe protect against negative numbers with ternary operator?
  'offset'            => $offsetcalc, // maybe protect against negative numbers with ternary operator?
  'tax_query'         => array(),
);

With the search related variables set at the beginning of your code, you can trust that the variables will always be set and they are either empty, have some default value, or contain the user submitted value. You can do simple checks with the variables and add any required parameters to the default $args.

if ( $search_term ) {
  $args['s'] = $search_term;
}

if ( $search_tags ) {
  $args['tax_query']['product_tag'] = array( // named keys are allowed, if I remember correctly
    'taxonomy'          => 'product_tag',
    'field'             => 'slug',
    'terms'             => array($search_tags),
  );
}

if ( $search_categories ) {
  $args['tax_query']['product_cat'] = array(
    'taxonomy'          => 'product_cat',
    'field'             => 'slug',
    'terms'             => array($search_categories),
  );
}

if ( $search_tags && $search_categories ) {
  $args['tax_query']['relation'] = $search_relation;
  $args['tax_query']['product_tag']['relation'] = $search_relation;
  $args['tax_query']['product_cat']['relation'] = $search_relation;
}

You can also overwrite the default values later, if needed.

if ( $some_condition ) {
  $args['post_type'] = 'another_post_type';  
}