<?php // https://divibooster.com/set-a-custom-start-value-in-divi-number-counter-modules/
namespace DiviBooster\DiviBooster\Modules\NumberCounter\InitialValue;

const DIVI4_MODULE_SLUG = 'et_pb_number_counter';
const DIVI5_MODULE_SLUG = 'divi/number-counter';
const DIVI4_SETTING_SLUG = 'db_initial_value';
const DIVI5_SETTING_SLUG = 'diviboosterInitialValue';
const FRONTEND_JS_HANDLE = 'divibooster_number_counter_initial_value';

// === Divi 4: Field Registration ===
add_filter('et_pb_all_fields_unprocessed_' . DIVI4_MODULE_SLUG, __NAMESPACE__ . '\add_divi4_field');
function add_divi4_field($fields) {
    if (!is_array($fields)) return $fields;
    $fields[DIVI4_SETTING_SLUG] = [
        'label'           => 'Initial Value',
        'type'            => 'text',
        'option_category' => 'basic_option',
        'description'     => 'Set the initial value the counter starts from.',
        'default'         => '0',
        'toggle_slug'     => 'main_content',
        'tab_slug'        => 'general'
    ];
    return $fields;
}

// === Divi 5: Attribute Registration (PHP fallback) ===
add_filter('divi_module_library_register_module_attrs', function($module_attrs, $filter_args) {
    if (($filter_args['name'] ?? '') !== DIVI5_MODULE_SLUG) {
        return $module_attrs;
    }
    $module_attrs[DIVI5_SETTING_SLUG] = get_divi5_attribute_definition();
    return $module_attrs;
}, 10, 2);

function get_divi5_attribute_definition() {
    return [
        'type'     => 'object',
        'settings' => [
            'innerContent' => [
                'groupType' => 'group-items',
                'items'     => [
                    'diviboosterInitialValueField' => [
                        'groupSlug'   => 'contentText',
                        'attrName'    => DIVI5_SETTING_SLUG,
                        'label'       => 'Initial Value',
                        'description' => 'Set the initial value the counter starts from.',
                        'features'    => [
                            'hover'      => false,
                            'sticky'     => false,
                            'responsive' => false,
                            'preset'     => 'content',
                        ],
                        'render'      => true,
                        'priority'    => 20,
                        'component'   => [
                            'type' => 'field',
                            'name' => 'divi/text',
                        ],
                        'defaultAttr' => [ 'desktop' => [ 'value' => '0' ] ],
                    ],
                ],
            ],
        ],
    ];
}

// === Divi 5: Attribute Mapping & D4â†’D5 Conversion (Inline JS) ===
add_action('wp_enqueue_scripts', __NAMESPACE__ . '\enqueue_divi5_mapping_js');
function enqueue_divi5_mapping_js() {
  $d5_enabled = function_exists('et_builder_d5_enabled') ? (bool) call_user_func('et_builder_d5_enabled') : false;
  $fb_enabled = function_exists('et_core_is_fb_enabled') ? (bool) call_user_func('et_core_is_fb_enabled') : false;
  if (!$d5_enabled || !$fb_enabled) {
        return;
    }
    $handle = sanitize_title('divi-booster-' . DIVI5_MODULE_SLUG . '-' . DIVI5_SETTING_SLUG);
    \dbdb_enqueue_inline_script_if_dependencies_available($handle, get_divi5_mapping_inline_js(), array('lodash', array('divi-vendor-wp-hooks', 'wp-hooks')));
}

function get_divi5_mapping_inline_js() {
    $attribute_json = wp_json_encode(get_divi5_attribute_definition());
    $divi4_slug = wp_json_encode(DIVI4_MODULE_SLUG);
    $divi5_setting_slug = wp_json_encode(DIVI5_SETTING_SLUG);
    $divi4_setting_slug = wp_json_encode(DIVI4_SETTING_SLUG);
    return <<<END
// Adds the custom attribute to the Number Counter module.
window.vendor.wp.hooks.addFilter('divi.moduleLibrary.moduleAttributes.divi.number-counter', 'divi', (attributes, metadata) => {
    attributes[{$divi5_setting_slug}] = {$attribute_json};
    return attributes;
});
window.vendor.wp.hooks.addFilter('divi.moduleLibrary.conversion.moduleConversionOutline', 'divi', (conversionOutline, name) => {
    if (name !== {$divi4_slug}) return conversionOutline;
    if (!conversionOutline.module) conversionOutline.module = {};
    conversionOutline.module[{$divi4_setting_slug}] = {$divi5_setting_slug} + '.*';
    return conversionOutline;
});
END;
}

