<?php
// disable if product variation table or only dynamic filters lazy load set
add_filter('wcpt_shortcode_attributes', 'wcpt__dynamic_filter__remove_shortcode_attribute');
function wcpt__dynamic_filter__remove_shortcode_attribute($atts)
{
  if (wcpt_module_is_disabled('dynamic-filter-recount')) {
    return $atts;
  }

  // variation table
  if (!empty($atts['product_variations'])) {
    unset($atts['dynamic_recount']);
    unset($atts['dynamic_hide_filters']);
  }

  // only dynamic filter lazy load
  if (!array_intersect(array('dynamic_recount', 'dynamic_hide_filters'), array_keys($atts))) {
    unset($atts['dynamic_filters_lazy_load']);
  }

  return $atts;
}

// disable for other reasons or add filter callbacks
add_action('wcpt_before_product_table_is_processed', 'wcpt__dynamic_filter__init', 10, 1);
function wcpt__dynamic_filter__init($attributes)
{
  $table_data = wcpt_get_table_data();

  if (
    wcpt_module_is_disabled('dynamic-filter-recount') ||
    !empty($table_data['query']['sc_attrs']['_return_query_args']) ||
    (
      empty($table_data['query']['sc_attrs']['dynamic_recount']) &&
      empty($table_data['query']['sc_attrs']['dynamic_hide_filters'])
    )
  ) {
    // remove filters applied by prev table
    remove_filter('wcpt_nav_filter_option', 'wcpt__dynamic_filter__gather_option', 100);
    remove_filter('wcpt_nav_filter_option', 'wcpt__dynamic_filter__insert_placeholders', 100);
    remove_filter('wcpt_query_args', 'wcpt__dynamic_filter__cache_results', 200);
    remove_filter('wcpt_variation_query_args', 'wcpt__dynamic_filter__cache_results', 200);
    remove_filter('wcpt_navigation', 'wcpt__dynamic_filter__modify_nav', 100);

    return $attributes;
  }

  // filter callbacks
  // -- get each filter option
  add_filter('wcpt_nav_filter_option', 'wcpt__dynamic_filter__gather_option', 100, 3);
  // -- add count placeholder if not lazy load and counts are required
  if (
    empty($table_data['query']['sc_attrs']['dynamic_filters_lazy_load']) &&
    !empty($table_data['query']['sc_attrs']['dynamic_recount'])
  ) {
    add_filter('wcpt_nav_filter_option', 'wcpt__dynamic_filter__insert_placeholders', 100, 3);
  }
  // -- cache
  add_filter('wcpt_query_args', 'wcpt__dynamic_filter__cache_results', 200, 1);
  add_filter('wcpt_variation_query_args', 'wcpt__dynamic_filter__cache_results', 200, 1);
  // -- modify the nav if not lazy load
  if (empty($table_data['query']['sc_attrs']['dynamic_filters_lazy_load'])) {
    add_filter('wcpt_navigation', 'wcpt__dynamic_filter__modify_nav', 100, 1);
  }

  return $attributes;
}

// container gets lazy load html attrs
add_filter('wcpt_container_html_attributes', 'wcpt__dynamic_filter__container_html_attribute');
function wcpt__dynamic_filter__container_html_attribute($attr)
{
  if (wcpt_module_is_disabled('dynamic-filter-recount')) {
    return $attr;
  }

  $table_data = wcpt_get_table_data();
  global $wcpt__dynamic_filter__query_data;

  if (
    !empty($table_data['query']['sc_attrs']['dynamic_filters_lazy_load']) &&
    !empty($wcpt__dynamic_filter__query_data)
  ) {
    $attr .= ' data-wcpt-encrypted-dynamic-filters-lazy-load-cache="' . wcpt_encrypt(json_encode($wcpt__dynamic_filter__query_data)) . '"';
  }
  return $attr;
}

