<?php
namespace DiviBooster\DiviBooster\Features\BlogModuleTags;

// Constants
const DIVI4_MODULE_SLUG = 'et_pb_blog';
const DIVI5_MODULE_SLUG = 'divi/blog';
const DIVI4_SHOW_TAGS   = 'dbdb_show_tags';
const DIVI4_SEPARATOR   = 'dbdb_tags_separator';
const DIVI5_SHOW_TAGS   = 'diviboosterShowTags';
const DIVI5_SEPARATOR   = 'diviboosterTagsSeparator';

// Register hooks immediately so Divi 5 migrator sees conversion outline early (no init wrapper needed)
add_filter('et_pb_all_fields_unprocessed_' . DIVI4_MODULE_SLUG, __NAMESPACE__ . '\\d4_register_fields');
add_filter('et_module_shortcode_output', __NAMESPACE__ . '\\d4_filter_output', 10, 3);
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);
add_action('wp_enqueue_scripts', __NAMESPACE__ . '\\enqueue_builder_mapping_script');
add_action('wp_ajax_dbdb_blog_tags_preview', __NAMESPACE__ . '\\ajax_get_preview_tags');

// === Divi 5: Attribute Registration (PHP, front-end) ===
// Register attributes via core WP filter so they're present at block registration time.
if (function_exists('add_filter')) {
    add_filter('block_type_metadata_settings', __NAMESPACE__ . '\\d5_register_metadata_attrs', 10, 2);
}

// =============== Divi 4 ===============
function d4_register_fields($fields) {
    $fields[DIVI4_SHOW_TAGS] = array(
        'label'             => esc_html__('Show Tags', 'et_builder'),
        'type'              => 'yes_no_button',
        'option_category'   => 'configuration',
        'options'           => array(
            'on'  => esc_html__('Yes', 'et_builder'),
            'off' => esc_html__('No', 'et_builder'),
        ),
        'default_on_front'  => 'off',
        'toggle_slug'       => 'elements',
        'description'       => esc_html__('Turn the tag links on or off.', 'et_builder'),
        'mobile_options'    => true,
        'hover'             => 'tabs',
    );

    $fields[DIVI4_SEPARATOR] = array(
        'label'             => esc_html__('Tag Separator', 'et_builder'),
        'type'              => 'text',
        'option_category'   => 'configuration',
        'show_if'           => array(
            DIVI4_SHOW_TAGS => 'on',
        ),
        'default'           => ', ',
        'toggle_slug'       => 'elements',
        'description'       => esc_html__('The text to use between the tags.', 'et_builder'),
    );

    return $fields;
}

function d4_filter_output($output, $render_slug, $module) {
    if (!is_string($output) || $render_slug !== DIVI4_MODULE_SLUG || !isset($module->props) || !is_array($module->props)) {
        return $output;
    }

    $show = isset($module->props[DIVI4_SHOW_TAGS]) ? $module->props[DIVI4_SHOW_TAGS] : 'off';
    if ($show !== 'on') {
        return $output;
    }

    $sep = isset($module->props[DIVI4_SEPARATOR]) ? (string) $module->props[DIVI4_SEPARATOR] : ', ';
    return inject_tags_into_blog_html($output, $sep);
}

// =============== Divi 5 ===============
function d5_register_metadata_attrs($settings, $metadata = null) {
    $module_name       = isset($settings['name']) ? $settings['name'] : '';
    $module_attributes = isset($settings['attributes']) ? $settings['attributes'] : false;

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

    // Inject our attributes into the block's metadata settings
    $defs = get_attribute_definitions();
    foreach ($defs as $slug => $def) {
        $settings['attributes'][$slug] = $def;
    }

    return $settings;
}

function d5_conversion_outline($conversion_outline, $module_name) {
    // Map Divi 4 fields to Divi 5 attributes during conversion
    // Note: Must check against the Divi 5 module slug here
    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_SHOW_TAGS] = DIVI5_SHOW_TAGS . '.*';
    $conversion_outline['module'][DIVI4_SEPARATOR] = DIVI5_SEPARATOR . '.*';

    return $conversion_outline;
}

