<?php

if (! defined('ABSPATH')) {
    die('You are not allowed to call this page directly.');
}

/**
 * This class handles the registrations and enqueues for MemberPress blocks
 */
class MeprBlocksCtrl extends MeprBaseCtrl
{
    /**
     * Register all class actions and filters.
     */
    public function load_hooks()
    {
        // Only load block stuff when Gutenberg is active (e.g. WordPress 5.0+).
        if (function_exists('register_block_type')) {
            add_action('init', [$this, 'register_block_types_serverside']);
            add_action('enqueue_block_editor_assets', [$this, 'enqueue_editor_block_scripts']);
            add_action('enqueue_block_assets', [$this, 'enqueue_block_scripts']);
            add_filter('mepr_is_product_page', [$this, 'signup_block_enqueues'], 10, 2);
            add_filter('mepr_is_account_page', [$this, 'account_block_enqueues'], 10, 2);
            add_filter('register_block_type_args', [$this, 'add_protection_attributes']);
            add_filter('render_block', [$this, 'block_content_protection'], 10, 2);
            add_filter('block_editor_settings_all', [$this, 'add_checkout_block_editor_styles']);
        }
    }

    /**
     * Render the frontend for the blocks on the server ("save" method must return null)
     *
     * @return void
     */
    public function register_block_types_serverside()
    {
        $mepr_options    = MeprOptions::fetch();
        $disabled_blocks = MeprHooks::apply_filters('mepr_disabled_blocks', []);

        // Membership signup form block.
        register_block_type(
            'memberpress/membership-signup',
            [
                'api_version'     => 2,
                'attributes'      => [
                    'membership' => [
                        'type' => 'string',
                    ],
                ],
                'render_callback' => [$this, 'render_membership_signup_block'],
            ]
        );

        // Account form block.
        register_block_type(
            'memberpress/account-form',
            [
                'attributes'      => [],
                'render_callback' => [$this, 'render_account_block'],
            ]
        );

        // Login form block.
        register_block_type(
            'memberpress/login-form',
            [
                'api_version'     => 2,
                'attributes'      => [
                    'use_redirect' => [
                        'type' => 'boolean',
                    ],
                ],
                'render_callback' => [$this, 'render_login_block'],
            ]
        );

        if (!in_array('protected-content', $disabled_blocks, true)) {
            // Protected content block.
            register_block_type(
                'memberpress/protected-content',
                [
                    'attributes'      => [
                        'rule'           => [
                            'type' => 'number',
                        ],
                        'ifallowed'      => [
                            'type' => 'string',
                        ],
                        'unauth'         => [
                            'type' => 'string',
                        ],
                        'unauth_message' => [
                            'type' => 'string',
                        ],
                    ],
                    'render_callback' => [$this, 'render_protected_content_block'],
                ]
            );
        }

        // Pro Login Form.
        register_block_type(
            'memberpress/pro-login-form',
            [
                'api_version'     => 2,
                'attributes'      => [
                    'show_welcome_image' => [
                        'type'    => 'boolean',
                        'default' => $mepr_options->design_show_login_welcome_image,
                    ],
                    'welcome_image'      => [
                        'type'    => 'string',
                        'default' => wp_get_attachment_url($mepr_options->design_login_welcome_img),
                    ],
                    'admin_view'         => [
                        'type' => 'boolean',
                    ],
                ],
                'render_callback' => [$this, 'render_pro_login_block'],
                'editor_style'    => 'mp-pro-login',
            ]
        );

        // Pricing Columns for Pro Template.
        register_block_type(
            'memberpress/pro-pricing-table',
            [
                'api_version'     => 2,
                'attributes'      => [
                    'show_title'             => [
                        'type' => 'boolean',
                    ],
                    'button_highlight_color' => [
                        'type'    => 'string',
                        'default' => '#EF1010',
                    ],
                    'group_id'               => [
                        'type'    => 'string',
                        'default' => '',
                    ],
                ],
                'render_callback' => [$this, 'render_pro_pricing_block'],
                'editor_style'    => 'mp-pro-pricing',
            ]
        );

        // Accounts Tab.
        register_block_type(
            'memberpress/pro-account-tabs',
            [
                'api_version'     => 2,
                'attributes'      => [
                    'show_welcome_image' => [
                        'type'    => 'boolean',
                        'default' => $mepr_options->design_show_account_welcome_image,
                    ],
                    'welcome_image'      => [
                        'type'    => 'string',
                        'default' => wp_get_attachment_url($mepr_options->design_account_welcome_img),
                    ],
                ],
                'editor_style'    => 'mp-pro-account',
                'render_callback' => [$this, 'render_pro_account_block'],
            ]
        );

        // ReadyLaunch Account wrapper block.
        register_block_type(
            'memberpress/readylaunch-account',
            [
                'api_version'     => 2,
                'attributes'      => [
                    'layout' => [
                        'type'    => 'string',
                        'default' => 'vertical',
                    ],
                ],
                'editor_script'   => 'memberpress/blocks',
                'editor_style'    => 'mp-readylaunch-account',
                'render_callback' => [$this, 'render_readylaunch_account_block'],
            ]
        );

        // ReadyLaunch Account Menu block.
        register_block_type(
            'memberpress/readylaunch-account-menu',
            [
                'api_version'     => 2,
                'attributes'      => [
                    'layout'                => [
                        'type'    => 'string',
                        'default' => 'vertical',
                    ],
                    'menuBackgroundColor'   => [
                        'type'    => 'string',
                        'default' => '',
                    ],
                    'buttonBackgroundColor' => [
                        'type'    => 'string',
                        'default' => '',
                    ],
                    'buttonHoverBgColor'    => [
                        'type'    => 'string',
                        'default' => '',
                    ],
                    'buttonTextColor'       => [
                        'type'    => 'string',
                        'default' => '',
                    ],
                    'buttonHoverTextColor'  => [
                        'type'    => 'string',
                        'default' => '',
                    ],
                ],
                'editor_script'   => 'memberpress/blocks',
                'editor_style'    => 'mp-pro-account',
                'render_callback' => [$this, 'render_readylaunch_account_menu_block'],
            ]
        );

        // ReadyLaunch Account Content block.
        register_block_type(
            'memberpress/readylaunch-account-content',
            [
                'api_version'     => 2,
                // No computed defaults here: resolving wp_get_attachment_url() at registration
                // would run a DB query on every init (front and back end) even when the block
                // isn't on the page. render_content_html() already falls back to the global
                // design options at render time when these attributes are unset.
                'attributes'      => [
                    'show_welcome_image' => [
                        'type' => 'boolean',
                    ],
                    'welcome_image'      => [
                        'type' => 'string',
                    ],
                ],
                'editor_script'   => 'memberpress/blocks',
                'editor_style'    => 'mp-pro-account',
                'render_callback' => [$this, 'render_readylaunch_account_content_block'],
            ]
        );

        // Checkout.
        register_block_type(
            'memberpress/checkout',
            [
                'api_version'     => 2,
                'attributes'      => [
                    'show_welcome_image' => [
                        'type' => 'boolean',
                    ],
                    'membership_id'      => [
                        'type'    => 'string',
                        'default' => '',
                    ],
                ],
                'render_callback' => [$this, 'render_checkout_block'],
                'editor_style'    => 'mp-pro-checkout',
            ]
        );

        // Account Links.
        register_block_type(
            'memberpress/account-links',
            [
                'api_version'     => 2,
                'attributes'      => [],
                'render_callback' => [$this, 'render_account_links_block'],
            ]
        );

        // Subscriptions.
        register_block_type(
            'memberpress/subscriptions',
            [
                'api_version'     => 2,
                'attributes'      => [
                    'order_by'                 => [
                        'type'    => 'string',
                        'default' => '',
                    ],
                    'order'                    => [
                        'type'    => 'string',
                        'default' => '',
                    ],
                    'not_logged_in_message'    => [
                        'type'    => 'string',
                        'default' => __('You are not logged in.', 'memberpress'),
                    ],
                    'no_subscriptions_message' => [
                        'type'    => 'string',
                        'default' => __('You have no Subscriptions yet.', 'memberpress'),
                    ],
                    'top_description'          => [
                        'type' => 'string',
                    ],
                    'bottom_description'       => [
                        'type' => 'string',
                    ],
                    'use_access_url'           => [
                        'type' => 'boolean',
                    ],
                ],
                'render_callback' => [$this, 'render_subscriptions_block'],
            ]
        );

        // Accounts Info.
        register_block_type(
            'memberpress/account-info',
            [
                'api_version'     => 2,
                'attributes'      => [
                    'field' => [
                        'type'    => 'string',
                        'default' => 'full_name',
                    ],
                ],
                'render_callback' => [$this, 'render_account_info'],
            ]
        );

        // ReadyLaunch Profile Menu.
        register_block_type(
            'memberpress/pro-profile-menu',
            [
                'api_version'     => 2,
                'supports'        => [
                    'color' => [
                        'text'       => true,
                        'background' => true,
                        'link'       => false,
                    ],
                    'align' => ['left', 'right'],
                ],
                'attributes'      => [
                    'dropdownBackgroundColor' => [
                        'type' => 'string',
                    ],
                    'dropdownTextColor'       => [
                        'type' => 'string',
                    ],
                ],
                'render_callback' => [$this, 'render_pro_profile_menu_block'],
                'style'           => 'mp-pro-profile-menu',
                'script'          => 'mp-pro-profile-menu',
            ]
        );

        // ReadyLaunch Thank You Block Shell - registered from block.json.
        register_block_type(
            MEPR_PATH . '/js/blocks/thank-you/block.json',
            [
                'render_callback' => [$this, 'render_thank_you_block'],
            ]
        );

        // Transaction Success Block - registered from block.json.
        register_block_type(
            MEPR_PATH . '/js/blocks/thank-you-success/block.json',
            [
                'render_callback' => [$this, 'render_transaction_success'],
            ]
        );

        register_block_type(
            MEPR_PATH . '/js/blocks/thank-you-details/block.json',
            [
                'render_callback' => [$this, 'render_details'],
            ]
        );

        register_block_type(
            MEPR_PATH . '/js/blocks/thank-you-image/block.json',
            [
                'render_callback' => [$this, 'render_image'],
            ]
        );

        register_block_type(
            MEPR_PATH . '/js/blocks/thank-you-failure/block.json',
            [
                'render_callback' => [$this, 'render_failure'],
            ]
        );

        register_block_type(
            MEPR_PATH . '/js/blocks/thank-you-order-value/block.json',
            [
                'render_callback' => [$this, 'render_thank_you_order_value'],
            ]
        );

        register_block_type(
            MEPR_PATH . '/js/blocks/thank-you-invoice/block.json',
            [
                'render_callback' => [$this, 'render_invoice_block'],
            ]
        );

        register_block_type(
            MEPR_PATH . '/js/blocks/thank-you-back-link/block.json',
            [
                'render_callback' => [$this, 'render_back_link'],
                'attributes'      => [
                    'text' => [
                        'type'    => 'string',
                        'default' => __('Back to home', 'memberpress'),
                    ],
                    'url'  => [
                        'type'    => 'string',
                        'default' => home_url('/'),
                    ],
                ],
            ]
        );

        register_block_type(
            MEPR_PATH . '/js/blocks/thank-you-icon/block.json',
            [
                'render_callback' => [$this, 'render_icon'],
            ]
        );
    }