// === Divi 5: PHP conversion outline registration ===
function dbdb_register_divi5_initial_value_conversion( $conversion_outline, $module_name ) {
    if ( DIVI5_MODULE_SLUG !== $module_name ) {
        return $conversion_outline;
    }
    if ( ! isset( $conversion_outline['module'] ) || ! is_array( $conversion_outline['module'] ) ) {
        $conversion_outline['module'] = array();
    }
    $conversion_outline['module'][ DIVI4_SETTING_SLUG ] = DIVI5_SETTING_SLUG . '.*';
    return $conversion_outline;
}
add_filter( 'divi.moduleLibrary.conversion.moduleConversionOutline', __NAMESPACE__ . '\\dbdb_register_divi5_initial_value_conversion', 10, 2 );

// === Register Frontend Feature JS (once) ===
add_action('wp_enqueue_scripts', __NAMESPACE__ . '\\register_frontend_js');
function register_frontend_js() {
    $js = <<<'END'
(function(){
  function parseNumber(str){
    if (typeof str !== 'string') str = String(str || '');
    str = str.replace(/[^0-9+\-\.]/g, '');
    var n = parseFloat(str);
    return isNaN(n) ? 0 : n;
  }
  function decimalsOf(n){
    n = (typeof n === 'number') ? n : parseNumber(n);
    var s = String(n);
    var p = s.split('.');
    return p.length > 1 ? p[1].length : 0;
  }
  function formatNumber(n, dec){
    if (typeof dec !== 'number') dec = 0;
    var s = n.toFixed(dec);
    if (parseFloat(s) === 0) return (dec>0? '0.'+Array(dec+1).join('0') : '0');
    return s;
  }
  function withThousands(s, sep){
    if (!sep) return s;
    var parts = s.split('.');
    parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, sep);
    return parts.join('.');
  }

  function primeChart(wrapper, start){
    var attempts = 0;
    var maxAttempts = 160; // ~8s for Divi 5 (increased for late chart init)
    var sep = wrapper.getAttribute('data-number-separator') || '';
    var valueEl = wrapper.querySelector('.percent .percent-value');
    // Read the target value from the data attribute if present
    var targetAttr = valueEl ? valueEl.getAttribute('data-divibooster-target-value') : null;
    var targetValue = targetAttr !== null ? parseNumber(targetAttr) : parseNumber(valueEl ? valueEl.textContent : '0');
    var dec = Math.max(decimalsOf(start), decimalsOf(targetValue));
    function tryPrime(){
      attempts++;
      var $ = window.jQuery;
      if ($){
        var $w = $(wrapper);
        var chart = $w.data('easyPieChart');
        if (chart && typeof chart.update === 'function'){
          // Prime the chart to the initial value WITHOUT animation.
          var prevAnimate = (chart.options && typeof chart.options === 'object') ? chart.options.animate : undefined;
          try { if (chart.options) chart.options.animate = false; } catch(e) {}
          try { chart.update(start); } catch(e) {}
          try { if (chart.options) chart.options.animate = prevAnimate; } catch(e) {}

          // Reveal the text once primed and mark as primed
          wrapper.classList.add('dbiv-nciv-ready');
          if (valueEl) valueEl.style.opacity = '1';
          try { wrapper.setAttribute('data-dbiv-primed', '1'); } catch(e) {}
          try { wrapper.__dbiv_canvasPrimed = wrapper.querySelector('canvas'); } catch(e) {}

          // If we have a valid target and it's different to the start, animate to target immediately after a frame.
          if (!isNaN(targetValue) && targetValue !== start) {
            var animateToTarget = function(){
              try { chart.update(targetValue); } catch(e) {}
            };
            if (window.requestAnimationFrame) {
              requestAnimationFrame(function(){ setTimeout(animateToTarget, 0); });
            } else {
              setTimeout(animateToTarget, 0);
            }
          }
          return true;
        }
      }
      if (attempts < maxAttempts){
        setTimeout(tryPrime, 50);
      } else {
        // Give up priming; reveal to avoid leaving content hidden. Divi may still handle animation.
        wrapper.classList.add('dbiv-nciv-ready');
        if (valueEl) valueEl.style.opacity = '1';
      }
      return false;
    }
    // set displayed start immediately
    if (valueEl){
      var s = formatNumber(start, dec);
      valueEl.textContent = withThousands(s, sep);
    }
    tryPrime();
  }

  function initInstance(wrapper){
    if (!wrapper) return;
    var valueEl = wrapper.querySelector('.percent .percent-value');
    if (!valueEl) return;
    var canvasEl = wrapper.querySelector('canvas');
    if (wrapper.__dbiv_canvasPrimed && wrapper.__dbiv_canvasPrimed === canvasEl) return;
    var start = parseNumber(valueEl.getAttribute('data-divibooster-initial-value'));
    if (isNaN(start)) start = 0;
    // If the element is already showing the target value (e.g., JS ran late or Divi already animated), don't re-prime to avoid flicker.
    var targetAttr = valueEl.getAttribute('data-divibooster-target-value');
    var currentVal = parseNumber(valueEl.textContent);
    if (targetAttr !== null) {
      var targetVal = parseNumber(targetAttr);
      if (!isNaN(targetVal) && currentVal === targetVal && wrapper.getAttribute('data-dbiv-primed') === '1') {
        wrapper.classList.add('dbiv-nciv-ready');
        valueEl.style.opacity = '1';
        return;
      }
    }
    // Hide during init only when feature is active for this instance.
    valueEl.style.opacity = '0';
    primeChart(wrapper, start);
    wrapper.__dbiv_init = true;
  }

  function initAll(root){
    root = root || document;
    var nodes = root.querySelectorAll('.dbiv-nciv');
    nodes.forEach(function(n){ initInstance(n); });
  }

  if (document.readyState === 'loading'){
    document.addEventListener('DOMContentLoaded', function(){ initAll(document); });
  } else {
    initAll(document);
  }

  if (window.MutationObserver){
    var mo = new MutationObserver(function(muts){
      muts.forEach(function(m){
        if (m.type === 'childList'){
          m.addedNodes && m.addedNodes.forEach(function(node){
            if (node.nodeType === 1){
              if (node.classList && node.classList.contains('dbiv-nciv')){
                initInstance(node);
              }
              var inner = node.querySelectorAll ? node.querySelectorAll('.dbiv-nciv') : [];
              inner.forEach(function(n){ initInstance(n); });
            }
          });
        }
      });
    });
    mo.observe(document.documentElement, {childList: true, subtree: true});
  }
})();
END;
    // Load early (in head) so we can prime easyPieChart before Divi triggers animations.
    wp_register_script(FRONTEND_JS_HANDLE, '', [], null, false);
    wp_add_inline_script(FRONTEND_JS_HANDLE, $js);
    // Ensure it is actually enqueued in the head on every page load
    wp_enqueue_script(FRONTEND_JS_HANDLE);
}