// cache results
function wcpt__dynamic_filter__cache_results($query_args)
{
  $table_data = wcpt_get_table_data();
  $sc_attrs = $table_data['query']['sc_attrs'];

  if (
    !empty($sc_attrs['dynamic_hide_filters']) &&
    !empty($sc_attrs['dynamic_recount'])
  ) {
    $type = 'both';
  }

  if (
    empty($sc_attrs['dynamic_hide_filters']) &&
    !empty($sc_attrs['dynamic_recount'])
  ) {
    $type = 'count';
  }

  if (
    !empty($sc_attrs['dynamic_hide_filters']) &&
    empty($sc_attrs['dynamic_recount'])
  ) {
    $type = 'hide';
  }

  $query = new WP_Query(
    array_merge(
      $query_args,
      ['posts_per_page' => -1, 'fields' => 'ids']
    )
  );

  $GLOBALS['wcpt__dynamic_filter__query_data'] = array(
    'results' => $query->posts,
    'options' => !empty($GLOBALS['wcpt__dynamic_filter__options']) ? $GLOBALS['wcpt__dynamic_filter__options'] : [],
    'type' => $type,
    'table_id' => $table_data['id'],
    'sc_attrs' => $sc_attrs,
    'query_args' => $query_args,
  );

  return $query_args;
}

// lazy load ajax endpoint
add_action('wc_ajax_wcpt__dynamic_filter__lazy_load', 'wcpt__dynamic_filter__lazy_load');
function wcpt__dynamic_filter__lazy_load()
{
  $dynamic_filter_data = json_decode(wcpt_decrypt($_REQUEST['wcpt_dynamic_filter_encrypted_cache']), true);

  $dynamic_filter_data['options'] = wcpt__dynamic_filter__add_count($dynamic_filter_data['options'], $dynamic_filter_data['results'], $dynamic_filter_data['type'], $dynamic_filter_data['table_id'], $dynamic_filter_data['sc_attrs'], $dynamic_filter_data['query_args']);

  // add style and script if hiding empty filter options
  $combined = wcpt__dynamic_filter__get_script_and_style($dynamic_filter_data['options'], $dynamic_filter_data['table_id'], $dynamic_filter_data['type']);

  echo json_encode(
    array(
      'options' => $dynamic_filter_data['options'],
      'style' => $combined['style'],
      'script' => $combined['script'],
    )
  );

  die();
}

// gather filter options (and create placeholders)
function wcpt__dynamic_filter__gather_option($option, $filter, $params = null)
{
  if (empty($GLOBALS['wcpt__dynamic_filter__options'])) {
    $GLOBALS['wcpt__dynamic_filter__options'] = array();
  }

  $value = gettype($option) === 'object' ? $option->value : $option['value'];
  $placeholder = '%%' . $value . '%%';

  // replace non-unique values 
  if ($filter === 'availability') {
    $placeholder = '%%_availability_%%'; // real value was (bool) true
  }
  if ($filter === 'on_sale') {
    $placeholder = '%%_on_sale_%%'; // real value was 'NO IN'
  }

  $GLOBALS['wcpt__dynamic_filter__options'][] = array(
    'filter' => $filter,
    'value' => $value,
    'taxonomy' => !empty($params) && !empty($params['taxonomy']) ? $params['taxonomy'] : null,
    'placeholder' => $placeholder,
  );

  if (gettype($option) == 'array') {
    $option['placeholder'] = $placeholder;
  } else if (gettype($option) == 'object') {
    $option->placeholder = $placeholder;
  }

  return $option;
}

// insert placeholders in filter options
function wcpt__dynamic_filter__insert_placeholders($option, $filter, $params = null)
{
  if (gettype($option) == 'array') {
    $option['label'] .= $option['placeholder'];
  } else if (gettype($option) == 'object') {
    $option->label .= $option->placeholder;
  }

  return $option;
}