    /**
     * Renders a membership's signup form
     *
     * @param array $props Properties/data from the block.
     *
     * @return string
     */
    public function render_membership_signup_block($props)
    {

        $membership_id = isset($props['membership']) ? (int) $props['membership'] : 0;

        if ($membership_id > 0) {
            ob_start();
            echo do_shortcode("[mepr_membership_registration_form id='{$membership_id}']");
            return ob_get_clean();
        }

        return _x('Uh oh, something went wrong. Not a valid Membership form.', 'ui', 'memberpress');
    }

    /**
     * Renders the MP account form
     *
     * @return string
     */
    public function render_account_block()
    {
        ob_start();
        echo do_shortcode('[mepr_account_form]');
        return ob_get_clean();
    }

    /**
     * Renders the MP login form
     *
     * @param array $props Properties/data from the block.
     *
     * @return string
     */
    public function render_login_block($props)
    {
        $shortcode = isset($props['use_redirect']) && true === $props['use_redirect'] ? "[mepr_login_form use_redirect='true']" : '[mepr_login_form]';
        ob_start();
        echo do_shortcode($shortcode);
        return ob_get_clean();
    }

    /**
     * Render the "dynamic" block
     *
     * @param array  $attributes Properties/data from the block.
     * @param string $content    Block content.
     *
     * @return string
     */
    public function render_protected_content_block($attributes, $content)
    {

        $attributes['ifallowed'] = ! empty($attributes['ifallowed']) ? $attributes['ifallowed'] : 'show';

        if (! isset($attributes['unauth_message']) || '' === $attributes['unauth_message']) {
            $attributes['unauth_message'] = __('You are unauthorized to view this content.', 'memberpress');
        }

        $content = MeprRulesCtrl::protect_shortcode_content($attributes, $content);

        return $content;
    }

    /**
     * Renders the MP login form
     *
     * @param array $atts The attributes.
     *
     * @return string
     */
    public function render_pro_login_block($atts)
    {
        wp_enqueue_style('mp-pro-login');

        $show_welcome_image = filter_var($atts['show_welcome_image'], FILTER_VALIDATE_BOOLEAN);

        $admin_view = isset($atts['admin_view']) ? filter_var($atts['admin_view'], FILTER_VALIDATE_BOOLEAN) : false;

        $welcome_image = isset($atts['welcome_image']) ?
            esc_url_raw($atts['welcome_image']) : '';

        $shortcode = "[mepr_pro_login_form
    show_welcome_image='$show_welcome_image'
    welcome_image='$welcome_image'
    admin_view='$admin_view']";

