<?php
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Safely read/write WPBakery (Visual Composer) content.
 *
 * Supports multiple text-based shortcodes and preserves
 * shortcode structure/attributes during text replacement.
 *
 * @since 2.1.0
 * @since 2.1.3 Added support for additional shortcodes and attribute text
 */
class AI_Magic_WPBakery_Processor
{

    /**
     * Shortcodes that contain editable text content.
     *
     * @var array
     */
    private $text_shortcodes = array(
        'vc_column_text',      // Rich text content
        'vc_custom_heading',   // Heading with title attribute
        'vc_message',          // Message box
        'vc_btn',              // Button with title attribute
        'vc_accordion_tab',    // Accordion tab
        'vc_tta_section',      // Tab/Accordion section
        'vc_tab',              // Legacy tab
        'vc_cta',              // Call to action
    );

    /**
     * Attributes that contain text (not content between tags).
     *
     * @var array
     */
    private $text_attributes = array(
        'title',
        'text',
        'heading',
        'sub_heading',
        'h2',
        'h4',
    );

    /**
     * Get raw post_content.
     *
     * @param int $post_id
     * @return string
     */
    public function get_content($post_id)
    {
        return get_post_field('post_content', $post_id);
    }

    /**
     * Save updated content back to post.
     *
     * @param int    $post_id
     * @param string $content
     * @return int|WP_Error
     */
    public function save_content($post_id, $content)
    {
        return wp_update_post(array(
            'ID' => $post_id,
            'post_content' => $content,
        ));
    }

    /**
     * Extract text content from WPBakery shortcodes.
     *
     * Handles both content between shortcode tags and
     * text stored in shortcode attributes.
     *
     * @param string $content Post content
     * @return array Array of text items
     */
    public function extract_vc_texts($content)
    {
        $texts = array();

        if (!is_string($content) || $content === '') {
            return $texts;
        }

        // Extract from each text shortcode
        foreach ($this->text_shortcodes as $shortcode) {
            $this->extract_from_shortcode($content, $shortcode, $texts);
        }

        return $texts;
    }

    /**
     * Extract text from a specific shortcode type.
     *
     * @param string $content   Full content
     * @param string $shortcode Shortcode name
     * @param array  &$texts    Texts array (by reference)
     * @return void
     */
    private function extract_from_shortcode($content, $shortcode, array &$texts)
    {
        // Pattern for self-closing and content shortcodes
        // [shortcode attr="value"]content[/shortcode] or [shortcode attr="value" /]
        $pattern = '/\[' . preg_quote($shortcode, '/') . '([^\]]*)\](?:(.*?)\[\/' . preg_quote($shortcode, '/') . '\])?/is';

        if (!preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) {
            return;
        }

        foreach ($matches as $match) {
            $full_shortcode = $match[0];
            $attributes_str = isset($match[1]) ? $match[1] : '';
            $inner_content = isset($match[2]) ? $match[2] : '';

            // Parse attributes for text fields
            $attributes = $this->parse_shortcode_attributes($attributes_str);

            // Add attribute-based text (title, text, heading, etc.)
            foreach ($this->text_attributes as $attr) {
                if (!empty($attributes[$attr]) && is_string($attributes[$attr])) {
                    $attr_value = $attributes[$attr];
                    $plain_text = wp_strip_all_tags($attr_value);

                    if (strlen(trim($plain_text)) > 2) {
                        $texts[] = array(
                            'shortcode' => $shortcode,
                            'full' => $full_shortcode,
                            'attrs' => $attributes_str,
                            'attr_name' => $attr,
                            'value' => $plain_text,
                            'original' => $attr_value,
                            'type' => 'attribute',
                            'content_inner' => $inner_content,
                        );
                    }
                }
            }

            // Add inner content if present
            if (!empty($inner_content)) {
                $plain_text = wp_strip_all_tags($inner_content);

                if (strlen(trim($plain_text)) > 10) {
                    $texts[] = array(
                        'shortcode' => $shortcode,
                        'full' => $full_shortcode,
                        'attrs' => $attributes_str,
                        'value' => $plain_text,
                        'original' => $inner_content,
                        'type' => 'content',
                    );
                }
            }
        }
    }

