<?php
namespace DiviBooster\DiviBooster\Modules\NumberCounter\StartWhenVisible;

const DIVI4_MODULE_SLUG = 'et_pb_number_counter';
const DIVI5_MODULE_SLUG = 'divi/number-counter';
const DIVI4_SETTING_SLUG = 'divibooster_start_when_visible';
const DIVI5_SETTING_SLUG = 'diviboosterStartWhenVisible';
const FRONTEND_JS_HANDLE = 'divibooster_number_counter_start_when_visible';

// === 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'           => 'Counter Start Mode',
    'type'            => 'select',
    'options'         => [
      'default'       => esc_html__('Default', 'et_builder'),
      'when_visible'  => esc_html__('When Visible', 'et_builder'),
    ],
    'option_category' => 'configuration',
    'description'     => 'Choose when the counter animation begins. Default = Divi\'s own behavior. "When Visible" waits until the counter scrolls into view.',
    'default'         => 'default',
    'toggle_slug'     => 'main_content',
    'tab_slug'        => 'general',
    'vb_support'      => true,
  ];
  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() {
  // Hidden attribute kept only for Divi 4 -> 5 conversion so that modules with the setting
  // are recognized as upgradable. Divi 5 already delays number counter until visible by default.
  return [
    'type'     => 'object',
    'settings' => [
      'innerContent' => [
        'groupType' => 'group-items',
        'items'     => [
          'diviboosterStartWhenVisibleToggle' => [
            'groupSlug'   => 'contentText',
            'attrName'    => DIVI5_SETTING_SLUG,
            'label'       => 'Counter Start Mode (Legacy)',
            'description' => 'Legacy setting migrated from Divi 4. Hidden because Divi 5 starts when visible by default.',
            'features'    => [
              'hover'      => false,
              'sticky'     => false,
              'responsive' => false,
              'preset'     => 'content',
            ],
            'render'   => false, // hide in Divi 5 UI
            'priority' => 120,
            'component' => [
              'type' => 'field',
              'name' => 'divi/select',
            ],
            'defaultAttr' => [ 'desktop' => [ 'value' => 'default' ] ],
          ],
        ],
      ],
    ],
  ];
}

// === Divi 5: Attribute Mapping & D4→D5 Conversion (Inline JS) ===
add_action('wp_enqueue_scripts', __NAMESPACE__ . '\\enqueue_divi5_mapping_js');
function enqueue_divi5_mapping_js() {
    if ((!function_exists('et_builder_d5_enabled') || !\et_builder_d5_enabled()) ||
        (!function_exists('et_core_is_fb_enabled') || !\et_core_is_fb_enabled())) {
        return;
    }
    $handle = sanitize_title('divi-booster-' . DIVI5_MODULE_SLUG . '-' . DIVI5_SETTING_SLUG . '-mapping');
    wp_register_script($handle, '', ['lodash', 'divi-vendor-wp-hooks'], null, true);
    wp_enqueue_script($handle);
    wp_add_inline_script($handle, get_divi5_mapping_inline_js());
}

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;
});