function d5_filter_output($block_content, $parsed_block, $block_instance) {
    // Extract attribute values
    $attrs = isset($parsed_block['attrs']) && is_array($parsed_block['attrs']) ? $parsed_block['attrs'] : array();

    $show = get_divi5_attr_value($attrs, DIVI5_SHOW_TAGS, 'off');

    if ($show !== 'on') {
        return $block_content;
    }

    $sep = get_divi5_attr_value($attrs, DIVI5_SEPARATOR, ', ');

    return inject_tags_into_blog_html($block_content, $sep);
}

function get_divi5_attr_value($attrs, $slug, $default = '') {
    if (!is_array($attrs) || !array_key_exists($slug, $attrs)) {
        return $default;
    }

    $value = $attrs[$slug];
    if (is_array($value)) {
        $paths = [
            ['desktop', 'value'],
            ['innerContent', 'desktop', 'value'],
            ['value'],
            ['innerContent', 'value'],
        ];

        foreach ($paths as $path) {
            $candidate = $value;
            foreach ($path as $key) {
                if (!is_array($candidate) || !array_key_exists($key, $candidate)) {
                    continue 2;
                }
                $candidate = $candidate[$key];
            }
            if (is_scalar($candidate)) {
                return (string) $candidate;
            }
        }
    }

    return is_scalar($value) ? (string) $value : $default;
}

function enqueue_builder_mapping_script() {
    // Only in Divi 5 Visual Builder
    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 . '-' . 'blog-tags');
    \dbdb_enqueue_inline_script_if_dependencies_available($handle, get_inline_mapping_js(), array('lodash', array('divi-vendor-wp-hooks', 'wp-hooks')));
}