// get script and style
function wcpt__dynamic_filter__get_script_and_style($options, $table_id, $type)
{
  $hide_selectors = array();

  $taxonomies = array();
  $keep_taxonomy = array();
  $hide_taxonomies = array();

  $availability_exists = false;
  $on_sale_exists = false;

  $keep_availability = false;
  $keep_on_sale = false;

  foreach ($options as $option) {

    // record existing filter taxononomy / type

    // -- taxonomy
    if (
      !empty($option['taxonomy']) &&
      !in_array($option['taxonomy'], $taxonomies)
    ) {
      $taxonomies[] = $option['taxonomy'];
    }

    // -- availability
    if ($option['filter'] == 'availability') {
      $availability_exists = true;
    }

    // -- on sale
    if ($option['filter'] == 'on_sale') {
      $on_sale_exists = true;
    }

    // hide option
    if (
      !$option['count'] &&
      !in_array($option['filter'], apply_filters('wcpt_dynamic_filter_skip_hide_empty_filters', array()))
    ) {
      $hide_selectors[] = ' [data-wcpt-filter="' . $option['filter'] . '"].wcpt-filter [data-wcpt-value="' . $option['value'] . '"]';

      // spare entire filter if an option has count
    } else {

      // -- taxonomies
      if (!in_array($option['taxonomy'], $keep_taxonomy)) {
        $keep_taxonomy[] = $option['taxonomy'];
      }

      // -- availability
      if ($option['filter'] === 'availability') {
        $keep_availability = true;
      }

      // -- on sale      
      if ($option['filter'] === 'on_sale') {
        $keep_on_sale = true;
      }

    }

  }

  // hide entire filters

  // -- taxonomy
  $hide_taxonomies = array_diff($taxonomies, $keep_taxonomy);
  foreach ($hide_taxonomies as $taxonomy) {
    $hide_selectors[] = ' .wcpt-filter[data-wcpt-taxonomy="' . $taxonomy . '"] ';
  }

  // -- availability
  if (
    $availability_exists &&
    !$keep_availability
  ) {
    $hide_selectors[] = ' .wcpt-filter[data-wcpt-filter="availability"] ';
  }

  // -- on sale
  if (
    $on_sale_exists &&
    !$keep_on_sale
  ) {
    $hide_selectors[] = ' .wcpt-filter[data-wcpt-filter="on_sale"] ';
  }

  $style = '';
  $script = '';

  if (count($hide_selectors)) {
    // Split selectors into chunks of 1000
    $table_selectors = array();
    $modal_selectors = array();

    foreach ($hide_selectors as $selector) {
      $table_selectors[] = '.wcpt-' . $table_id . ' ' . $selector;
      $modal_selectors[] = '[data-wcpt-table-id="' . $table_id . '"].wcpt-nav-modal ' . $selector;
    }

    $all_selectors = array_merge($table_selectors, $modal_selectors);
    $selector_chunks = array_chunk($all_selectors, 1000);

    $style = '';
    // hide invalid options
    if (in_array($type, array('both', 'hide'))) {
      $style = '<style>';
      foreach ($selector_chunks as $chunk) {
        $style .= implode(', ', $chunk) . ' { display: none !important; } ';
      }
      $style .= '</style>';
    }

    $hide_selectors = implode(', ', $all_selectors);

    // disable invalid options
    $script = '<script>jQuery(function($){ $(\'' . $hide_selectors . '\').addClass("wcpt-disabled-by-dynamic-filter").find("input").prop({"checked": false, "disabled": true}); })</script>';
  }

  return array(
    'style' => $style,
    'script' => $script
  );
}

// replace placeholders
function wcpt__dynamic_filter__replace_placeholders(&$nav_markup, $options)
{
  $replacements = array();
  foreach ($options as $option) {
    $replacements[$option['placeholder']] = ' <span class="wcpt-count">(' . $option['count'] . ')</span>';
  }

  if (!empty($replacements)) {
    $nav_markup = strtr($nav_markup, $replacements);
  }
}

// modify the nav
function wcpt__dynamic_filter__modify_nav($nav_markup)
{
  $dynamic_filter_data = $GLOBALS['wcpt__dynamic_filter__query_data'];

  if (empty($dynamic_filter_data['options'])) {
    return $nav_markup;
  }

  $dynamic_filter_data['options'] = wcpt__dynamic_filter__add_count($dynamic_filter_data['options'], $dynamic_filter_data['results'], $dynamic_filter_data['type'], $dynamic_filter_data['table_id'], $dynamic_filter_data['sc_attrs'], $dynamic_filter_data['query_args']);

  // replace placeholders if recounting filter options
  if (in_array($dynamic_filter_data['type'], array('both', 'count'))) {
    wcpt__dynamic_filter__replace_placeholders($nav_markup, $dynamic_filter_data['options']);
  }

  // add styles if hiding empty filter options
  $combined = wcpt__dynamic_filter__get_script_and_style($dynamic_filter_data['options'], $dynamic_filter_data['table_id'], $dynamic_filter_data['type']);
  $nav_markup = $combined['style'] . $combined['script'] . $nav_markup;

  return $nav_markup;
}