// Map Divi 4 field to Divi 5 attribute for conversion
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_start_when_visible_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'] = [];
    }
    $conversion_outline['module'][DIVI4_SETTING_SLUG] = DIVI5_SETTING_SLUG . '.*';
    return $conversion_outline;
}
add_filter('divi.moduleLibrary.conversion.moduleConversionOutline', __NAMESPACE__ . '\\dbdb_register_divi5_start_when_visible_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 getChart(wrapper){
    var $ = window.jQuery;
    if (!$) return null;
    var $percent = $(wrapper).find('.percent');
    return $percent.data('easyPieChart') || $(wrapper).data('easyPieChart') || null;
  }

  function getValues(wrapper){
    var valueEl = wrapper.querySelector('.percent .percent-value');
    var sep = wrapper.getAttribute('data-number-separator') || '';
    var startAttr = valueEl ? valueEl.getAttribute('data-divibooster-initial-value') : null;
    var start = (startAttr !== null) ? parseNumber(startAttr) : 0;
    var targetAttr = valueEl ? valueEl.getAttribute('data-divibooster-target-value') : null;
    var targetSrc = wrapper.getAttribute('data-number-value') || targetAttr || (valueEl ? valueEl.textContent : '0');
    var target = parseNumber(targetSrc);
    return { valueEl: valueEl, sep: sep, start: start, target: target };
  }

  function showStart(wrapper, vals){
    var dec = Math.max(decimalsOf(vals.start), decimalsOf(vals.target));
    if (vals.valueEl){
      var s = formatNumber(vals.start, dec);
      vals.valueEl.textContent = withThousands(s, vals.sep);
    }
  }

  function primeChart(wrapper, cb){
    var attempts = 0;
    var maxAttempts = 120; // ~6s
    function tryPrime(){
      attempts++;
      var chart = getChart(wrapper);
      var vals = getValues(wrapper);
      if (chart && typeof chart.update === 'function'){
        // Ensure animation is disabled while waiting
        if (typeof chart.disableAnimation === 'function') chart.disableAnimation();
        // Normalize options.animate if needed (compat with duration feature)
        chart.options = chart.options || {};
        // Set chart value to start
        chart.update(vals.start);
        // If not yet visible, override update() to suppress premature auto animation
        if (!isMidVisible(wrapper) && !chart.__dbiv_original_update){
          (function(){
            var originalUpdate = chart.update;
            if (typeof originalUpdate === 'function'){
              chart.__dbiv_original_update = originalUpdate;
              chart.update = function(v){
                // While waiting and still not visible, capture intended target and keep at start value
                if (!wrapper.__dbiv_ncvis_started && !isMidVisible(wrapper)){
                  wrapper.__dbiv_ncvis_pendingTarget = v;
                  return originalUpdate.call(chart, getValues(wrapper).start);
                }
                return originalUpdate.call(chart, v);
              };
            }
          })();
        }
        showStart(wrapper, vals);
        wrapper.classList.add('dbiv-ncvis-waiting');
        // While waiting off-screen, periodically re-assert the start value in case Divi / easyPieChart rewrites it
        if (!isMidVisible(wrapper) && !wrapper.__dbiv_ncvis_enforceStart){
          wrapper.__dbiv_ncvis_enforceStart = setInterval(function(){
            if (wrapper.__dbiv_ncvis_started || isMidVisible(wrapper)) return; // keep until visible or started
            var v = getValues(wrapper); // refresh (in case attributes change)
            showStart(wrapper, v);
          }, 120);
        }
        if (typeof cb === 'function') cb({chart: chart, vals: vals});
        return true;
      }
      // If chart not ready yet, at least show starting label
      showStart(wrapper, vals);
      if (attempts < maxAttempts){ setTimeout(tryPrime, 50); }
      return false;
    }
    tryPrime();
  }

  function isMidVisible(el){
    var r = el.getBoundingClientRect();
    var midY = r.top + r.height / 2;
    var vh = window.innerHeight || document.documentElement.clientHeight;
    return midY >= 0 && midY <= vh;
  }

  function startAnimation(wrapper){
    if (!wrapper || wrapper.__dbiv_ncvis_started) return;
    wrapper.__dbiv_ncvis_started = true;
    var chart = getChart(wrapper);
    var vals = getValues(wrapper);
    var doStart = function(){
      var ch = chart || getChart(wrapper);
      if (!ch || typeof ch.update !== 'function') return;
      if (typeof ch.enableAnimation === 'function') ch.enableAnimation();
      // Restore original update if we overrode it while waiting
      if (ch.__dbiv_original_update){
        ch.update = ch.__dbiv_original_update;
        delete ch.__dbiv_original_update;
      }
      // Let duration feature decide animate timing via chart.options.animate
      wrapper.classList.remove('dbiv-ncvis-waiting');
      if (wrapper.__dbiv_ncvis_enforceStart){
        clearInterval(wrapper.__dbiv_ncvis_enforceStart);
        delete wrapper.__dbiv_ncvis_enforceStart;
      }
      var targetVal = wrapper.__dbiv_ncvis_pendingTarget || vals.target;
      if (wrapper.__dbiv_ncvis_pendingTarget) delete wrapper.__dbiv_ncvis_pendingTarget;
      ch.update(targetVal);
      // Safety: fix NaN after small delay if occurs
      setTimeout(function(){
        var el = wrapper.querySelector('.percent .percent-value');
        if (el && (el.textContent === 'NaN' || isNaN(parseFloat(el.textContent)))){
          el.textContent = String(vals.target);
        }
      }, 150);
    };

    if (!chart){
      // If chart not ready yet, wait briefly
      var tries = 0; var max = 120;
      (function waitForChart(){
        tries++;
        chart = getChart(wrapper);
        if (chart){ doStart(); return; }
        if (tries < max) setTimeout(waitForChart, 50);
      })();
    } else {
      doStart();
    }
  }

  function setupObserver(wrapper){
    if (wrapper.__dbiv_ncvis_observed) return;
    wrapper.__dbiv_ncvis_observed = true;

    var observed = false;
    if ('IntersectionObserver' in window){
      var io = new IntersectionObserver(function(entries){
        entries.forEach(function(entry){
          if (entry.target !== wrapper) return;
          if (entry.isIntersecting && entry.intersectionRatio >= 0.5){
            if (!observed){ observed = true; io.unobserve(wrapper); }
            startAnimation(wrapper);
          }
        });
      }, { threshold: [0, 0.25, 0.5, 0.75, 1] });
      io.observe(wrapper);
      // If already visible on load
      if (isMidVisible(wrapper)){
        startAnimation(wrapper);
        try { io.unobserve(wrapper); } catch(e){}
      }
    } else {
      // Fallback: scroll/resize check for midpoint visibility
      var onScroll = function(){
        if (!wrapper.__dbiv_ncvis_started && isMidVisible(wrapper)){
          window.removeEventListener('scroll', onScroll, true);
          window.removeEventListener('resize', onScroll, true);
          startAnimation(wrapper);
        }
      };
      window.addEventListener('scroll', onScroll, true);
      window.addEventListener('resize', onScroll, true);
      // Initial check
      onScroll();
    }
  }

  function initInstance(wrapper){
    if (!wrapper || wrapper.__dbiv_ncvis_init) return;
    wrapper.__dbiv_ncvis_init = true;

    // Prime chart to start value and prevent auto animation
    primeChart(wrapper);
    // Set up visibility observer
    setupObserver(wrapper);
  }

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

  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-ncvis')){
                initAll(node);
              }
              var inner = node.querySelectorAll ? node.querySelectorAll('.dbiv-ncvis') : [];
              inner.forEach(initInstance);
            }
          });
        }
      });
    });
    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);
}