function get_inline_mapping_js() {
    $defs = json_encode(get_attribute_definitions());
    $divi5_slug = json_encode(DIVI5_MODULE_SLUG);
    $toggleSlug = json_encode(DIVI5_SHOW_TAGS);
    $separatorSlug = json_encode(DIVI5_SEPARATOR);
    $preview_config = json_encode([
        'ajaxUrl' => admin_url('admin-ajax.php'),
        'nonce'   => wp_create_nonce('dbdb_blog_tags_preview'),
        'action'  => 'dbdb_blog_tags_preview',
    ]);
    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;
  var previewConfig = {$preview_config};
  var previewCache = {};
  var previewPending = {};
  function requestNextFrame(callback) {
    if (typeof window.requestAnimationFrame === 'function') {
      return window.requestAnimationFrame(callback);
    }
    return window.setTimeout(callback, 16);
  }
  function getAttrValue(attrs, slug, fallback) {
    var raw = attrs && attrs[slug];
    if (!raw) return fallback;
    if (raw.desktop && typeof raw.desktop.value !== 'undefined') return raw.desktop.value;
    if (raw.innerContent && raw.innerContent.desktop && typeof raw.innerContent.desktop.value !== 'undefined') return raw.innerContent.desktop.value;
    if (typeof raw.value !== 'undefined') return raw.value;
    if (raw.innerContent && typeof raw.innerContent.value !== 'undefined') return raw.innerContent.value;
    return typeof raw === 'string' || typeof raw === 'boolean' || typeof raw === 'number' ? raw : fallback;
  }
  function isOn(value) {
    return value === 'on' || value === true || value === 'true' || value === 1 || value === '1';
  }
  hooks.addFilter('divi.moduleLibrary.moduleMapping', 'divi', function(modules){
    var path = [{$divi5_slug}, 'metadata', 'attributes'];
    var lodash = window.lodash || window._;
    if(!lodash) return modules;
    var set = lodash.set, get = lodash.get, has = lodash.has;
    var defs = {$defs};
    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;
  });
    // Register the attributes in the settings panel and show Tag Separator only when Show Tags is ON.
    hooks.addFilter('divi.moduleLibrary.moduleAttributes.divi.blog', 'divi-booster', function(attributes){
        var defs = {$defs};
        var toggleSlug = {$toggleSlug};
        var dependentSlugs = [{$separatorSlug}];
        attributes = attributes || {};
        Object.keys(defs).forEach(function(key){ attributes[key] = defs[key]; });
        var isToggleOn = function(attrs){
            return isOn(getAttrValue(attrs, toggleSlug, 'off'));
        };
        dependentSlugs.forEach(function(dep){
            var items = attributes && attributes[dep] && attributes[dep].settings && attributes[dep].settings.innerContent && attributes[dep].settings.innerContent.items;
            if(!items) return;
            Object.keys(items).forEach(function(key){
                items[key].visible = function(ctx){ return isToggleOn(ctx.attrs); };
            });
        });
        return attributes;
    });
  hooks.addFilter('divi.module.wrapper.render', 'divi-booster-blog-tags', function(moduleWrapper, param) {
    var moduleName = param && param.name || '';
    if (moduleName !== {$divi5_slug}) {
      return moduleWrapper;
    }
    var attrs = param && param.attrs || {};
    var moduleId = param && (param.id || param.moduleId) || moduleWrapper && moduleWrapper.props && (moduleWrapper.props['data-id'] || moduleWrapper.props['data-module-id']) || '';
    schedulePreviewSync(moduleId, attrs, 0);
    return moduleWrapper;
  });
  function schedulePreviewSync(moduleId, attrs, attempt) {
    requestNextFrame(function(){
      syncPreview(moduleId, attrs, attempt || 0);
    });
  }
  function syncPreview(moduleId, attrs, attempt) {
    var moduleEl = findModuleElement(moduleId);
    if (!moduleEl) {
      if (attempt < 3) {
        window.setTimeout(function(){ schedulePreviewSync(moduleId, attrs, attempt + 1); }, 40);
      }
      return;
    }
    if (!isOn(getAttrValue(attrs, {$toggleSlug}, 'off'))) {
      moduleEl.removeAttribute('data-dbdb-blog-tags-preview-key');
      clearPreviewTags(moduleEl);
      return;
    }
    var articles = Array.prototype.slice.call(moduleEl.querySelectorAll('article.et_pb_post'));
    if (!articles.length) {
      if (attempt < 3) {
        window.setTimeout(function(){ schedulePreviewSync(moduleId, attrs, attempt + 1); }, 40);
      }
      return;
    }
    var sep = String(getAttrValue(attrs, {$separatorSlug}, ', '));
    var entries = [];
    articles.forEach(function(article){
      var postId = getArticlePostId(article);
      if (!postId) return;
      entries.push({ id: postId, type: getArticlePostType(article) });
    });
    if (!entries.length) {
      moduleEl.removeAttribute('data-dbdb-blog-tags-preview-key');
      clearPreviewTags(moduleEl);
      return;
    }
    var cacheKey = sep + '|' + entries.map(function(entry){ return entry.id + ':' + entry.type; }).join(',');
    moduleEl.setAttribute('data-dbdb-blog-tags-preview-key', cacheKey);
    if (previewCache[cacheKey]) {
      applyPreviewTags(moduleEl, articles, previewCache[cacheKey], cacheKey);
      return;
    }
    if (!previewConfig.ajaxUrl || typeof window.fetch !== 'function') {
      return;
    }
    if (!previewPending[cacheKey]) {
      var form = new window.FormData();
      form.append('action', previewConfig.action);
      form.append('nonce', previewConfig.nonce);
      form.append('separator', sep);
      form.append('posts', JSON.stringify(entries));
      previewPending[cacheKey] = window.fetch(previewConfig.ajaxUrl, {
        method: 'POST',
        credentials: 'same-origin',
        body: form
      }).then(function(response){
        return response.json();
      }).then(function(response){
        var tags = response && response.success && response.data && response.data.tags ? response.data.tags : {};
        previewCache[cacheKey] = tags;
        delete previewPending[cacheKey];
        return tags;
      }).catch(function(){
        delete previewPending[cacheKey];
        return {};
      });
    }
    previewPending[cacheKey].then(function(tags){
      applyPreviewTags(moduleEl, articles, tags, cacheKey);
    });
  }
  function findModuleElement(moduleId) {
    if (!moduleId) return null;
    var id = String(moduleId);
    if (window.CSS && typeof window.CSS.escape === 'function') {
      try {
        return document.querySelector('[data-id="' + window.CSS.escape(id) + '"]');
      } catch (e) {}
    }
    var nodes = document.querySelectorAll('[data-id]');
    for (var i = 0; i < nodes.length; i++) {
      if (nodes[i].getAttribute('data-id') === id) return nodes[i];
    }
    return null;
  }
  function getArticlePostId(article) {
    var cls = article && article.className ? String(article.className) : '';
    var match = cls.match(/(?:^|\s)et_pb_post_id_(\d+)(?:\s|$)/) || cls.match(/(?:^|\s)post-(\d+)(?:\s|$)/);
    return match ? parseInt(match[1], 10) : 0;
  }
  function getArticlePostType(article) {
    var cls = article && article.className ? String(article.className) : '';
    var match = cls.match(/(?:^|\s)type-([a-z0-9_-]+)(?:\s|$)/i);
    return match ? match[1] : 'post';
  }
  function clearPreviewTags(root) {
    Array.prototype.slice.call(root.querySelectorAll('.dbdb-post-tags, .dbdb-post-tags-meta-separator')).forEach(function(node){
      if (node && node.parentNode) node.parentNode.removeChild(node);
    });
  }
  function applyPreviewTags(moduleEl, articles, tags, cacheKey) {
    if (!moduleEl || moduleEl.getAttribute('data-dbdb-blog-tags-preview-key') !== cacheKey) return;
    clearPreviewTags(moduleEl);
    articles.forEach(function(article){
      var postId = getArticlePostId(article);
      var html = postId && tags && tags[String(postId)] ? tags[String(postId)] : '';
      if (!html) return;
      insertPreviewTags(article, html);
    });
  }
  function insertPreviewTags(article, tagsHtml) {
    var meta = article.querySelector('p.post-meta');
    if (meta) {
      meta.insertAdjacentHTML('beforeend', '<span class="dbdb-post-tags-meta-separator"> | </span>' + tagsHtml);
      return;
    }
    var title = article.querySelector('.entry-title');
    if (title) {
      title.insertAdjacentHTML('afterend', '<p class="post-meta">' + tagsHtml + '</p>');
      return;
    }
    article.insertAdjacentHTML('afterbegin', '<p class="post-meta">' + tagsHtml + '</p>');
  }
})();
JS;
}