// supplement the options data with potential result count for each
function wcpt__dynamic_filter__add_count($options, $results, $type, $table_id, $sc_attrs, $query_args)
{
  global $wpdb;

  $results_without_taxonomy = array();
  $attribute_taxonomies = array();
  $meta_attribute_taxonomies = array();
  $taxonomy_option_groups = array();
  $filtered_taxonomies = array();
  $category_term_taxonomy_ids = array();

  $query_args['posts_per_page'] = -1;
  $query_args['fields'] = 'ids';

  // Remove ordering args to optimize query performance
  unset($query_args['orderby']);
  unset($query_args['order']);
  unset($query_args['meta_key']);

  $attribute_relation__or = false; // whether all attribute filters need to be satisifed or just one for a product to match
  if (
    !empty($sc_attrs['attribute_relation']) &&
    strtoupper($sc_attrs['attribute_relation']) === 'OR' &&
    !empty($query_args['tax_query']['wcpt_attributes__or'])
  ) {
    $attribute_relation__or = true;
  }

  // max result count
  if ($type !== array('hide')) {
    if (!empty($sc_attrs['dynamic_recount_max']) && is_numeric($sc_attrs['dynamic_recount_max'])) {
      $max_count = $sc_attrs['dynamic_recount_max'];
    } else {
      $max_count = 200;
    }
  } else {
    $max_count = 1;
  }

  $post_ids = $results;

  // product variations
  if (
    !empty($query_args['post_type']) &&
    $query_args['post_type'] === 'product_variation'
  ) {

    if ($attribute_relation__or) {
      // @TODO later
    } else {
      if (
        !empty($query_args['meta_query']) &&
        is_array($query_args['meta_query'])
      ) {

        foreach ($query_args['meta_query'] as $arr) {
          if (
            is_array($arr) &&
            !empty($arr['key']) &&
            strpos($arr['key'], 'attribute_') !== false
          ) {
            $meta_attribute_taxonomies[] = str_replace('attribute_', '', $arr['key']);
          }
        }
      }
    }

    // regular products
  } else {
    if (!empty($query_args['tax_query'])) {

      foreach ($query_args['tax_query'] as $arr) {
        if (is_array($arr) && !empty($arr['taxonomy'])) {
          if ('pa_' === substr($arr['taxonomy'], 0, 3)) {
            $attribute_taxonomies[] = $arr['taxonomy'];
            $filtered_taxonomies[] = $arr['taxonomy'];
          } else {
            $taxonomies[] = $arr['taxonomy'];
            $filtered_taxonomies[] = $arr['taxonomy'];
          }
        }
      }

      if ($attribute_relation__or && !empty($query_args['tax_query']['wcpt_attributes__or'])) {
        $or_taxonomies = array_column(
          array_filter($query_args['tax_query']['wcpt_attributes__or'], function ($arr) {
            return is_array($arr) && !empty($arr['taxonomy']);
          }),
          'taxonomy'
        );
        $attribute_taxonomies = array_merge($attribute_taxonomies, $or_taxonomies);
        $filtered_taxonomies = array_merge($filtered_taxonomies, $or_taxonomies);
      }
    }

  }

  $category_filtering = false;
  if (!empty($query_args['tax_query'])) {
    foreach ($query_args['tax_query'] as $arr) {
      if (
        is_array($arr) &&
        !empty($arr['taxonomy']) &&
        $arr['taxonomy'] == 'product_cat'
      ) {
        $category_filtering = true;
        break;
      }
    }
  }


  foreach ($options as &$option) {

    $count = 0;

    if (!empty($option['taxonomy'])) {
      $taxonomy_option_groups[$option['taxonomy']][] = $option['value'];
    }

    // attribute
    if ($option['filter'] == 'attribute') {

      // product variations - attributes are stored as post meta
      if ($query_args['post_type'] === 'product_variation') {


        // attribute relation - OR ie any of the attribute filters may be satisfied for a product to match
        if ($attribute_relation__or) {

          // @TODO later - convert to meta query
          // $_query_args = $query_args;
          // unset($_query_args['tax_query']['wcpt_attributes__or']);
          // $_query_args['tax_query'][] = array(
          //   'taxonomy' => $option['taxonomy'],
          //   'field' => 'term_taxonomy_id',
          //   'terms' => $option['value'],
          // );
          // $query = new WP_Query($_query_args);
          // $_count = $query->post_count;

        } else {

          // attr not in use 
          if (!in_array($option['taxonomy'], $meta_attribute_taxonomies)) {
            $_post_ids = $post_ids;

            // attr in use
          } else {

            if (empty($results_without_taxonomy[$option['taxonomy']])) {
              // re-build query without this meta attribute
              $_query_args = $query_args;
              $_meta_query = array();

              foreach ($_query_args['meta_query'] as $key => $val) {
                if ($key === 'relation') {
                  $_meta_query['relation'] = $val;
                } else if (
                  !is_array($val) ||
                  empty($val['key']) ||
                  $val['key'] !== 'attribute_' . $option['taxonomy']
                ) {
                  $_meta_query[] = $val;
                }
              }

              $_meta_query['relation'] = 'AND';
              $_query_args['meta_query'] = $_meta_query;

              // get results for original query but without this meta attribute
              $query = new WP_Query($_query_args);
              $results_without_taxonomy[$option['taxonomy']] = $query->posts;
            }

            $_post_ids = $results_without_taxonomy[$option['taxonomy']];
          }

          // how many products from the original result coincide with this term?
          if (count($_post_ids)) {

            // Get the term slug for this taxonomy/value
            $term_slug = $wpdb->get_var($wpdb->prepare(
              "SELECT slug FROM {$wpdb->terms} t 
                 JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id 
                 WHERE tt.term_taxonomy_id = %d",
              $option['value']
            ));

            // Query variation postmeta for matching attribute value
            $query = $wpdb->prepare(
              "SELECT DISTINCT post_id 
                 FROM {$wpdb->postmeta} 
                 WHERE post_id IN (" . implode(',', $_post_ids) . ")
                 AND meta_key = %s 
                 AND meta_value = %s
                 LIMIT %d",
              'attribute_' . urlencode($option['taxonomy']),
              $term_slug,
              $max_count + 1
            );

            $count = count($wpdb->get_col($query));
          }

        }

        // regular product types
      } else {

        // 'OR' relationship between sets of attributes
        if ($attribute_relation__or) {
          $_query_args = $query_args;
          unset($_query_args['tax_query']['wcpt_attributes__or']);
          $_query_args['tax_query'][] = array(
            'taxonomy' => $option['taxonomy'],
            'field' => 'term_taxonomy_id',
            'terms' => $option['value'],
          );
          $query = new WP_Query($_query_args);
          $_count = $query->post_count;

          // 'AND' relationship between sets of attributes
        } else {

          $results_without_taxonomy = wcpt__dynamic_filter__update_results_without_taxonomy(
            $results_without_taxonomy,
            $option['taxonomy'],
            $query_args,
            $post_ids,
            $filtered_taxonomies
          );
        }

      }

      // taxonomy
    } else if ($option['filter'] == 'taxonomy') {

      // taxonomy not in use 
      $results_without_taxonomy = wcpt__dynamic_filter__update_results_without_taxonomy(
        $results_without_taxonomy,
        $option['taxonomy'],
        $query_args,
        $post_ids,
        $filtered_taxonomies
      );

      // category
    } else if ($option['filter'] == 'category') {

      // taxonomy not in use 
      $results_without_taxonomy = wcpt__dynamic_filter__update_results_without_taxonomy(
        $results_without_taxonomy,
        $option['taxonomy'],
        $query_args,
        $post_ids,
        $filtered_taxonomies
      );

      $category_term_taxonomy_ids[] = $option['value'];

      // on sale
    } else if ($option['filter'] == 'on_sale') {

      // product variations
      if (
        !empty($query_args['post_type']) &&
        $query_args['post_type'] === 'product_variation'
      ) {
        // Get variations that are on sale through sale price
        $variation_sale_ids = $wpdb->get_col(
          "SELECT post_id FROM {$wpdb->postmeta} 
           WHERE post_id IN (" . implode(',', $post_ids) . ")
           AND meta_key = '_sale_price'
           AND meta_value != ''
           AND meta_value != '0'"
        );

        $count = count($variation_sale_ids);

        // regular products
      } else {
        $count = count(array_intersect(wc_get_product_ids_on_sale(), $post_ids));
      }


      // availability
    } else if ($option['filter'] == 'availability') {

      $product_visibility_terms = wc_get_product_visibility_term_ids();

      if (count($post_ids)) {
        $query = new WP_Query(
          array(
            'post_type' => 'product',
            'posts_per_page' => ($max_count + 1),
            'post__in' => $post_ids,
            'fields' => 'ids',
            'tax_query' => array(
              array(
                'taxonomy' => 'product_visibility',
                'field' => 'term_taxonomy_id',
                'terms' => array($product_visibility_terms['outofstock']),
                'operator' => $option['value'],
              ),
            ),
          )
        );

        // does this filter option have a count?
        $count = $query->post_count > $max_count ? $max_count . '+' : $query->post_count;

      } else {
        $count = 0;

      }

    }

    $option['count'] = apply_filters('wcpt_dynamic_filter_option_count', $count, $option, $post_ids);
  }
  unset($option);

  foreach ($taxonomy_option_groups as $taxonomy => $option_values) {
    if (!empty($results_without_taxonomy[$taxonomy])) {
      $term_counts = wcpt__dynamic_filter__get_term_object_counts($option_values, $results_without_taxonomy[$taxonomy]);

      foreach ($options as &$option) {
        // Check if this option matches the current taxonomy and has a valid count
        if (
          $option['taxonomy'] === $taxonomy &&
          in_array($option['value'], $option_values) &&
          isset($term_counts[$option['value']])
        ) {
          // Get the count for this term
          $count = $term_counts[$option['value']];

          // Format count with max limit if needed
          $option['count'] = ($count > $max_count) ? $max_count . '+' : $count;
        }
      }
    }
  }

  // category

  // -- convert taxonomy term ids to term ids
  if (!empty($category_term_taxonomy_ids)) {
    $query = "SELECT term_id 
       FROM {$wpdb->term_taxonomy}
       WHERE term_taxonomy_id IN (" . implode(',', array_map('intval', $category_term_taxonomy_ids)) . ")";
    $category_term_ids = $wpdb->get_col($query);

    // -- get all child term ids for each term
    $category_family_term_ids = [];

    // -- also collect all terms ids including children
    $category_all_term_ids = [];

    foreach ($category_term_ids as $term_id) {
      $children = get_term_children($term_id, 'product_cat');
      if (!is_array($children)) {
        $children = array();
      }

      $category_family_term_ids[$term_id] = array_merge($children, array($term_id));
      $category_all_term_ids = array_merge($category_family_term_ids[$term_id], $category_all_term_ids);
    }

    // -- dictionary to convert taxonomy term ids to term ids
    $category_all_term_conversions = [];
    $query = "
      SELECT term_id, term_taxonomy_id
      FROM {$wpdb->term_taxonomy}
      WHERE term_id IN (" . implode(',', $category_all_term_ids) . ")
    ";
    foreach ($wpdb->get_results($query) as $term) {
      $category_all_term_conversions[$term->term_id] = $term->term_taxonomy_id;
    }

    // -- get and update product count for each category family group
    foreach ($category_family_term_ids as $term_id => $term_ids) {

      // Convert term IDs to taxonomy term IDs
      $taxonomy_term_ids = array_map(function ($id) use ($category_all_term_conversions) {
        return $category_all_term_conversions[$id];
      }, $term_ids);

      if (!empty($results_without_taxonomy['product_cat'])) {
        // Get product counts for these terms
        $query = "SELECT COUNT(DISTINCT object_id) as count
        FROM $wpdb->term_relationships
        WHERE term_taxonomy_id IN (" . implode(', ', $taxonomy_term_ids) . ")
        AND object_id IN (" . implode(', ', $results_without_taxonomy['product_cat']) . ")";

        $product_count = $wpdb->get_var($query);
      } else {
        $product_count = 0;
      }

      if (empty($product_count)) {
        continue;
      }

      // Get the term_taxonomy_id for this term
      $term_taxonomy_id = $category_all_term_conversions[$term_id];

      // Update the count for matching options
      foreach ($options as &$option) {
        if ($option['value'] == $term_taxonomy_id) {
          $option['count'] = $product_count;
        }
      }
    }

  }

  return $options;
}