        ob_start();
        echo do_shortcode($shortcode);
        return ob_get_clean();
    }

    /**
     * Renders the MP login form
     *
     * @param array $atts The attributes.
     *
     * @return string
     */
    public function render_pro_pricing_block($atts)
    {
        wp_enqueue_style('mp-pro-pricing');

        $show_title = isset($atts['show_title']) &&
            filter_var($atts['show_title'], FILTER_VALIDATE_BOOLEAN) ?
            true : false;

        $button_highlight_color = isset($atts['button_highlight_color']) ?
            sanitize_text_field($atts['button_highlight_color']) : '';

        $group_id = isset($atts['group_id']) ?
            absint($atts['group_id']) : '';

        if (empty($group_id) && is_singular('memberpressgroup')) {
            $group_id = get_queried_object_id();
        }

        $shortcode = "[mepr_pro_pricing_table
        show_title='$show_title'
        button_highlight_color='$button_highlight_color'
        group_id='$group_id']";

        ob_start();
        echo do_shortcode($shortcode);
        return ob_get_clean();
    }


    /**
     * Renders the MP login form
     *
     * @param array $atts The attributes.
     *
     * @return string
     */
    public function render_pro_account_block($atts)
    {
        wp_enqueue_style('mp-pro-account');

        $show_welcome_image = isset($atts['show_welcome_image']) &&
            filter_var($atts['show_welcome_image'], FILTER_VALIDATE_BOOLEAN) ?
            true : false;

        $welcome_image = isset($atts['welcome_image']) ?
            esc_url_raw($atts['welcome_image']) : '';

        $shortcode = "[mepr_pro_account_tabs
        show_welcome_image='$show_welcome_image'
        welcome_image='$welcome_image']";

        ob_start();
        echo do_shortcode($shortcode);
        return ob_get_clean();
    }

    /**
     * Renders the MP login form
     *
     * @param array $atts The attributes.
     *
     * @return string
     */
    public function render_checkout_block($atts)
    {
        wp_enqueue_style('mp-pro-checkout');

        $membership_id = isset($atts['membership_id']) ?
            absint($atts['membership_id']) : '';

        if (empty($membership_id) && is_singular('memberpressproduct')) {
            $membership_id = get_queried_object_id();
        }

        $shortcode = "[mepr_pro_checkout membership_id='$membership_id']";
        ob_start();
        echo do_shortcode($shortcode);
        return ob_get_clean();
    }

    /**
     * Renders the MP account links
     *
     * @param array $atts Properties/data from the block.
     *
     * @return string
     */
    public function render_account_links_block(array $atts)
    {
        ob_start();
        $mepr_options = MeprOptions::fetch();
        if (MeprUtils::is_user_logged_in()) {
            $account_url = $mepr_options->account_page_url();
            $logout_url  = MeprUtils::logout_url();
            MeprView::render('/account/logged_in_widget', get_defined_vars());
        } else {
            $login_url = MeprUtils::login_url();
            MeprView::render('/account/logged_out_widget', get_defined_vars());
        }
        return ob_get_clean();
    }

    /**
     * Renders the MP subscriptions
     *
     * @param array $atts Properties/data from the block.
     *
     * @return string
     */
    public function render_subscriptions_block(array $atts)
    {
        ob_start();
        $user         = MeprUtils::get_currentuserinfo();
        $mepr_options = MeprOptions::fetch();

        $order_by                 = isset($atts['order_by']) ?
            sanitize_text_field($atts['order_by']) : '';
        $order                    = isset($atts['order']) ?
            sanitize_text_field($atts['order']) : '';
        $not_logged_in_message    = isset($atts['not_logged_in_message']) ?
            sanitize_text_field($atts['not_logged_in_message']) : '';
        $no_subscriptions_message = isset($atts['no_subscriptions_message']) ?
            sanitize_text_field($atts['no_subscriptions_message']) : '';
        $top_desc                 = isset($atts['top_description']) ?
            sanitize_text_field($atts['top_description']) : '';
        $bottom_desc              = isset($atts['bottom_description']) ?
            sanitize_text_field($atts['bottom_description']) : '';
        $use_access_url           = isset($atts['use_access_url']) &&
            filter_var($atts['use_access_url'], FILTER_VALIDATE_BOOLEAN) ?
            true : false;

        MeprView::render('/account/subscriptions_widget', get_defined_vars());
        return ob_get_clean();
    }

    /**
     * Renders the MP account info
     *
     * @param array $props Properties/data from the block.
     *
     * @return string
     */
    public function render_account_info(array $props)
    {
        $shortcode = isset($props['field'])
            ? '[mepr_account_info field="' . sanitize_text_field($props['field']) . '"]'
            : '[mepr_account_info field="full_name"]';
        ob_start();
        echo '<p>' . do_shortcode($shortcode) . '</p>';
        return ob_get_clean();
    }

    /**
     * Renders the MP ReadyLaunch Profile Menu
     *
     * @param array $atts Properties/data from the block.
     *
     * @return string
     */
    public function render_pro_profile_menu_block(array $atts)
    {
        ob_start();
        $user         = MeprUtils::get_currentuserinfo();
        $mepr_options = MeprOptions::fetch();

        if ($user) {
            $account_url         = $mepr_options->account_page_url();
            $change_password_url = $mepr_options->account_page_url('password');
            $logout_url          = MeprUtils::logout_url();

            $dropdown_bg   = isset($atts['dropdownBackgroundColor']) ? esc_attr($atts['dropdownBackgroundColor']) : '';
            $dropdown_text = isset($atts['dropdownTextColor']) ? esc_attr($atts['dropdownTextColor']) : '';

            $style = '';
            if (!empty($dropdown_bg)) {
                $style .= "--mp-dropdown-bg: {$dropdown_bg};";
            }
            if (!empty($dropdown_text)) {
                $style .= " --mp-dropdown-text: {$dropdown_text};";
            }

            $wrapper_attributes = get_block_wrapper_attributes([
                'style' => trim($style),
            ]);

            MeprView::render('/readylaunch/blocks/profile-menu', get_defined_vars());
        }


        return ob_get_clean();
    }

    /**
     * Renders the ReadyLaunch account wrapper block (frontend).
     *
     * @param array  $atts    Block attributes.
     * @param string $content Inner block content.
     *
     * @return string
     */
    public function render_readylaunch_account_block($atts, $content)
    {
        wp_enqueue_style('mp-pro-account');
        wp_enqueue_style('mp-readylaunch-shell');
        wp_enqueue_style('mp-readylaunch-account');

        $layout       = isset($atts['layout']) ? sanitize_text_field($atts['layout']) : 'vertical';
        $layout_class = esc_attr($layout);
        $wrapper      = get_block_wrapper_attributes([
            // Emit both the canonical shell classes (for the shared layout in shell.css)
            // and the Account-specific classes (for any addon-scoped overrides). Match
            // classic pro-account-tabs block: wide alignment / readable line width.
            'class' => 'mepr-rl-shell mepr-rl-shell--' . $layout_class
                . ' mepr-rl-account mepr-rl-account--' . $layout_class
                . ' alignwide wp-block',
        ]);

        return sprintf(
            '<div %s>%s</div>',
            $wrapper,
            $content
        );
    }

    /**
     * Renders the ReadyLaunch account menu block (frontend).
     *
     * @param array $atts Block attributes.
     *
     * @return string
     */
    public function render_readylaunch_account_menu_block($atts)
    {
        if (!MeprUtils::is_user_logged_in()) {
            return '';
        }

        ob_start();

        // Ensure the global $post points to the account page so that
        // add-on hooks (e.g. Directory) that rely on it don't fatal.
        global $post;
        $original_post = $post;

        try {
            wp_enqueue_style('mp-pro-account');
            wp_enqueue_style('mp-readylaunch-shell');
            wp_enqueue_style('mp-readylaunch-account');

            add_filter('mepr_pro_templates_has_account_block', '__return_true');

            $mepr_options    = MeprOptions::fetch();
            $account_page_id = isset($mepr_options->account_page_id) ? (int) $mepr_options->account_page_id : 0;

            if ($account_page_id > 0) {
                $post = get_post($account_page_id); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- temporary account page context for add-on hooks
            }

            $layout = isset($atts['layout']) ? sanitize_text_field($atts['layout']) : 'vertical';

            // Emit canonical shell variables (consumed by shell.css) and the
            // Account-specific variables in parallel. shell.css does not depend
            // on the addon-specific names; they remain available as customizer
            // hooks for site-specific overrides scoped to `.mepr-rl-account`.
            $color_map = [
                'menuBackgroundColor'   => ['--mepr-rl-shell-menu-bg', '--mepr-account-menu-bg'],
                'buttonBackgroundColor' => ['--mepr-rl-shell-button-bg', '--mepr-account-button-bg'],
                'buttonHoverBgColor'    => ['--mepr-rl-shell-button-hover-bg', '--mepr-account-button-hover-bg'],
                'buttonTextColor'       => ['--mepr-rl-shell-button-text', '--mepr-account-button-text'],
                'buttonHoverTextColor'  => ['--mepr-rl-shell-button-hover-text', '--mepr-account-button-hover-text'],
            ];

            $nav_style = '';
            foreach ($color_map as $attr_key => $css_vars) {
                if (! empty($atts[ $attr_key ])) {
                    $color = $this->sanitize_readylaunch_account_menu_color($atts[ $attr_key ]);
                    if ($color !== '') {
                        foreach ($css_vars as $css_var) {
                            $nav_style .= "{$css_var}:{$color};";
                        }
                    }
                }
            }

            /**
             * Account controller used to render the nav markup.
             *
             * @var MeprAccountCtrl $acct_ctrl
             */
            $acct_ctrl = MeprCtrlFactory::fetch('account');

            $html = $acct_ctrl->render_nav_html([
                'layout'    => $layout,
                'nav_style' => $nav_style,
            ]);
        } catch (\Throwable $e) {
            $html = '';
            MeprUtils::debug_log('ReadyLaunch account menu render error: ' . $e->getMessage(), [$e]);
        } finally {
            $post = $original_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
        }

        ob_end_clean();

        if (empty($html)) {
            return '';
        }

        return $html;
    }

    /**
     * Renders the ReadyLaunch account content block (frontend).
     *
     * @param array $atts Block attributes.
     *
     * @return string
     */
    public function render_readylaunch_account_content_block($atts)
    {
        if (!MeprUtils::is_user_logged_in()) {
            global $post;
            return '<div id="mepr-account-content" class="mepr-rl-shell__content mp_wrapper mepr-account-container">'
                . do_shortcode(MeprRulesCtrl::unauthorized_message($post))
                . '</div>';
        }

        wp_enqueue_style('mp-pro-account');
        wp_enqueue_style('mp-readylaunch-shell');
        wp_enqueue_style('mp-readylaunch-account');
        wp_enqueue_script('mepr-accountjs', MEPR_JS_URL . '/readylaunch/account.js', ['jquery'], MEPR_VERSION, true);
        wp_localize_script('mepr-accountjs', 'MeprAccount', [
            'navSelector' => '#mepr-account-nav',
        ]);

        add_filter('mepr_pro_templates_has_account_block', '__return_true');

        /**
         * Account controller used to render the account content area.
         *
         * @var MeprAccountCtrl $acct_ctrl
         */
        $acct_ctrl = MeprCtrlFactory::fetch('account');

        $html = $acct_ctrl->render_content_html($atts);

        return '<div id="mepr-account-content" class="mepr-rl-shell__content mp_wrapper mepr-account-container">' . $html . '</div>';
    }

    /**
     * Builds the nav items array for the editor preview.
     *
     * @return array
     */
    private function get_account_nav_items_for_editor()
    {
        $account_url = MeprUtils::get_account_url();
        $delim       = MeprAppCtrl::get_param_delimiter_char($account_url);

        $items = [
            [
                'label' => MeprHooks::apply_filters('mepr_account_nav_home_label', _x('My Profile', 'ui', 'memberpress')),
                'icon'  => 'home',
                'slug'  => 'home',
            ],
            [
                'label' => MeprHooks::apply_filters('mepr_account_nav_payments_label', _x('Payments', 'ui', 'memberpress')),
                'icon'  => 'payments',
                'slug'  => 'payments',
            ],
            [
                'label' => MeprHooks::apply_filters('mepr_account_nav_subscriptions_label', _x('Subscriptions', 'ui', 'memberpress')),
                'icon'  => 'subscriptions',
                'slug'  => 'subscriptions',
            ],
        ];

        $items = MeprHooks::apply_filters('mepr_account_nav_items_editor', $items);

        $items[] = [
            'label' => MeprHooks::apply_filters('mepr_account_nav_logout_label', _x('Logout', 'ui', 'memberpress')),
            'icon'  => 'logout',
            'slug'  => 'logout',
        ];

        return $items;
    }

    /**
     * Enqueue the necessary scripts/styles in the editor
     *
     * @return void
     */
    public function enqueue_editor_block_scripts()
    {
        $asset_file = include MEPR_JS_PATH . '/build/blocks.asset.php';

        $dependencies = array_unique(
            array_merge(
                [
                    'wp-blocks',
                    'wp-i18n',
                    'wp-editor',
                ], // Legacy dependencies.
                (array) $asset_file['dependencies']
            )
        );

        wp_enqueue_script(
            'memberpress/blocks',
            MEPR_JS_URL . '/build/blocks.js',
            $dependencies,
            $asset_file['version'],
            true
        );

        $membership_options = [];
        $rule_options       = [];

        // Assemble MP Products into an options array.
        foreach (MeprCptModel::all('MeprProduct') as $membership) {
            $membership_options[] = [
                'label' => $membership->post_title,
                'value' => $membership->ID,
            ];
        }

        // Assemble MP Rules into an options array.
        foreach (MeprCptModel::all('MeprRule') as $rule) {
            $rule_options[] = [
                'label'    => $rule->post_title,
                'value'    => $rule->ID,
                'ruleLink' => get_edit_post_link($rule->ID, '&'),
            ];
        }

        // Assemble MP Groups into an options array.
        $groups = [];
        foreach (MeprCptModel::all('MeprGroup') as $group) {
            $groups[] = [
                'label' => $group->post_title,
                'value' => $group->ID,
            ];
        }

        // Assemble custom fields into an options array.
        $mepr_options  = MeprOptions::fetch();
        $custom_fields = [];
        if (!empty($mepr_options->custom_fields)) {
            foreach ($mepr_options->custom_fields as $field) {
                $custom_fields[] = [
                    'label' => $field->field_key,
                    'value' => $field->field_key,
                ];
            }
        }

        // Build nav items for the ReadyLaunch account menu editor preview.
        $account_nav_items = $this->get_account_nav_items_for_editor();

        // Make the data available to the script.
        wp_localize_script(
            'memberpress/blocks',
            'memberpressBlocks',
            [
                'memberships'              => $membership_options,
                'rules'                    => $rule_options,
                'groups'                   => $groups,
                'custom_fields'            => $custom_fields,
                'redirect_url_setting_url' => menu_page_url('memberpress-options', false) . '#mepr-accounts',
                'disabled_blocks'          => MeprHooks::apply_filters('mepr_disabled_blocks', []),
                'block_protection'         => MeprHooks::apply_filters('mepr_block_protection_enabled', true),
                'block_protection_exclude' => MeprHooks::apply_filters(
                    'mepr_block_protection_exclude',
                    ['memberpress/protected-content']
                ),
                'showThankyouWelcomeImage' => (bool) $mepr_options->design_show_thankyou_welcome_image,
                'thankyouWelcomeImageId'   => (int) $mepr_options->design_thankyou_welcome_img,
                'thankyouWelcomeImageUrl'  => wp_get_attachment_url($mepr_options->design_thankyou_welcome_img),
                'showCustomMessage'        => (bool) $mepr_options->design_thankyou_hide_invoice,
                'customMessage'            => $mepr_options->design_thankyou_invoice_message,
                'homeUrl'                  => home_url('/'),
            ]
        );

        wp_enqueue_style('mp-theme', MEPR_CSS_URL . '/ui/theme.css', null, MEPR_VERSION);
    }

    /**
     * Inline ui/theme.css into the block editor canvas (Site Editor iframe).
     *
     * The block's `editor_style` (mp-pro-checkout → mp-signup) already reaches the
     * canvas, but ui/theme.css — the base .mp_wrapper field styling — is only
     * enqueued for the outer editor frame. Adding it to the editor `styles`
     * setting inlines it into the iframe so the previewed form fields are styled;
     * the baseURL keeps its relative url() references resolving. Editor-only.
     *
     * @param array $settings The block editor settings.
     *
     * @return array
     */
    public function add_checkout_block_editor_styles($settings)
    {
        $path = MEPR_CSS_PATH . '/ui/theme.css';

        if (is_readable($path)) {
            $settings['styles'][] = [
                'css'     => file_get_contents($path),
                'baseURL' => MEPR_CSS_URL . '/ui/theme.css',
            ];
        }

        return $settings;
    }

    /**
     * Enqueue the necessary scripts / styles for each block
     *
     * @return void
     */
    public function enqueue_block_scripts()
    {

        // Register account scripts.
        wp_register_style('mp-pro-fonts', MEPR_CSS_URL . '/readylaunch/fonts.css', null, MEPR_VERSION);
        wp_register_style('mp-pro-login', MEPR_CSS_URL . '/readylaunch/login.css', null, MEPR_VERSION);
        wp_register_style('mp-pro-account', MEPR_CSS_URL . '/readylaunch/account.css', ['mp-pro-fonts', 'mp-pro-login'], MEPR_VERSION);

        // Register ReadyLaunch shell — shared layout for sidebar-style FSE templates
        // (Account, Coaching, …). Addons opt in via the `mepr-rl-shell` class convention.
        wp_register_style('mp-readylaunch-shell', MEPR_CSS_URL . '/readylaunch/shell.css', [], MEPR_VERSION);

        // Register ReadyLaunch account layout styles. Depends on shell.css so the
        // shared layout rules are loaded first; account.css now only carries
        // Account-specific bits that remain after the shell extraction.
        wp_register_style('mp-readylaunch-account', MEPR_CSS_URL . '/readylaunch/readylaunch-account.css', ['mp-pro-account', 'mp-readylaunch-shell'], MEPR_VERSION);

        // Register pricing scripts.
        wp_register_style('mp-pro-pricing', MEPR_CSS_URL . '/readylaunch/pricing.css', null, MEPR_VERSION);

        // Register checkout scripts.
        $prereqs = MeprHooks::apply_filters('mepr_signup_styles', []);
        wp_register_style('mp-signup', MEPR_CSS_URL . '/signup.css', $prereqs, MEPR_VERSION);
        wp_register_style('mp-pro-checkout', MEPR_CSS_URL . '/readylaunch/checkout.css', ['mp-signup'], MEPR_VERSION);

        // Register profile menu scripts.
        wp_register_style('mp-pro-profile-menu', MEPR_CSS_URL . '/readylaunch/profile-menu.css', null, MEPR_VERSION);
        wp_register_script('mp-pro-profile-menu', MEPR_JS_URL . '/blocks/pro-profile-menu/frontend.js', [], MEPR_VERSION, true);
    }

    /**
     * Filter to add the necessary frontend enqueues for Membership Signup block
     *
     * @param mixed  $return MeprProduct object if scripts will be enqueued, else false.
     * @param object $post   WP_Post.
     *
     * @return boolean
     */
    public function signup_block_enqueues($return, $post)
    {

        if (! isset($post->post_content)) {
            return $return;
        }

        // We don't want to mess with enqueues on MemberPress products since the files are already properly enqueued there.
        if (! is_object($return) || ! is_a($return, 'MeprProduct')) {
            $membership = false;

            // Check that the signup form block is added.
            $match = preg_match('/(?:wp:memberpress\/membership-signup\s)(\{(?:[^{}]|(?R))*\})/', $post->post_content, $matches);

            if (1 === $match && isset($matches[1]) && isset(json_decode($matches[1], true)['membership'])) {
                $membership = new MeprProduct(json_decode($matches[1], true)['membership']);
            } elseif (
                preg_match(
                    '~(?:wp:memberpress\/checkout\s)+{\"membership_id\"\:[\"\\\'](\d+)[\"\\\']~',
                    $post->post_content,
                    $m
                )
            ) {
                $membership = new MeprProduct($m[1]);
            }

            // Valid membership.
            if (isset($membership->ID) && $membership->ID > 0) {
                $return = $membership; // Return the MeprProduct instead of just boolean true (backward compatibility).
            }
        }

        return $return;
    }

    /**
     * Filter to add the necessary frontend enqueues for the Account Form block
     *
     * @param boolean $return Whether the page is an "Account" page.
     * @param object  $post   WP_Post.
     *
     * @return boolean
     */
    public function account_block_enqueues($return, $post)
    {

        if (! isset($post->post_content)) {
            return $return;
        }

        // Post is an "Account" page if it has the Account Form block or ReadyLaunch account blocks.
        if (
            has_block('memberpress/account-form', $post) ||
            has_block('memberpress/readylaunch-account', $post) ||
            MeprAppHelper::block_template_has_block('account-form') ||
            MeprAppHelper::block_template_has_block('readylaunch-account')
        ) {
            $return = true;
        }

        return $return;
    }

    /**
     * Add protection attributes to blocks.
     *
     * @param  array $args Array of arguments for registering a block type.
     * @return array
     */
    public function add_protection_attributes($args)
    {
        $args['attributes']['mepr_protection_rule'] = [
            'type'    => 'number',
            'default' => 0,
        ];

        $args['attributes']['mepr_protection_ifallowed'] = [
            'type'    => 'string',
            'default' => 'show',
        ];

        $args['attributes']['mepr_protection_unauth'] = [
            'type'    => 'string',
            'default' => 'default',
        ];

        $args['attributes']['mepr_protection_unauth_message'] = [
            'type'    => 'string',
            'default' => '',
        ];

        return $args;
    }

    /**
     * Applies content protection to a given block based on defined rules and attributes.
     *
     * This method evaluates the protection rule assigned to a block and modifies its content accordingly.
     * If the user does not have the required permissions, it will handle unauthorized access by showing
     * a default or custom message, or applying the specified settings for unauthorized users.
     *
     * @param  string $block_content The original content of the block.
     * @param  array  $block         An associative array containing block attributes, including protection rules and settings.
     * @return string The modified block content if a rule is applied, or the original content if no rule is set.
     */
    public function block_content_protection($block_content, $block)
    {
        // Skip if no protection rule is set.
        if (empty($block['attrs']['mepr_protection_rule'])) {
            return $block_content;
        }

        $attributes = [
            'rule'      => $block['attrs']['mepr_protection_rule'],
            'ifallowed' => !empty($block['attrs']['mepr_protection_ifallowed']) ? $block['attrs']['mepr_protection_ifallowed'] : 'show',
            'unauth'    => !empty($block['attrs']['mepr_protection_unauth']) ? $block['attrs']['mepr_protection_unauth'] : 'hide',
        ];

        $rule = new MeprRule($attributes['rule']);
        if (!($rule->ID > 0)) {
            return '<div class="mepr_block_error">' . esc_html__('Invalid rule', 'memberpress') . '</div>';
        }

        $unauth_message = '';

        if ($attributes['unauth'] === 'default') {
            $global_settings = MeprRule::get_global_unauth_settings();
            $post            = MeprUtils::get_current_post();
            $post_settings   = $post instanceof WP_Post ? MeprRule::get_post_unauth_settings($post) : null;

            if (is_object($post_settings) && $post_settings->unauth_message_type !== 'default') {
                $unauth_message = $post_settings->unauth_message;
            } elseif ($rule->unauth_message_type !== 'default') {
                $unauth_message = $rule->unauth_message;
            } else {
                $unauth_message = $global_settings->message;
            }
        } elseif ($attributes['unauth'] === 'message' && !empty($block['attrs']['mepr_protection_unauth_message'])) {
            $unauth_message = $block['attrs']['mepr_protection_unauth_message'];
        }

        if (!empty($unauth_message)) {
            $attributes['unauth']         = 'message';
            $attributes['unauth_message'] = do_shortcode(wp_kses_post($unauth_message));
        }

        return MeprRulesCtrl::protect_shortcode_content($attributes, $block_content);
    }

    /**
     * Render thank you block.
     *
     * @param  array  $attributes Block attributes.
     * @param  string $content    Block content.
     * @return string
     */
    public function render_thank_you_block($attributes, $content)
    {
        // Membership-specific override message.
        $message = MeprProductsCtrl::maybe_get_thank_you_page_message();
        if (! empty($message)) {
            return $message;
        }

        $wrapper_attributes = get_block_wrapper_attributes([
            'class' => 'mepr-thank-you-wrapper',
        ]);

        return sprintf(
            '<div %1$s>%2$s</div>',
            $wrapper_attributes,
            $content
        );
    }

    /**
     * Render Transaction Success layout block.
     *
     * @param  array  $attributes Block attributes.
     * @param  string $content    Block content.
     * @return string
     */
    public function render_transaction_success($attributes, $content)
    {
        $txn = MeprTransactionsHelper::get_thankyou_transaction();
        if (!$txn) {
            return '';
        }

        // Determine if welcome image should show: use block attribute if set, otherwise fall back to global setting.
        $mepr_options = MeprOptions::fetch();
        $show_image   = $attributes['showImage'] ?? $mepr_options->design_show_thankyou_welcome_image;

        // Add a CSS class to control welcome image visibility.
        $classes = ['mepr-success'];

        // Hide image block if showImage is false OR if content doesn't contain an actual image.
        if (!$show_image || strpos($content, '<img') === false) {
            $classes[] = 'mepr-hide-welcome-image';
        }

        $wrapper_attributes = get_block_wrapper_attributes(['class' => implode(' ', $classes)]);
        return sprintf('<div %1$s>%2$s</div>', $wrapper_attributes, $content);
    }

    /**
     * Render Details block with legacy message override.
     *
     * @param  array  $attributes Block attributes.
     * @param  string $content    Block content.
     * @return string
     */
    public function render_details($attributes, $content)
    {
        // Check if content is empty (no saved blocks yet).
        $is_empty = empty(trim(wp_strip_all_tags((string) $content)));

        // Server-side fallback for when the page has never been saved in the editor.
        // Keep in sync with TEMPLATE and TEMPLATE_WITH_CUSTOM in js/blocks/thank-you-details/index.js.
        if ($is_empty) {
            $mepr_options = MeprOptions::fetch();

            // Check if custom message should be shown (matches JS logic).
            $show_custom_message = $mepr_options->design_thankyou_hide_invoice;
            $custom_message      = $show_custom_message ? do_shortcode($mepr_options->design_thankyou_invoice_message) : '';

            // Generate appropriate template based on setting.
            if ($show_custom_message) {
                // TEMPLATE_WITH_CUSTOM (matches JS).
                $content =
                    '<!-- wp:heading {"textAlign":"center","level":1} -->' .
                    '<h1 class="wp-block-heading has-text-align-center">' . __('Thank you for your purchase', 'memberpress') . '</h1>' .
                    '<!-- /wp:heading -->' .
                    '<!-- wp:paragraph {"align":"center","className":"mepr-custom-message"} -->' .
                    '<p class="has-text-align-center mepr-custom-message">' . $custom_message . '</p>' .
                    '<!-- /wp:paragraph -->' .
                    '<!-- wp:memberpress/thank-you-back-link /-->';
            } else {
                // TEMPLATE (matches JS) - Full invoice.
                $content =
                    '<!-- wp:heading {"textAlign":"center","level":1} -->' .
                    '<h1 class="wp-block-heading has-text-align-center">' . __('Thank you for your purchase', 'memberpress') . '</h1>' .
                    '<!-- /wp:heading -->' .
                    '<!-- wp:memberpress/thank-you-icon /-->' .
                    '<!-- wp:group {"className":"mepr-order-no"} -->' .
                    '<div class="wp-block-group mepr-order-no">' .
                    '<!-- wp:paragraph {"align":"center"} -->' .
                    '<p class="has-text-align-center">' . __('Payment Successful', 'memberpress') . '</p>' .
                    '<!-- /wp:paragraph -->' .
                    '<!-- wp:memberpress/thank-you-order-value {"field":"order_number","label":"Order:","style":{"typography":{"textAlign":"center"}}} /-->' .
                    '</div>' .
                    '<!-- /wp:group -->' .
                    '<!-- wp:memberpress/thank-you-order-value {"field":"amount","className":"invoice-amount"} /-->' .
                    '<!-- wp:memberpress/thank-you-invoice /-->' .
                    '<!-- wp:memberpress/thank-you-back-link /-->';
            }

            $content = do_blocks($content);
        }

        $wrapper_attributes = get_block_wrapper_attributes(['class' => 'mepr-details']);
        return sprintf('<div %s>%s</div>', $wrapper_attributes, $content);
    }

    /**
     * Render Image block based on legacy settings.
     *
     * @param  array $attributes Block attributes.
     * @return string
     */
    public function render_image($attributes)
    {
        // Check if mediaId is explicitly set (including 0 for "removed").
        $media_id_is_set = array_key_exists('mediaId', $attributes);
        $media_id        = $media_id_is_set ? (int) $attributes['mediaId'] : null;
        $image_url       = '';

        if ($media_id_is_set) {
            // MediaId was explicitly set by the user.
            if ($media_id > 0) {
                // User selected a custom image.
                $image_url = wp_get_attachment_url($media_id);
            }
            // If $media_id === 0, user explicitly removed the image, so don't fall back to global.
        } else {
            // MediaId not set, use global settings.
            $mepr_options = MeprOptions::fetch();
            if ($mepr_options->design_show_thankyou_welcome_image) {
                $image_url = wp_get_attachment_url($mepr_options->design_thankyou_welcome_img);
            }
        }

        if (!$image_url) {
            return is_admin() ? '<div class="mepr-image-placeholder">' . __('No image selected', 'memberpress') . '</div>' : '';
        }

        $wrapper_attributes = get_block_wrapper_attributes(['class' => 'mepr-image']);
        return sprintf('<div %1$s><img src="%2$s" alt="" /></div>', $wrapper_attributes, esc_url($image_url));
    }

    /**
     * Render Failure block (no transaction).
     *
     * @param  array  $attributes Block attributes.
     * @param  string $content    Block content.
     * @return string
     */
    public function render_failure($attributes, $content)
    {
        $txn = MeprTransactionsHelper::get_thankyou_transaction();
        if ($txn) {
            return '';
        }

        $wrapper_attributes = get_block_wrapper_attributes(['class' => 'mepr-failure']);
        return sprintf('<div %s>%s</div>', $wrapper_attributes, $content);
    }

    /**
     * Render Dynamic Transaction Order Value fields.
     *
     * @param  array $attributes Block attributes.
     * @return string
     */
    public function render_thank_you_order_value($attributes)
    {
        $txn = MeprTransactionsHelper::get_thankyou_transaction();
        if (!$txn) {
            return '';
        }

        $field = isset($attributes['field']) ? $attributes['field'] : 'order_number';
        $label = isset($attributes['label']) ? $attributes['label'] : '';
        $data  = $this->get_thank_you_data($txn);

        $output = '';
        switch ($field) {
            case 'order_number':
                $output = $data['trans_num'];
                break;
            case 'amount':
                $output = $data['amount'];
                break;
            case 'date':
                $output = wp_date(get_option('date_format'), strtotime($txn->created_at));
                break;
        }

        $content = '';
        if (!empty($label)) {
            $content .= '<span class="mepr-order-value-label">' . esc_html($label) . ' </span>';
        }
        $content .= '<span class="mepr-order-value-data">' . esc_html($output) . '</span>';

        $wrapper_attributes = get_block_wrapper_attributes(['class' => 'mepr-thank-you-order-value mepr-val-' . esc_attr($field)]);
        return sprintf('<div %s>%s</div>', $wrapper_attributes, $content);
    }

    /**
     * Render Invoice Table block.
     *
     * @param  array $attributes Block attributes.
     * @return string
     */
    public function render_invoice_block($attributes)
    {
        $txn = MeprTransactionsHelper::get_thankyou_transaction();
        if (!$txn) {
            return '';
        }

        $data               = $this->get_thank_you_data($txn);
        $wrapper_attributes = get_block_wrapper_attributes(['class' => 'invoice-wrapper']);
        return sprintf('<div %s>%s</div>', $wrapper_attributes, $data['invoice_html']);
    }

    /**
     * Helper to prepare data for thank you details.
     *
     * @param  MeprTransaction $txn The transaction object.
     * @return array
     */
    private function get_thank_you_data(MeprTransaction $txn)
    {
        $mepr_options    = MeprOptions::fetch();
        $hide_invoice    = $mepr_options->design_thankyou_hide_invoice;
        $invoice_message = do_shortcode($mepr_options->design_thankyou_invoice_message);

        $order        = $txn->order();
        $amount       = 0.00;
        $invoice_html = '';
        $trans_num    = '';

        if ($order instanceof MeprOrder) {
            $order_bump_transactions = MeprTransaction::get_all_by_order_id_and_gateway($order->id, $txn->gateway, $txn->id);
            $transactions            = array_merge([$txn], $order_bump_transactions);
            $processed_sub_ids       = [];
            $order_bumps             = [];

            foreach ($transactions as $index => $transaction) {
                if ($transaction->is_one_time_payment()) {
                    $amount += (float) $transaction->total;

                    if ($index > 0) {
                        $order_bumps[] = [$transaction->product(), $transaction, null];
                    }
                } else {
                    $subscription = $transaction->subscription();

                    if ($subscription instanceof MeprSubscription) {
                        if (in_array((int) $subscription->id, $processed_sub_ids, true)) {
                            continue;
                        }

                        $processed_sub_ids[] = (int) $subscription->id;

                        if (($subscription->trial && $subscription->trial_days > 0 && $subscription->txn_count < 1) || $transaction->txn_type === MeprTransaction::$subscription_confirmation_str) {
                            MeprTransactionsHelper::set_invoice_txn_vars_from_sub($transaction, $subscription);
                        }

                        $amount += (float) $transaction->total;

                        if ($index > 0) {
                            $order_bumps[] = [$transaction->product(), $transaction, $subscription];
                        }
                    }
                }
            }

            $amount       = MeprAppHelper::format_currency($amount);
            $trans_num    = $order->trans_num;
            $invoice_html = MeprTransactionsHelper::get_invoice_order_bumps($txn, '', $order_bumps);
        } else {
            $sub = $txn->subscription();

            if ($sub instanceof MeprSubscription && (($sub->trial && $sub->trial_days > 0 && $sub->txn_count < 1) || $txn->txn_type === MeprTransaction::$subscription_confirmation_str)) {
                MeprTransactionsHelper::set_invoice_txn_vars_from_sub($txn, $sub);
            }

            // Strip the trailing period text (e.g. " / Month") with strtok() so the block shows just the price.
            $amount       = strtok(MeprAppHelper::format_price_string($txn, $txn->amount), ' ');
            $trans_num    = $txn->trans_num;
            $invoice_html = MeprTransactionsHelper::get_invoice($txn);
        }

        return [
            'invoice_message' => $invoice_message,
            'hide_invoice'    => $hide_invoice,
            'amount'          => $amount,
            'trans_num'       => $trans_num,
            'invoice_html'    => $invoice_html,
        ];
    }

    /**
     * Render Back to Home link block.
     *
     * @param  array $attributes Block attributes.
     * @return string
     */
    public function render_back_link($attributes)
    {
        $text = isset($attributes['text']) ? esc_html($attributes['text']) : __('Back to home', 'memberpress');
        $url  = isset($attributes['url']) && !empty($attributes['url']) ? esc_url($attributes['url']) : home_url('/');

        $wrapper_attributes = get_block_wrapper_attributes(['class' => 'mepr-back-link has-text-align-center']);

        return sprintf(
            '<p %s><a href="%s">%s</a></p>',
            $wrapper_attributes,
            $url,
            $text
        );
    }

    /**
     * Render thank you icon block.
     *
     * @param  array $attributes Block attributes.
     * @return string
     */
    public function render_icon($attributes)
    {

        $svg = $attributes['svgContent'] ?? '';

        if (trim($svg) === '') {
            // Fallback to the same default SVG as in block.json.
            $svg = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="thankyou" style=""><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>';
        }

        $wrapper_attributes = get_block_wrapper_attributes([
            'class' => 'mepr-icon',
        ]);

        return sprintf(
            '<div %s>%s</div>',
            $wrapper_attributes,
            $this->sanitize_svg_for_output($svg)
        );
    }

    /**
     * Sanitize a color for ReadyLaunch account menu CSS custom properties.
     *
     * Accepts hex (picker), theme palette variables, and rgb/rgba from the editor.
     *
     * @param  mixed $value Raw block attribute value.
     * @return string
     */
    private function sanitize_readylaunch_account_menu_color($value)
    {
        if (! is_string($value)) {
            return '';
        }

        $value = trim($value);
        if ($value === '') {
            return '';
        }

        $hex = sanitize_hex_color($value);
        if ($hex) {
            return $hex;
        }

        // Block / theme palette, e.g. var(--wp--preset--color--vivid-cyan-blue).
        if (preg_match('/^var\(\s*--wp--preset--color--([a-z0-9_-]+)\s*\)$/i', $value, $matches)) {
            return 'var(--wp--preset--color--' . strtolower($matches[1]) . ')';
        }

        // Color picker may emit functional notation; disallow injection (no parens/semicolons inside).
        if (preg_match('/^rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(,\s*(?:\d*\.?\d+|\d{1,3}%)\s*)?\)$/i', $value)) {
            return $value;
        }

        return '';
    }

    /**
     * Sanitize SVG for output.
     *
     * @param  string $svg SVG content.
     * @return string
     */
    private function sanitize_svg_for_output($svg)
    {
        // Presentation attributes shared by every allowed element — icon libraries (Heroicons,
        // Feather, Lucide) set stroke/fill/transform on the shapes, not just on <svg>.
        $common = [
            'fill'              => true,
            'fill-rule'         => true,
            'fill-opacity'      => true,
            'stroke'            => true,
            'stroke-width'      => true,
            'stroke-linecap'    => true,
            'stroke-linejoin'   => true,
            'stroke-miterlimit' => true,
            'stroke-dasharray'  => true,
            'stroke-dashoffset' => true,
            'stroke-opacity'    => true,
            'clip-rule'         => true,
            'opacity'           => true,
            'color'             => true,
            'transform'         => true,
            'class'             => true,
            'style'             => true,
            'id'                => true,
            'aria-hidden'       => true,
            'role'              => true,
            'focusable'         => true,
        ];

        $allowed = [
            'svg'      => array_merge($common, [
                'xmlns'               => true,
                'viewbox'             => true,
                'width'               => true,
                'height'              => true,
                'preserveaspectratio' => true,
            ]),
            'g'        => $common,
            'path'     => array_merge($common, ['d' => true]),
            'circle'   => array_merge($common, [
                'cx' => true,
                'cy' => true,
                'r'  => true,
            ]),
            'ellipse'  => array_merge($common, [
                'cx' => true,
                'cy' => true,
                'rx' => true,
                'ry' => true,
            ]),
            'line'     => array_merge($common, [
                'x1' => true,
                'y1' => true,
                'x2' => true,
                'y2' => true,
            ]),
            'polyline' => array_merge($common, ['points' => true]),
            'polygon'  => array_merge($common, ['points' => true]),
            'rect'     => array_merge($common, [
                'x'      => true,
                'y'      => true,
                'width'  => true,
                'height' => true,
                'rx'     => true,
                'ry'     => true,
            ]),
            'title'    => ['id' => true],
            'desc'     => ['id' => true],
        ];

        return wp_kses($svg, $allowed);
    }
}