function ajax_get_preview_tags() {
    if (!check_ajax_referer('dbdb_blog_tags_preview', 'nonce', false)) {
        wp_send_json_error(array('message' => 'Invalid nonce'), 403);
    }

    $posts_json = isset($_POST['posts']) ? wp_unslash($_POST['posts']) : '[]';
    $posts      = json_decode(is_string($posts_json) ? $posts_json : '[]', true);
    $separator  = isset($_POST['separator']) ? sanitize_text_field(wp_unslash($_POST['separator'])) : ', ';

    if (!is_array($posts)) {
        wp_send_json_success(array('tags' => array()));
    }

    $tags = array();
    foreach (array_slice($posts, 0, 50) as $post) {
        $post_id   = isset($post['id']) ? absint($post['id']) : 0;
        $post_type = isset($post['type']) ? sanitize_key($post['type']) : 'post';

        if (!$post_id) {
            continue;
        }

        $tags_html = build_tags_html($post_id, $post_type, $separator);
        if ($tags_html !== '') {
            $tags[(string) $post_id] = $tags_html;
        }
    }

    wp_send_json_success(array('tags' => $tags));
}

function get_attribute_definitions() {
    // Two attributes: Show Tags (toggle) and Tag Separator (text)
    return [
        DIVI5_SHOW_TAGS => [
            'type'     => 'object',
            'settings' => [
                'innerContent' => [
                    'groupType' => 'group-items',
                    'items'     => [
                        'showTagsToggle' => [
                            'groupSlug'   => 'contentElements',
                            'attrName'    => DIVI5_SHOW_TAGS,
                            'label'       => 'Show Tags',
                            'description' => 'Turn the tag links on or off.',
                            'features'    => [
                                'hover'      => false,
                                'sticky'     => false,
                                'responsive' => false,
                                'preset'     => 'content',
                            ],
                            'render'   => true,
                            'priority' => 75,
                            'component' => [
                                'type' => 'field',
                                'name' => 'divi/toggle',
                            ],
                            // Provide defaultAttr so value exists for migrator / builder
                            'defaultAttr' => [ 'desktop' => [ 'value' => 'off' ] ],
                        ],
                    ],
                ],
            ],
        ],
        DIVI5_SEPARATOR => [
            'type'     => 'object',
            'settings' => [
                'innerContent' => [
                    'groupType' => 'group-items',
                    'items'     => [
                        'tagsSeparatorText' => [
                            'groupSlug'   => 'contentElements',
                            'attrName'    => DIVI5_SEPARATOR,
                            'label'       => 'Tag Separator',
                            'description' => 'The text to use between the tags.',
                            'features'    => [
                                'hover'      => false,
                                'sticky'     => false,
                                'responsive' => false,
                                'preset'     => 'content',
                            ],
                            'render'   => true,
                            'priority' => 76,
                            'component' => [
                                'type' => 'field',
                                'name' => 'divi/text',
                                'props' => [
                                    // Attempt to hide unless toggle is on (may depend on Divi 5 UI support)
                                    'showIf' => [ DIVI5_SHOW_TAGS => 'on' ],
                                ],
                            ],
                            // Default separator value for migration / builder
                            'defaultAttr' => [ 'desktop' => [ 'value' => ', ' ] ],
                        ],
                    ],
                ],
            ],
        ],
    ];
}