function wcpt__dynamic_filter__get_term_object_counts($term_ids, $post_ids)
{
  global $wpdb;

  // Get the max count from the table data
  $table_data = wcpt_get_table_data();
  $sc_attrs = $table_data['query']['sc_attrs'];
  $max_count = !empty($sc_attrs['dynamic_recount_max']) && is_numeric($sc_attrs['dynamic_recount_max'])
    ? $sc_attrs['dynamic_recount_max']
    : 200;

  // Get counts for all terms, including those with no relationships
  $query = "
    SELECT tr.term_taxonomy_id,
           COUNT(DISTINCT tr.object_id) as count
    FROM {$wpdb->term_taxonomy} tt
    LEFT JOIN {$wpdb->term_relationships} tr ON tr.term_taxonomy_id = tt.term_taxonomy_id
    LEFT JOIN {$wpdb->posts} p ON p.ID = tr.object_id AND p.post_status = 'publish'
    WHERE tt.term_taxonomy_id IN (" . implode(', ', array_map('intval', $term_ids)) . ")
      AND (tr.object_id IS NULL OR tr.object_id IN (" . implode(', ', array_map('intval', $post_ids)) . "))
    GROUP BY tt.term_taxonomy_id
  ";

  $results = $wpdb->get_results($query);

  $term_counts = array();
  foreach ($results as $result) {
    $count = (int) $result->count;
    $term_counts[$result->term_taxonomy_id] = $count > $max_count ? $max_count . '+' : $count;
  }

  // Handle terms that weren't in the results (they have no relationships)
  foreach ($term_ids as $term_id) {
    if (!isset($term_counts[$term_id])) {
      $term_counts[$term_id] = 0;
    }
  }

  return $term_counts;
}

