<?php

namespace DiviBooster\DiviBooster\Modules\Blog\PaginationLinkText;

const DIVI4_MODULE_SLUG = 'et_pb_blog';
const DIVI5_MODULE_SLUG = 'divi/blog';
const DIVI4_SHOW_PAGINATION_SETTING_SLUG = 'show_pagination';
const DIVI4_OLDER_SETTING_SLUG = 'dbdb_older_entries_text';
const DIVI4_NEWER_SETTING_SLUG = 'dbdb_newer_entries_text';
const DIVI5_OLDER_SETTING_SLUG = 'diviboosterOlderEntriesText';
const DIVI5_NEWER_SETTING_SLUG = 'diviboosterNewerEntriesText';

// === Hook registration ===
if (function_exists('add_filter')) {
    add_filter('et_pb_all_fields_unprocessed_' . DIVI4_MODULE_SLUG, __NAMESPACE__ . '\\d4_register_fields');
    add_filter(DIVI4_MODULE_SLUG . '_shortcode_output', __NAMESPACE__ . '\\d4_filter_output', 10, 3);
    add_filter('block_type_metadata_settings', __NAMESPACE__ . '\\d5_register_metadata_attrs', 10, 2);
    add_filter('divi.moduleLibrary.conversion.moduleConversionOutline', __NAMESPACE__ . '\\d5_conversion_outline', 10, 2);
    add_filter('render_block_' . DIVI5_MODULE_SLUG, __NAMESPACE__ . '\\d5_filter_output', 10, 3);
}

if (function_exists('add_action')) {
    add_action('wp_enqueue_scripts', __NAMESPACE__ . '\\enqueue_builder_mapping_script');
}

// === Shared helpers ===

// Translate UI labels while keeping a safe fallback for non-WordPress contexts.
function translatable_text($text) {
    if (!is_scalar($text)) {
        return '';
    }

    $text = (string) $text;

    if (!function_exists('__')) {
        return $text;
    }

    return (string) __($text, 'divi-booster');
}

// Normalize custom label values to trimmed plain strings.
function sanitize_custom_link_text($value) {
    return is_scalar($value) ? trim((string) $value) : '';
}

function translate_custom_link_text($value) {
    $value = sanitize_custom_link_text($value);

    return $value !== '' ? translatable_text($value) : '';
}

function translatable_escaped_text($text) {
    return escape_html_text(translatable_text($text));
}

function encode_js_value($value) {
    if (!function_exists('wp_json_encode')) {
        return 'null';
    }

    $encoded = wp_json_encode($value, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);

    return is_string($encoded) && $encoded !== '' ? $encoded : 'null';
}

// Read a text value from the attribute shapes used in saved content and VB state.
function get_divi5_text_value($attrs, $setting_slug) {
    if (!is_array($attrs) || !array_key_exists($setting_slug, $attrs)) {
        return '';
    }

    $value = $attrs[$setting_slug];

    if (is_array($value)) {
        if (isset($value['desktop']['value']) && is_scalar($value['desktop']['value'])) {
            return translate_custom_link_text($value['desktop']['value']);
        }

        if (isset($value['innerContent']['desktop']['value']) && is_scalar($value['innerContent']['desktop']['value'])) {
            return translate_custom_link_text($value['innerContent']['desktop']['value']);
        }

        if (isset($value['value']) && is_scalar($value['value'])) {
            return translate_custom_link_text($value['value']);
        }

        if (isset($value['innerContent']['value']) && is_scalar($value['innerContent']['value'])) {
            return translate_custom_link_text($value['innerContent']['value']);
        }
    }

    if (is_scalar($value)) {
        return translate_custom_link_text($value);
    }

    return '';
}

// Escape replacement text while tolerating lightweight test stubs.
function escape_html_text($text) {
    if (!is_scalar($text)) {
        return '';
    }

    $text = (string) $text;

    if (function_exists('esc_html')) {
        return esc_html($text);
    }

    return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}