// === Frontend CSS Output (conditional, non-blocking) ===
// Print a small CSS only if feature instances exist, and avoid default hiding.
add_action('wp_footer', __NAMESPACE__ . '\\output_css');
function output_css() {
  static $done = false;
  if ($done) return; $done = true;
  $instances = get_collected_instances();
  if (empty($instances)) return;
  // Provide a smooth transition when JS reveals the value, and hide the ring until primed.
  echo '<style>.dbiv-nciv .percent-value{transition:opacity .2s ease;} .dbiv-nciv.dbiv-nciv-ready .percent-value{opacity:1;} .dbiv-nciv:not(.dbiv-nciv-ready) canvas{visibility:hidden}</style>';
}

function &get_collected_instances() {
    static $instances = [];
    return $instances;
}

function collect_instance($order_class) {
  $instances = &get_collected_instances();
  if (!in_array($order_class, $instances, true)) {
    $instances[] = $order_class;
  }
}

// === Shared: Process Output ===
function process_number_counter_output($content, $initial_value_raw, $target_value_raw = null) {
  $start = sanitize_initial_value($initial_value_raw);
  $target = isset($target_value_raw) ? sanitize_initial_value($target_value_raw) : null;

  // If initial value is not set or is effectively zero, do nothing and let Divi handle defaults.
  // Treat empty strings, null, non-numeric and numeric zero as "not set".
  if ($start == 0.0) { // loose compare is fine here for 0, 0.0, '0', '0.0'
    return $content;
  }

  // Inject data attributes into the first .percent-value element and hide it until primed
  $content = preg_replace_callback(
    '#(<[^>]*class="[^"]*\bpercent-value\b[^"]*"[^>]*)(>)#',
    function($m) use ($start, $target) {
      // Avoid duplicating attribute if already present
      if (strpos($m[0], 'data-divibooster-initial-value') !== false) {
        return $m[0];
      }
      $open = $m[1];
      $attr = ' data-divibooster-initial-value="' . esc_attr((string)$start) . '"';
      if (isset($target)) {
        $attr .= ' data-divibooster-target-value="' . esc_attr((string)$target) . '"';
      }
      // Ensure the number is hidden initially to avoid 0/target flash before JS primes
      if (strpos($open, 'style=') !== false) {
        $open = preg_replace('#style=\"([^\"]*)\"#', 'style="$1 opacity:0 !important;"', $open, 1);
      } else {
        $attr .= ' style="opacity:0 !important;"';
      }
      return $open . $attr . $m[2];
    },
    $content,
    1
  );

  // Add feature class to wrapper and collect order class for CSS scoping
  $order_class = extract_order_class($content);
  if ($order_class) {
    $content = add_wrapper_class($content, 'dbiv-nciv');
    collect_instance($order_class);
  }

  return $content;
}