function wcpt__dynamic_filter__update_results_without_taxonomy($results_without_taxonomy, $taxonomy, $query_args, $post_ids, $filtered_taxonomies)
{
  // Return cached results if available
  if (!empty($results_without_taxonomy[$taxonomy])) {
    return $results_without_taxonomy;
  }

  $taxonomy_operator = wcpt__dynamic_filter__get_taxonomy_operator($taxonomy, $query_args);

  // No attribute value selected by user
  if (
    !in_array($taxonomy, $filtered_taxonomies) ||
    $taxonomy_operator === 'AND'
  ) {
    $results_without_taxonomy[$taxonomy] = $post_ids;
    return $results_without_taxonomy;
  }

  // Re-build tax_query excluding current taxonomy
  $_query_args = $query_args;
  $_tax_query = array('relation' => 'AND');

  // Preserve existing tax query items except for current taxonomy
  foreach ($_query_args['tax_query'] as $key => $val) {
    if ($key !== 'relation' && $val['taxonomy'] !== $taxonomy) {
      $_tax_query[] = $val;
    }
  }

  $_query_args['tax_query'] = $_tax_query;

  // Get filtered results without current taxonomy
  $query = new WP_Query($_query_args);
  $results_without_taxonomy[$taxonomy] = $query->posts;

  if (!$query->posts || !count($query->posts)) {
    $results_without_taxonomy[$taxonomy] = [0];
  }

  return $results_without_taxonomy;
}

function wcpt__dynamic_filter__get_taxonomy_operator($taxonomy, $query_args)
{
  foreach ($query_args['tax_query'] as $key => $val) {
    if (
      !empty($val['taxonomy']) &&
      $val['taxonomy'] === $taxonomy
    ) {
      return !empty($val['operator']) ? $val['operator'] : 'IN';
    }
  }

  return 'IN';
}