// Replace one pagination link label inside Divi's rendered pagination markup.
function replace_pagination_anchor_text($html, $alignment_class, $replacement_text) {
    if (!is_string($html) || $html === '' || $replacement_text === '') {
        return $html;
    }

    $alignment_class = preg_quote($alignment_class, '/');
    $replacement_html = escape_html_text($replacement_text);
    $pattern = '/(<div[^>]*class="[^"]*\bpagination\b[^"]*"[^>]*>[\s\S]*?<div[^>]*class="[^"]*\b' . $alignment_class . '\b[^"]*"[^>]*>\s*<a\b[^>]*>)([\s\S]*?)(<\/a>)/i';

    $updated_html = preg_replace_callback(
        $pattern,
        function($matches) use ($replacement_html) {
            return $matches[1] . $replacement_html . $matches[3];
        },
        $html,
        1
    );

    return is_string($updated_html) ? $updated_html : $html;
}

// Apply custom older/newer labels only when pagination markup and custom text are present.
function replace_blog_pagination_link_texts($html, $older_entries_text, $newer_entries_text) {
    if (!is_string($html) || $html === '') {
        return $html;
    }

    $older_entries_text = sanitize_custom_link_text($older_entries_text);
    $newer_entries_text = sanitize_custom_link_text($newer_entries_text);

    if ($older_entries_text === '' && $newer_entries_text === '') {
        return $html;
    }

    if (strpos($html, 'pagination') === false || strpos($html, '<a') === false) {
        return $html;
    }

    if ($older_entries_text !== '') {
        $html = replace_pagination_anchor_text($html, 'alignleft', $older_entries_text);
    }

    if ($newer_entries_text !== '') {
        $html = replace_pagination_anchor_text($html, 'alignright', $newer_entries_text);
    }

    return $html;
}

// Skip PHP-side output rewriting in VB preview, where the preview is synced in JS.
function is_divi5_builder_preview_request() {
    if (!function_exists('et_builder_d5_enabled') || !function_exists('et_core_is_fb_enabled')) {
        return false;
    }

    return \et_builder_d5_enabled() && \et_core_is_fb_enabled();
}

// === Divi 4 integration ===

// Register custom pagination label fields on the Blog module.
function d4_register_fields($fields) {
    if (!is_array($fields)) {
        return $fields;
    }

    $fields[DIVI4_OLDER_SETTING_SLUG] = get_divi4_field_definition(
        translatable_escaped_text('Older Entries Text'),
        translatable_escaped_text('Enter custom text for the Older Entries pagination link, or leave blank to use Divi\'s default text.')
    );

    $fields[DIVI4_NEWER_SETTING_SLUG] = get_divi4_field_definition(
        translatable_escaped_text('Newer Entries Text'),
        translatable_escaped_text('Enter custom text for the Newer Entries pagination link, or leave blank to use Divi\'s default text.')
    );

    return $fields;
}

// Rewrite rendered Blog module pagination labels from the saved D4 field values.
function d4_filter_output($output, $render_slug, $module) {
    if (!is_string($output) || !is_object($module) || !isset($module->props) || !is_array($module->props)) {
        return $output;
    }

    $older_entries_text = translate_custom_link_text($module->props[DIVI4_OLDER_SETTING_SLUG] ?? '');
    $newer_entries_text = translate_custom_link_text($module->props[DIVI4_NEWER_SETTING_SLUG] ?? '');

    return replace_blog_pagination_link_texts($output, $older_entries_text, $newer_entries_text);
}

// === Divi 5 integration ===

// Build the shared Divi 5 attribute schema for both custom pagination text fields.
function get_attribute_definitions() {
    return [
        DIVI5_OLDER_SETTING_SLUG => get_divi5_attribute_definition(
            'diviboosterOlderEntriesTextField',
            DIVI5_OLDER_SETTING_SLUG,
            translatable_text('Older Entries Text'),
            translatable_text('Enter custom text for the Older Entries pagination link, or leave blank to use Divi\'s default text.'),
            85
        ),
        DIVI5_NEWER_SETTING_SLUG => get_divi5_attribute_definition(
            'diviboosterNewerEntriesTextField',
            DIVI5_NEWER_SETTING_SLUG,
            translatable_text('Newer Entries Text'),
            translatable_text('Enter custom text for the Newer Entries pagination link, or leave blank to use Divi\'s default text.'),
            86
        ),
    ];
}