function sanitize_initial_value($value) {
    if (is_numeric($value)) {
        return (float)$value;
    }
    $value = is_string($value) ? $value : '';
    $value = str_replace([",", ' '], '', $value);
    if (is_numeric($value)) {
        return (float)$value;
    }
    return 0.0;
}

function extract_order_class($html) {
    // Match order class like et_pb_number_counter_1234 or with suffixes (e.g., _tb_header)
    if (preg_match('#class=\"[^\"]*(et_pb_number_counter_\d+(?:_[A-Za-z0-9_-]+)?)\b[^\"]*\"#', $html, $m)) {
        return $m[1];
    }
    return '';
}

function add_wrapper_class($html, $class_to_add) {
  return preg_replace(
    '#(<[^>]+class=")([^\"]*\bet_pb_number_counter\b[^\"]*)(")#',
    '$1$2 ' . $class_to_add . '$3',
    $html,
    1
  );
}

// === Divi 4: Output Filter ===
add_filter('et_module_shortcode_output', function($output, $render_slug, $module) {
    if (!is_string($output) || $render_slug !== DIVI4_MODULE_SLUG || !isset($module->props) || !is_array($module->props)) {
        return $output;
    }
    $initial = isset($module->props[DIVI4_SETTING_SLUG]) ? $module->props[DIVI4_SETTING_SLUG] : '0';
    $target = isset($module->props['number']) ? $module->props['number'] : null;
    return process_number_counter_output($output, $initial, $target);
}, 10, 3);

// === Divi 5: Output Filter ===
add_filter('render_block_' . DIVI5_MODULE_SLUG, function($block_content, $parsed_block, $block) {
    $attrs = isset($parsed_block['attrs']) && is_array($parsed_block['attrs']) ? $parsed_block['attrs'] : [];
    $initial = '0';
    if (isset($attrs[DIVI5_SETTING_SLUG])) {
        if (isset($attrs[DIVI5_SETTING_SLUG]['desktop']['value'])) {
            $initial = $attrs[DIVI5_SETTING_SLUG]['desktop']['value'];
        } elseif (isset($attrs[DIVI5_SETTING_SLUG]['value'])) {
            $initial = $attrs[DIVI5_SETTING_SLUG]['value'];
        }
    }
    $target = '0';
    if (isset($attrs['number']['innerContent']['desktop']['value'])) {
        $target = $attrs['number']['innerContent']['desktop']['value'];
    }
    return process_number_counter_output($block_content, $initial, $target);
}, 10, 3);

// Created at 1755585936.