// === Utilities ===
function sanitize_toggle($value) {
  // Backwards compatibility: treat old 'on' / yes/no values and new 'when_visible'
  $v = is_string($value) ? strtolower(trim($value)) : (string) $value;
  if (in_array($v, ['on','1','yes','true','when_visible'], true)) return 'on';
  return 'off'; // includes 'default', 'off', '', null
}

function extract_order_class($html) {
    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
    );
}

// === Shared: Process Output ===
function process_number_counter_visibility_output($content, $enabled_raw) {
  if (!is_string($content)) return $content;
// If Divi 5 is active, skip – behavior already native.
if (
    (function_exists('et_builder_d5_enabled') && \et_builder_d5_enabled()) ||
    !defined('ET_CORE_VERSION') ||
    (preg_match('/^(\d+)/', ET_CORE_VERSION, $m) && intval($m[1]) >= 5)
) {
    return $content;
}
  $enabled = sanitize_toggle($enabled_raw);
  if ($enabled !== 'on') return $content;

    // Add indicator attribute and feature class to wrapper (first instance only)
    $content = preg_replace_callback(
        '#(<[^>]*class=\"[^\"]*\bet_pb_number_counter\b[^\"]*\"[^>]*)(>)#',
        function($m){
            $attr = ' data-divibooster-start-when-visible="on"';
            if (strpos($m[0], 'data-divibooster-start-when-visible') !== false) {
                return $m[0];
            }
            return $m[1] . $attr . $m[2];
        },
        $content,
        1
    );

    // Add feature class
    $content = add_wrapper_class($content, 'dbiv-ncvis');

    // Add minimal, per-instance CSS to avoid flicker before primed
    $order_class = extract_order_class($content);
  if ($order_class) {
    $style  = "\n<style>\n";
    // Always show the percent value (no hiding while waiting) to avoid unit-only partial display
    $style .= ".$order_class.dbiv-ncvis .percent-value{opacity:1;}\n";
    $style .= "</style>\n";
    $content .= $style;
  }

  // Ensure feature JS is enqueued only if the feature is present in the output and 'when_visible' is selected
  if (
    strpos($content, 'dbiv-ncvis') !== false &&
    $enabled_raw === 'when_visible' &&
    !wp_script_is(FRONTEND_JS_HANDLE, 'enqueued')
  ) {
    register_frontend_js();
    wp_enqueue_script(FRONTEND_JS_HANDLE);
  }

    return $content;
}

// === 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;
  }
  $raw = isset($module->props[DIVI4_SETTING_SLUG]) ? $module->props[DIVI4_SETTING_SLUG] : 'default';
  return process_number_counter_visibility_output($output, $raw);
}, 10, 3);


// Created at 1757669498.