// Build one Divi 4 text field definition with consistent visibility and defaults.
function get_divi4_field_definition($label, $description) {
    return array(
        'label' => $label,
        'type' => 'text',
        'option_category' => 'basic_option',
        'description' => $description,
        'show_if' => array(
            DIVI4_SHOW_PAGINATION_SETTING_SLUG => 'on',
        ),
        'toggle_slug' => 'elements',
        'tab_slug' => 'general',
        'default' => '',
        'dynamic_content' => 'text',
    );
}

// Build one Divi 5 text attribute definition with consistent UI metadata.
function get_divi5_attribute_definition($field_slug, $attr_name, $label, $description, $priority) {
    return [
        'type' => 'object',
        'settings' => [
            'innerContent' => [
                'groupType' => 'group-items',
                'items' => [
                    $field_slug => [
                        'groupSlug' => 'contentElements',
                        'attrName' => $attr_name,
                        'label' => $label,
                        'description' => $description,
                        'features' => [
                            'hover' => false,
                            'sticky' => false,
                            'responsive' => false,
                            'preset' => 'content',
                        ],
                        'render' => true,
                        'priority' => $priority,
                        'component' => [
                            'type' => 'field',
                            'name' => 'divi/text',
                        ],
                        'defaultAttr' => [ 'desktop' => [ 'value' => '' ] ],
                    ],
                ],
            ],
        ],
    ];
}

// Register the custom attributes on the Blog block metadata.
function d5_register_metadata_attrs($settings, $metadata = null) {
    if (!is_array($settings)) {
        return $settings;
    }

    $module_name = $settings['name'] ?? '';
    $module_attributes = $settings['attributes'] ?? false;

    if ($module_name !== DIVI5_MODULE_SLUG || !$module_attributes || !is_array($module_attributes)) {
        return $settings;
    }

    foreach (get_attribute_definitions() as $slug => $definition) {
        $settings['attributes'][$slug] = $definition;
    }

    return $settings;
}

// Map legacy D4 field values into the D5 attribute names during conversion.
function d5_conversion_outline($conversion_outline, $module_name) {
    if ($module_name !== DIVI5_MODULE_SLUG || !is_array($conversion_outline)) {
        return $conversion_outline;
    }

    if (!isset($conversion_outline['module']) || !is_array($conversion_outline['module'])) {
        $conversion_outline['module'] = array();
    }

    $conversion_outline['module'][DIVI4_OLDER_SETTING_SLUG] = DIVI5_OLDER_SETTING_SLUG . '.*';
    $conversion_outline['module'][DIVI4_NEWER_SETTING_SLUG] = DIVI5_NEWER_SETTING_SLUG . '.*';

    return $conversion_outline;
}

// Rewrite front-end Blog block pagination labels from the saved D5 attributes.
function d5_filter_output($block_content, $parsed_block, $block_instance) {
    if (is_divi5_builder_preview_request()) {
        return $block_content;
    }

    $attrs = is_array($parsed_block) && isset($parsed_block['attrs']) && is_array($parsed_block['attrs']) ? $parsed_block['attrs'] : array();
    $older_entries_text = get_divi5_text_value($attrs, DIVI5_OLDER_SETTING_SLUG);
    $newer_entries_text = get_divi5_text_value($attrs, DIVI5_NEWER_SETTING_SLUG);

    return replace_blog_pagination_link_texts($block_content, $older_entries_text, $newer_entries_text);
}

// === Divi 5 Visual Builder preview sync ===