// =============== Common HTML Injection ===============
function inject_tags_into_blog_html($html, $separator) {
    if (!is_string($html) || $html === '') {
        return $html;
    }

    $sep = is_string($separator) ? $separator : ', ';

    return preg_replace_callback('/<article\b[\s\S]*?<\/article>/', function($m) use ($sep) {
        $article = $m[0];
        // Avoid duplicate insertions
        if (strpos($article, 'class="dbdb-post-tags"') !== false) {
            return $article;
        }
        $updated = process_single_article($article, $sep);
        return $updated;
    }, $html);
}

function process_single_article($article_html, $sep) {
    // Extract post ID
    $post_id = extract_post_id_from_article($article_html);
    if (!$post_id) {
        return $article_html;
    }

    // Extract post type from class attribute (e.g., type-post)
    $post_type = 'post';
    if (preg_match('/\bclass="[^"]*\btype-([a-z0-9_\-]+)\b/i', $article_html, $m_type)) {
        $post_type = sanitize_key($m_type[1]);
    }

    $tags_html = build_tags_html($post_id, $post_type, $sep);
    if ($tags_html === '') {
        return $article_html;
    }

    // If post-meta exists, append to its content before closing </p>
    if (strpos($article_html, '<p class="post-meta">') !== false) {
        $replacement = '$1 | ' . $tags_html . '$2';
        $article_html = preg_replace('/(<p class="post-meta">[\s\S]*?)(<\/p>)/i', $replacement, $article_html, 1);
        return $article_html;
    }

    // Insert <p class="post-meta"> after entry-title block if present
    if (preg_match('/(<h2 class="entry-title">[\s\S]*?<\/h2>)/i', $article_html)) {
        $article_html = preg_replace('/(<h2 class="entry-title">[\s\S]*?<\/h2>)/i', '$1<p class="post-meta">' . $tags_html . '</p>', $article_html, 1);
        return $article_html;
    }

    // Otherwise insert above post-content if possible
    if (preg_match('/<div class="post-content"/i', $article_html)) {
        $article_html = preg_replace('/(<div class="post-content"\b)/i', '<p class="post-meta">' . $tags_html . '</p>$1', $article_html, 1);
        return $article_html;
    }

    // Fallback: append at top of article content
    $article_html = preg_replace('/(<article\b[^>]*>)/i', '$1<p class="post-meta">' . $tags_html . '</p>', $article_html, 1);
    return $article_html;
}

function extract_post_id_from_article($article_html) {
    if (preg_match('/\bid="post-(\d+)"/i', $article_html, $m_id)) {
        return intval($m_id[1]);
    }

    if (preg_match('/\bclass="[^"]*\bet_pb_post_id_(\d+)\b[^"]*"/i', $article_html, $m_id)) {
        return intval($m_id[1]);
    }

    if (preg_match('/\bclass="[^"]*\bpost-(\d+)\b[^"]*"/i', $article_html, $m_id)) {
        return intval($m_id[1]);
    }

    return 0;
}

function build_tags_html($post_id, $post_type, $sep) {
    $taxonomy = ($post_type === 'post') ? 'post_tag' : $post_type . '_tag';
    if (!taxonomy_exists($taxonomy)) {
        return '';
    }

    $sep_safe = esc_html($sep);
    $list = get_the_term_list($post_id, $taxonomy, '', $sep_safe, '');
    if (is_wp_error($list) || empty($list)) {
        return '';
    }

    return '<span class="dbdb-post-tags">' . $list . '</span>';
}

// Created at 1762860783