    /**
     * Parse shortcode attributes string.
     *
     * @param string $attrs_str Attributes string from shortcode
     * @return array Parsed attributes
     */
    private function parse_shortcode_attributes($attrs_str)
    {
        $attrs = array();

        // Match key="value" or key='value' patterns
        if (preg_match_all('/(\w+)=["\']([^"\']*)["\']/', $attrs_str, $matches, PREG_SET_ORDER)) {
            foreach ($matches as $match) {
                $attrs[$match[1]] = $match[2];
            }
        }

        return $attrs;
    }

    /**
     * Apply new texts back to shortcode content.
     *
     * @param string $content    Original content
     * @param array  $texts      Items from extract_vc_texts()
     * @param array  $new_values New text values
     * @return string Updated content
     */
    public function apply_new_vc_texts($content, array $texts, array $new_values)
    {
        // Process from end to preserve offsets
        for ($i = count($texts) - 1; $i >= 0; $i--) {
            if (!isset($new_values[$i])) {
                continue;
            }

            $item = $texts[$i];
            $new_text = $new_values[$i];

            if ($item['type'] === 'attribute') {
                // Replace attribute value
                $content = $this->replace_attribute_value(
                    $content,
                    $item['full'],
                    $item['attr_name'],
                    $item['original'],
                    $new_text
                );
            } else {
                // Replace inner content
                $content = $this->replace_inner_content(
                    $content,
                    $item['full'],
                    $item['original'],
                    $new_text
                );
            }
        }

        return $content;
    }

    /**
     * Replace attribute value in a shortcode.
     *
     * @param string $content       Full content
     * @param string $full_shortcode Full shortcode match
     * @param string $attr_name     Attribute name
     * @param string $old_value     Old attribute value
     * @param string $new_value     New attribute value
     * @return string Updated content
     */
    private function replace_attribute_value($content, $full_shortcode, $attr_name, $old_value, $new_value)
    {
        // Escape for regex
        $escaped_old = preg_quote($old_value, '/');

        // Create new shortcode with replaced attribute
        $pattern = '/(' . preg_quote($attr_name, '/') . '=["\'])' . $escaped_old . '(["\'])/';
        $new_shortcode = preg_replace($pattern, '${1}' . $new_value . '${2}', $full_shortcode, 1);

        if ($new_shortcode !== $full_shortcode) {
            $content = str_replace($full_shortcode, $new_shortcode, $content);
        }

        return $content;
    }

    /**
     * Replace inner content of a shortcode.
     *
     * @param string $content        Full content
     * @param string $full_shortcode Full shortcode match
     * @param string $old_inner      Old inner content
     * @param string $new_inner      New inner content
     * @return string Updated content
     */
    private function replace_inner_content($content, $full_shortcode, $old_inner, $new_inner)
    {
        // Replace the inner content within the shortcode
        $new_shortcode = str_replace($old_inner, $new_inner, $full_shortcode);

        if ($new_shortcode !== $full_shortcode) {
            $content = str_replace($full_shortcode, $new_shortcode, $content);
        }

        return $content;
    }

    /**
     * Get supported text shortcodes.
     *
     * @return array
     */
    public function get_text_shortcodes()
    {
        return $this->text_shortcodes;
    }

    /**
     * Add custom shortcode(s) to process.
     *
     * @param string|array $shortcodes Shortcode name(s)
     * @return void
     */
    public function add_text_shortcodes($shortcodes)
    {
        if (is_string($shortcodes)) {
            $shortcodes = array($shortcodes);
        }

        $this->text_shortcodes = array_unique(array_merge($this->text_shortcodes, $shortcodes));
    }

    /**
     * Add custom attribute(s) to extract.
     *
     * @param string|array $attributes Attribute name(s)
     * @return void
     */
    public function add_text_attributes($attributes)
    {
        if (is_string($attributes)) {
            $attributes = array($attributes);
        }

        $this->text_attributes = array_unique(array_merge($this->text_attributes, $attributes));
    }

}