// Load the inline VB integration only while the builder preview is active.
function enqueue_builder_mapping_script() {
    if ((!function_exists('et_builder_d5_enabled') || !\et_builder_d5_enabled()) ||
        (!function_exists('et_core_is_fb_enabled') || !\et_core_is_fb_enabled())) {
        return;
    }

    if (!function_exists('sanitize_title')) {
        return;
    }

    if (!function_exists('dbdb_enqueue_inline_script_if_dependencies_available')) {
        return;
    }

    $inline_mapping_js = get_inline_mapping_js();
    if ($inline_mapping_js === '') {
        return;
    }

    $handle = sanitize_title('divi-booster-' . DIVI5_MODULE_SLUG . '-pagination-link-text');
    \dbdb_enqueue_inline_script_if_dependencies_available($handle, $inline_mapping_js, array('lodash', array('divi-vendor-wp-hooks', 'wp-hooks')));
}

// Generate the VB script that registers custom fields and keeps preview text in sync.
function get_inline_mapping_js() {
    if (!function_exists('wp_json_encode')) {
        return '';
    }

    $attribute_definitions = encode_js_value(get_attribute_definitions());
    $divi5_slug = encode_js_value(DIVI5_MODULE_SLUG);
    $divi5_older_slug = encode_js_value(DIVI5_OLDER_SETTING_SLUG);
    $divi5_newer_slug = encode_js_value(DIVI5_NEWER_SETTING_SLUG);

    return <<<JS
(function(){
  if (typeof window === 'undefined' || typeof document === 'undefined') return;

  var hooks = (window.vendor && window.vendor.wp && window.vendor.wp.hooks) ? window.vendor.wp.hooks : null;
  if (!hooks) return;

  function requestNextFrame(callback) {
    if (typeof window.requestAnimationFrame === 'function') {
      return window.requestAnimationFrame(callback);
    }

    return window.setTimeout(callback, 16);
  }

  function showPaginationEnabled(attrs) {
    var raw = attrs && attrs.pagination && attrs.pagination.advanced && attrs.pagination.advanced.enable;
    if (!raw) return true;

    if (raw.desktop && typeof raw.desktop.value !== 'undefined') {
      return raw.desktop.value === 'on' || raw.desktop.value === true;
    }

    if (typeof raw.value !== 'undefined') {
      return raw.value === 'on' || raw.value === true;
    }

    if (raw === 'on' || raw === true) {
      return true;
    }

    return false;
  }

  function attachVisibilityCallbacks(defs) {
    [{$divi5_older_slug}, {$divi5_newer_slug}].forEach(function(slug){
      var items = defs &&
        defs[slug] &&
        defs[slug].settings &&
        defs[slug].settings.innerContent &&
        defs[slug].settings.innerContent.items;

      if (!items) return;

      Object.keys(items).forEach(function(key){
        items[key].visible = function(ctx) {
          return showPaginationEnabled(ctx && ctx.attrs);
        };
      });
    });

    return defs;
  }

  hooks.addFilter('divi.moduleLibrary.moduleMapping', 'divi-booster', function(modules){
    var lodash = window.lodash || window._;
    if (!lodash) return modules;

    var path = [{$divi5_slug}, 'metadata', 'attributes'];
    var get = lodash.get;
    var has = lodash.has;
    var set = lodash.set;
    var defs = attachVisibilityCallbacks({$attribute_definitions});
    var target = get(modules, path) ? get(modules, path) : set(modules, path, {});

    if (has(modules, path)) {
      Object.keys(defs).forEach(function(key){
        target[key] = defs[key];
      });
    }

    return modules;
  });

  hooks.addFilter('divi.moduleLibrary.moduleAttributes.divi.blog', 'divi-booster', function(attributes){
    var defs = attachVisibilityCallbacks({$attribute_definitions});

    [{$divi5_older_slug}, {$divi5_newer_slug}].forEach(function(slug){
      attributes[slug] = defs[slug];
    });

    return attributes;
  });

  var textByModuleId = window._diviBoosterBlogPaginationTextById || {};
  window._diviBoosterBlogPaginationTextById = textByModuleId;
  var moduleObserversById = window._diviBoosterBlogPaginationObserversById || {};
  window._diviBoosterBlogPaginationObserversById = moduleObserversById;
  var moduleRootsById = window._diviBoosterBlogPaginationRootsById || {};
  window._diviBoosterBlogPaginationRootsById = moduleRootsById;
  var documentObserver = window._diviBoosterBlogPaginationDocumentObserver || null;
  var queuedModuleIds = {};

  function getTextValue(attrs, slug) {
    var raw = attrs && attrs[slug];
    if (!raw) return '';

    if (raw.desktop && typeof raw.desktop.value !== 'undefined') {
      return String(raw.desktop.value || '');
    }

    if (raw.innerContent && raw.innerContent.desktop && typeof raw.innerContent.desktop.value !== 'undefined') {
      return String(raw.innerContent.desktop.value || '');
    }

    if (typeof raw.value !== 'undefined') {
      return String(raw.value || '');
    }

    if (raw.innerContent && typeof raw.innerContent.value !== 'undefined') {
      return String(raw.innerContent.value || '');
    }

    return '';
  }

  function ensureDefaultAnchorState(anchor) {
    if (!anchor || anchor.hasAttribute('data-divibooster-default-pagination-html')) {
      return;
    }

    anchor.setAttribute('data-divibooster-default-pagination-html', anchor.innerHTML);

    if (anchor.hasAttribute('aria-label')) {
      anchor.setAttribute('data-divibooster-default-pagination-aria-label', anchor.getAttribute('aria-label'));
    }
  }

  function applyAnchorText(anchor, customText) {
    if (!anchor) return;

    ensureDefaultAnchorState(anchor);

    if (customText) {
      if (anchor.textContent !== customText) {
        anchor.textContent = customText;
      }

      if (anchor.getAttribute('aria-label') !== customText) {
        anchor.setAttribute('aria-label', customText);
      }

      return;
    }

    var defaultHtml = anchor.getAttribute('data-divibooster-default-pagination-html');
    if (typeof defaultHtml === 'string' && anchor.innerHTML !== defaultHtml) {
      anchor.innerHTML = defaultHtml;
    }

    var defaultAriaLabel = anchor.getAttribute('data-divibooster-default-pagination-aria-label');
    if (typeof defaultAriaLabel === 'string') {
      if (anchor.getAttribute('aria-label') !== defaultAriaLabel) {
        anchor.setAttribute('aria-label', defaultAriaLabel);
      }
    } else if (anchor.hasAttribute('aria-label')) {
      anchor.removeAttribute('aria-label');
    }
  }

  function applyModuleTexts(moduleElement, textSettings) {
    if (!moduleElement || !textSettings) return;

    applyAnchorText(moduleElement.querySelector('.pagination > .alignleft a'), textSettings.older);
    applyAnchorText(moduleElement.querySelector('.pagination > .alignright a'), textSettings.newer);
  }

  function getModuleElement(moduleId) {
    if (!moduleId) {
      return null;
    }

    var moduleIdString = String(moduleId);

    if (window.CSS && typeof window.CSS.escape === 'function') {
      try {
        return document.querySelector('[data-id="' + window.CSS.escape(moduleIdString) + '"]');
      } catch (e) {}
    }

    var moduleElements = document.querySelectorAll('[data-id]');
    for (var i = 0; i < moduleElements.length; i++) {
      if (moduleElements[i].getAttribute('data-id') === moduleIdString) {
        return moduleElements[i];
      }
    }

    return null;
  }

  function disconnectModuleObserver(moduleId) {
    if (moduleObserversById[moduleId]) {
      moduleObserversById[moduleId].disconnect();
      delete moduleObserversById[moduleId];
    }

    if (moduleRootsById[moduleId]) {
      delete moduleRootsById[moduleId];
    }
  }

  function watchModuleElement(moduleId, moduleElement) {
    if (!moduleId || !moduleElement || moduleRootsById[moduleId] === moduleElement) {
      return;
    }

    disconnectModuleObserver(moduleId);

    if (typeof window.MutationObserver !== 'function') {
      return;
    }

    moduleObserversById[moduleId] = new window.MutationObserver(function() {
      scheduleApply(moduleId);
    });

    moduleObserversById[moduleId].observe(moduleElement, {
      childList: true,
      subtree: true,
      characterData: true
    });

    moduleRootsById[moduleId] = moduleElement;
  }

  function applyModuleTextsById(moduleId) {
    if (!moduleId || !textByModuleId[moduleId]) {
      disconnectModuleObserver(moduleId);
      return;
    }

    var moduleElement = getModuleElement(moduleId);
    if (!moduleElement) {
      disconnectModuleObserver(moduleId);
      return;
    }

    watchModuleElement(moduleId, moduleElement);
    applyModuleTexts(moduleElement, textByModuleId[moduleId]);
  }

  var rafId = 0;
  function scheduleApply(moduleId) {
    if (moduleId) {
      queuedModuleIds[moduleId] = true;
    } else {
      Object.keys(textByModuleId).forEach(function(knownModuleId) {
        queuedModuleIds[knownModuleId] = true;
      });
    }

    if (rafId) return;

    rafId = requestNextFrame(function() {
      var moduleIds = Object.keys(queuedModuleIds);
      queuedModuleIds = {};
      rafId = 0;

      for (var i = 0; i < moduleIds.length; i++) {
        applyModuleTextsById(moduleIds[i]);
      }
    });
  }

  function queueKnownModuleId(moduleId) {
    if (moduleId && textByModuleId[moduleId]) {
      scheduleApply(moduleId);
    }
  }

  function queueKnownModuleIdsFromNode(node) {
    if (!node || node.nodeType !== 1) {
      return;
    }

    if (node.hasAttribute && node.hasAttribute('data-id')) {
      queueKnownModuleId(node.getAttribute('data-id'));
    }

    if (!node.querySelectorAll) {
      return;
    }

    var moduleElements = node.querySelectorAll('[data-id]');
    for (var i = 0; i < moduleElements.length; i++) {
      queueKnownModuleId(moduleElements[i].getAttribute('data-id'));
    }
  }

  function queueMutatedModuleIds(mutations) {
    for (var i = 0; i < mutations.length; i++) {
      var mutation = mutations[i];

      queueKnownModuleIdsFromNode(mutation.target);

      if (!mutation.addedNodes) {
        continue;
      }

      for (var j = 0; j < mutation.addedNodes.length; j++) {
        queueKnownModuleIdsFromNode(mutation.addedNodes[j]);
      }
    }
  }

  hooks.addFilter('divi.module.wrapper.render', 'divi-booster', function(moduleWrapper, param) {
    var moduleName = param && param.name || '';
    if (moduleName !== {$divi5_slug}) {
      return moduleWrapper;
    }

    var moduleId = param && param.id || '';
    if (!moduleId) {
      return moduleWrapper;
    }

    var attrs = param && param.attrs || {};
    textByModuleId[moduleId] = {
      older: getTextValue(attrs, {$divi5_older_slug}).trim(),
      newer: getTextValue(attrs, {$divi5_newer_slug}).trim()
    };

    scheduleApply(moduleId);
    return moduleWrapper;
  });

  function connectDocumentObserver() {
    if (documentObserver) {
      return;
    }

    if (typeof window.MutationObserver !== 'function') {
      return;
    }

    var observerRoot = document.body || document.documentElement;
    if (!observerRoot) {
      requestNextFrame(connectDocumentObserver);
      return;
    }

    documentObserver = new window.MutationObserver(function(mutations) {
      queueMutatedModuleIds(mutations);
    });

    documentObserver.observe(observerRoot, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ['data-id']
    });

    window._diviBoosterBlogPaginationDocumentObserver = documentObserver;
  }

  connectDocumentObserver();
  scheduleApply(); // Catch modules already present when the script initializes.
})();
JS;
}
