<?php
/**
 * RankForge — Schema Builder renderer.
 *
 * Extracted from class-analyzer.php (F14 — analyzer decomposition, per
 * forge-meta/DECOMPOSITION_PLAN.md). The 580-line render_schema_builder
 * method was the biggest single block in the 1837-line analyzer file;
 * moving it out makes the remaining scoring + metabox code easier to
 * navigate and paves the way for scoring → its own class in a follow-up.
 *
 * Pure output. Reads post meta and delegates to RANKFORGE_Schema /
 * RANKFORGE_AI_Client. No scoring state. Called from
 * RANKFORGE_Analyzer::render_meta_box (thin delegator).
 */

if ( ! defined( 'ABSPATH' ) ) { exit; }

// phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
if (!class_exists('RANKFORGE_Schema_Builder_View')) {

// The original guard lived here; keeping the declaration wrapped makes
// it conditional at *runtime*, so PHP 8.3 / OpCache can't hoist the
// class past a sibling's prior include. See docs for context.

class RANKFORGE_Schema_Builder_View {

    /**
 * Render the Schema Builder <details> block into the SEO metabox.
 */
    public static function render($post) {
        // SF-hotfix 2026-04-21: defensive guard. We've seen the render called
        // in rare flows where $post isn't a WP_Post (e.g. a nulled preview
        // context). Without this a single null-object access warning cascades
        // into a spammy error.php telemetry stream.
        if (!$post || !is_object($post) || !isset($post->ID)) {
            return;
        }

        $manual_schema = get_post_meta($post->ID, '_rankforge_manual_schema', true);
        $saved_type = get_post_meta($post->ID, '_rankforge_schema_type', true);
        $auto_type = RANKFORGE_Schema::instance()->auto_detect_type($post->ID);
        $current_type = $saved_type ?: $auto_type;
        $available_types = RANKFORGE_Schema::get_available_types();
        // RANKFORGE_AI_Client lives in rankforge-pro (post-split). On Free-only
        // sites the AI "auto-detect" button is hidden because $ai_configured
        // stays false.
        $ai_configured = class_exists('RANKFORGE_AI_Client')
            ? RANKFORGE_AI_Client::instance()->is_configured()
            : false;
        // show a locked "Schema Auto-Fill" entry to Free users so the
        // AI affordance is visible; the click opens the upsell modal instead.
        $ai_locked = !RANKFORGE_License::can('ai_features');
        // SF-hotfix 2026-04-21: $meta_desc was a parent-scope var in the
        // original render_meta_box — the F14 extraction forgot to pull
        // it through. PHP 8.2 emitted "Undefined variable $meta_desc" on
        // the placeholder= attr below (line 122), tripping our error
        // telemetry. Resolving it here mirrors how the parent method
        // fetches it.
        $meta_desc = (string) get_post_meta($post->ID, '_rankforge_meta_description', true);

        // Parse existing manual schema for field population
        $manual_data = [];
        if (!empty($manual_schema)) {
            $manual_data = json_decode($manual_schema, true);
            if (!is_array($manual_data)) $manual_data = [];
        }

        // Get AI-detected schema types
        $ai_schema = get_post_meta($post->ID, '_rankforge_ai_schema', true);
        $detected_types = [];
        if (!empty($ai_schema) && is_array($ai_schema) && !empty($ai_schema['detected_types'])) {
            $detected_types = $ai_schema['detected_types'];
        }
        ?>
        <details class="sf-schema-builder" style="margin:12px 0 8px;" open>
            <summary style="cursor:pointer;font-size:14px;font-weight:600;color:#FF6B35;display:flex;align-items:center;gap:6px;">
                <span class="dashicons dashicons-editor-code" style="font-size:14px;width:14px;height:14px;"></span>
                <?php esc_html_e('Schema Markup', 'rankforge'); ?><?php if (class_exists('Forge_Docs_Link')) Forge_Docs_Link::render('rankforge', 'schema-markup-rich-results-in-google', __('Schema markup', 'rankforge'), __('JSON-LD structured data that unlocks rich results (stars, FAQ, HowTo) in Google.', 'rankforge')); ?>
                <?php if (!empty($detected_types)): ?>
                <span style="font-size:14px;color:#64748b;font-weight:400;margin-left:4px;">(<?php echo count($detected_types); ?> <?php esc_html_e('detected', 'rankforge'); ?>)</span>
                <?php endif; ?>
            </summary>
            <div style="margin-top:10px;">

                <!-- Detected type badges -->
                <div class="sf-schema-badges" id="sf-schema-badges" style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:10px;">
                    <?php if (!empty($detected_types)): ?>
                        <?php foreach ($detected_types as $dt): ?>
                        <span class="sf-schema-badge sf-schema-badge--active"><?php echo esc_html($dt); ?></span>
                        <?php endforeach; ?>
                    <?php elseif ($current_type): ?>
                        <span class="sf-schema-badge sf-schema-badge--active"><?php echo esc_html($current_type); ?></span>
                    <?php endif; ?>
                    <?php if (empty($detected_types) && !$saved_type): ?>
                        <span class="sf-schema-badge"><?php esc_html_e('Auto', 'rankforge'); ?>: <?php echo esc_html($auto_type); ?></span>
                    <?php endif; ?>
                </div>

                <!-- AI Detect + Type selector -->
                <div style="display:flex;gap:6px;align-items:center;margin-bottom:10px;flex-wrap:wrap;">
                    <?php if ($ai_configured): ?>
                    <button type="button" class="sf-ai-gen-btn" id="sf-schema-detect" data-post="<?php echo esc_attr( $post->ID ); ?>">
                        <span class="dashicons dashicons-superhero-alt"></span> <?php esc_html_e('Detect with AI', 'rankforge'); ?> <span class="sf-credits-badge" style="background:rgba(255,255,255,0.25);color:#fff;"><?php esc_html_e('credits', 'rankforge'); ?></span>
                    </button>
                    <?php elseif ($ai_locked): echo wp_kses( RANKFORGE_License::ai_lock_html('schema', esc_html__('Schema Auto-Fill', 'rankforge'), 'pill'), forge_kses_credit_badge() ); endif; ?>
                    <select name="rankforge_schema_type_override" id="sf-schema-type" style="flex:1;min-width:100px;">
                        <option value="auto" <?php selected(!$saved_type); ?>><?php esc_html_e('Auto', 'rankforge'); ?> (<?php echo esc_html($auto_type); ?>)</option>
                        <?php foreach ($available_types as $t): ?>
                        <option value="<?php echo esc_attr($t); ?>" <?php selected($saved_type, $t); ?>><?php echo esc_html($t); ?></option>
                        <?php endforeach; ?>
                    </select>
                </div>

                <!-- Loading spinner -->
                <div id="sf-schema-loading" style="display:none;text-align:center;padding:10px;">
                    <span class="spinner is-active" style="float:none;margin:0 6px 0 0;"></span>
                    <?php esc_html_e('Detecting schema...', 'rankforge'); ?>
                </div>

                <!-- Field groups by schema type -->
                <div id="sf-schema-fields">
                    <!-- Article / BlogPosting / NewsArticle — extended
 set to match Google's required + recommended Article
 fields: publisher/logo, image, author URL, datePublished,
 dateModified, keywords, articleSection. -->
                    <?php
                        $default_author_name = get_the_author_meta('display_name', $post->post_author);
                        $default_author_url  = get_author_posts_url($post->post_author);
                        $default_image       = get_the_post_thumbnail_url($post->ID, 'large') ?: get_option('rankforge_default_og_image', '');
                        $default_date_pub    = get_the_date('c', $post);
                        $default_date_mod    = get_the_modified_date('c', $post);
                        $default_publisher   = get_bloginfo('name');
                        $default_logo_id     = (int) get_theme_mod('custom_logo');
                        $default_logo_url    = $default_logo_id ? wp_get_attachment_image_url($default_logo_id, 'full') : (get_site_icon_url(512) ?: '');
                        $default_section     = '';
                        $cats = get_the_category($post->ID);
                        if (!empty($cats)) $default_section = $cats[0]->name;
                        $default_keywords = '';
                        $tags = get_the_tags($post->ID);
                        if (!empty($tags)) $default_keywords = implode(', ', wp_list_pluck($tags, 'name'));
                    ?>
                    <div class="sf-schema-group" data-schema="Article,BlogPosting" style="display:none;">
                        <p style="margin:0 0 6px;"><label class="sf-schema-label"><?php esc_html_e('Headline', 'rankforge'); ?></label>
                        <input type="text" class="widefat sf-schema-field" data-key="headline" value="<?php echo esc_attr($manual_data['headline'] ?? $post->post_title); ?>"></p>

                        <p style="margin:0 0 6px;"><label class="sf-schema-label"><?php esc_html_e('Description', 'rankforge'); ?></label>
                        <textarea class="widefat sf-schema-field" data-key="description" rows="2" placeholder="<?php echo esc_attr($meta_desc); ?>"><?php echo esc_textarea($manual_data['description'] ?? ''); ?></textarea></p>

                        <p style="margin:0 0 6px;"><label class="sf-schema-label"><?php esc_html_e('Image URL', 'rankforge'); ?></label>
                        <input type="url" class="widefat sf-schema-field" data-key="image" placeholder="<?php echo esc_attr($default_image); ?>" value="<?php echo esc_attr(is_string($manual_data['image'] ?? '') ? ($manual_data['image'] ?? '') : ''); ?>"></p>

                        <div style="display:flex;gap:6px;margin-bottom:6px;">
                            <div style="flex:1;"><label class="sf-schema-label"><?php esc_html_e('Author Name', 'rankforge'); ?></label>
                            <input type="text" class="widefat sf-schema-field" data-key="author" value="<?php echo esc_attr($manual_data['author']['name'] ?? $default_author_name); ?>"></div>
                            <div style="flex:1;"><label class="sf-schema-label"><?php esc_html_e('Author URL', 'rankforge'); ?></label>
                            <input type="url" class="widefat sf-schema-field" data-key="authorUrl" placeholder="<?php echo esc_attr($default_author_url); ?>" value="<?php echo esc_attr($manual_data['author']['url'] ?? ''); ?>"></div>
                        </div>

                        <div style="display:flex;gap:6px;margin-bottom:6px;">
                            <div style="flex:1;"><label class="sf-schema-label"><?php esc_html_e('Date Published', 'rankforge'); ?></label>
                            <input type="text" class="widefat sf-schema-field" data-key="datePublished" placeholder="<?php echo esc_attr($default_date_pub); ?>" value="<?php echo esc_attr($manual_data['datePublished'] ?? ''); ?>"></div>
                            <div style="flex:1;"><label class="sf-schema-label"><?php esc_html_e('Date Modified', 'rankforge'); ?></label>
                            <input type="text" class="widefat sf-schema-field" data-key="dateModified" placeholder="<?php echo esc_attr($default_date_mod); ?>" value="<?php echo esc_attr($manual_data['dateModified'] ?? ''); ?>"></div>
                        </div>

                        <div style="display:flex;gap:6px;margin-bottom:6px;">
                            <div style="flex:1;"><label class="sf-schema-label"><?php esc_html_e('Publisher Name', 'rankforge'); ?></label>
                            <input type="text" class="widefat sf-schema-field" data-key="publisher" placeholder="<?php echo esc_attr($default_publisher); ?>" value="<?php echo esc_attr($manual_data['publisher']['name'] ?? ''); ?>"></div>
                            <div style="flex:1;"><label class="sf-schema-label"><?php esc_html_e('Publisher Logo URL', 'rankforge'); ?></label>
                            <input type="url" class="widefat sf-schema-field" data-key="publisherLogo" placeholder="<?php echo esc_attr($default_logo_url); ?>" value="<?php echo esc_attr($manual_data['publisher']['logo']['url'] ?? ''); ?>"></div>
                        </div>

                        <div style="display:flex;gap:6px;margin-bottom:6px;">
                            <div style="flex:1;"><label class="sf-schema-label"><?php esc_html_e('Article Section', 'rankforge'); ?></label>
                            <input type="text" class="widefat sf-schema-field" data-key="articleSection" placeholder="<?php echo esc_attr($default_section); ?>" value="<?php echo esc_attr($manual_data['articleSection'] ?? ''); ?>"></div>
                            <div style="flex:1;"><label class="sf-schema-label"><?php esc_html_e('Keywords (comma)', 'rankforge'); ?></label>
                            <input type="text" class="widefat sf-schema-field" data-key="keywords" placeholder="<?php echo esc_attr($default_keywords); ?>" value="<?php echo esc_attr(is_array($manual_data['keywords'] ?? '') ? implode(', ', $manual_data['keywords']) : ($manual_data['keywords'] ?? '')); ?>"></div>
                        </div>

                        <!-- Auto-fill hints surfaced as data attributes so
 buildJsonLd can fall back when user leaves a
 field blank. -->
                        <span class="sf-schema-defaults" style="display:none;"
                              data-author-name="<?php echo esc_attr($default_author_name); ?>"
                              data-author-url="<?php echo esc_attr($default_author_url); ?>"
                              data-image="<?php echo esc_attr($default_image); ?>"
                              data-date-published="<?php echo esc_attr($default_date_pub); ?>"
                              data-date-modified="<?php echo esc_attr($default_date_mod); ?>"
                              data-publisher-name="<?php echo esc_attr($default_publisher); ?>"
                              data-publisher-logo="<?php echo esc_attr($default_logo_url); ?>"
                              data-article-section="<?php echo esc_attr($default_section); ?>"
                              data-keywords="<?php echo esc_attr($default_keywords); ?>"></span>
                    </div>

                    <!-- FAQ -->
                    <div class="sf-schema-group" data-schema="FAQ" style="display:none;">
                        <div id="sf-faq-list">
                            <?php
                            $faq_items = [];
                            if (!empty($manual_data['mainEntity']) && is_array($manual_data['mainEntity'])) {
                                $faq_items = $manual_data['mainEntity'];
                            }
                            if (empty($faq_items)) {
                                $faq_items = [['name' => '', 'acceptedAnswer' => ['text' => '']]];
                            }
                            foreach ($faq_items as $i => $faq): ?>
                            <div class="sf-faq-pair" style="margin-bottom:8px;padding:8px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:6px;">
                                <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;">
                                    <span class="sf-schema-label" style="margin:0;">Q<?php echo esc_html( $i + 1 ); ?></span>
                                    <button type="button" class="sf-faq-remove" title="<?php esc_attr_e('Remove', 'rankforge'); ?>" style="background:none;border:none;color:#dc2626;cursor:pointer;font-size:16px;line-height:1;padding:0;">&times;</button>
                                </div>
                                <input type="text" class="widefat sf-faq-q" placeholder="<?php esc_attr_e('Question', 'rankforge'); ?>" value="<?php echo esc_attr($faq['name'] ?? ''); ?>" style="margin-bottom:4px;">
                                <textarea class="widefat sf-faq-a" rows="2" placeholder="<?php esc_attr_e('Answer', 'rankforge'); ?>"><?php echo esc_textarea($faq['acceptedAnswer']['text'] ?? ''); ?></textarea>
                            </div>
                            <?php endforeach; ?>
                        </div>
                        <button type="button" id="sf-faq-add" class="sf-action-btn" style="margin-top:4px;">
                            <span class="dashicons dashicons-plus-alt2"></span> <?php esc_html_e('Add Q&A', 'rankforge'); ?>
                        </button>
                    </div>

                    <!-- Product -->
                    <div class="sf-schema-group" data-schema="Product" style="display:none;">
                        <p style="margin:0 0 6px;"><label class="sf-schema-label"><?php esc_html_e('Product Name', 'rankforge'); ?></label>
                        <input type="text" class="widefat sf-schema-field" data-key="name" value="<?php echo esc_attr($manual_data['name'] ?? $post->post_title); ?>"></p>
                        <p style="margin:0 0 6px;"><label class="sf-schema-label"><?php esc_html_e('Description', 'rankforge'); ?></label>
                        <textarea class="widefat sf-schema-field" data-key="description" rows="2"><?php echo esc_textarea($manual_data['description'] ?? ''); ?></textarea></p>
                        <div style="display:flex;gap:6px;margin-bottom:6px;">
                            <div style="flex:1;"><label class="sf-schema-label"><?php esc_html_e('Price', 'rankforge'); ?></label>
                            <input type="text" class="widefat sf-schema-field" data-key="price" value="<?php echo esc_attr($manual_data['offers']['price'] ?? ''); ?>"></div>
                            <div style="flex:1;"><label class="sf-schema-label"><?php esc_html_e('Currency', 'rankforge'); ?></label>
                            <input type="text" class="widefat sf-schema-field" data-key="currency" value="<?php echo esc_attr($manual_data['offers']['priceCurrency'] ?? 'USD'); ?>"></div>
                        </div>
                        <p style="margin:0 0 6px;"><label class="sf-schema-label"><?php esc_html_e('Availability', 'rankforge'); ?></label>
                        <select class="widefat sf-schema-field" data-key="availability">
                            <?php
                            $avail = $manual_data['offers']['availability'] ?? 'InStock';
                            $avail_options = ['InStock' => __('In Stock', 'rankforge'), 'OutOfStock' => __('Out of Stock', 'rankforge'), 'PreOrder' => __('Pre-Order', 'rankforge'), 'Discontinued' => __('Discontinued', 'rankforge')];
                            foreach ($avail_options as $av => $al): ?>
                            <option value="<?php echo esc_attr($av); ?>" <?php selected($avail, $av); selected($avail, 'https://schema.org/' . $av); ?>><?php echo esc_html($al); ?></option>
                            <?php endforeach; ?>
                        </select></p>
                        <p style="margin:0 0 6px;"><label class="sf-schema-label"><?php esc_html_e('Brand', 'rankforge'); ?></label>
                        <input type="text" class="widefat sf-schema-field" data-key="brand" value="<?php echo esc_attr($manual_data['brand']['name'] ?? ($manual_data['brand'] ?? '')); ?>"></p>
                    </div>

                    <!-- HowTo -->
                    <div class="sf-schema-group" data-schema="HowTo" style="display:none;">
                        <!-- R3 (2026-04-28): live extraction feedback. -->
                        <div id="sf-howto-step-feedback" class="sf-howto-feedback" style="display:none;margin:0 0 10px;padding:10px 12px;border-radius:8px;font-size:14px;line-height:1.5;"></div>
                        <p style="margin:0 0 6px;"><label class="sf-schema-label"><?php esc_html_e('Name', 'rankforge'); ?></label>
                        <input type="text" class="widefat sf-schema-field" data-key="name" value="<?php echo esc_attr($manual_data['name'] ?? $post->post_title); ?>"></p>
                        <p style="margin:0 0 6px;"><label class="sf-schema-label"><?php esc_html_e('Description', 'rankforge'); ?></label>
                        <textarea class="widefat sf-schema-field" data-key="howto-description" rows="2"><?php echo esc_textarea($manual_data['description'] ?? ''); ?></textarea></p>
                        <div id="sf-howto-steps">
                            <?php
                            $steps = [];
                            if (!empty($manual_data['step']) && is_array($manual_data['step'])) {
                                $steps = $manual_data['step'];
                            }
                            if (empty($steps)) {
                                $steps = [['text' => '']];
                            }
                            foreach ($steps as $si => $step): ?>
                            <div class="sf-howto-step" style="display:flex;gap:4px;align-items:center;margin-bottom:4px;">
                                <span style="font-size:14px;font-weight:600;color:#64748b;min-width:18px;"><?php echo esc_html( $si + 1 ); ?>.</span>
                                <input type="text" class="widefat sf-step-text" value="<?php echo esc_attr($step['text'] ?? ''); ?>" placeholder="<?php esc_attr_e('Step description', 'rankforge'); ?>">
                                <button type="button" class="sf-step-remove" title="<?php esc_attr_e('Remove', 'rankforge'); ?>" style="background:none;border:none;color:#dc2626;cursor:pointer;font-size:16px;line-height:1;padding:0;">&times;</button>
                            </div>
                            <?php endforeach; ?>
                        </div>
                        <button type="button" id="sf-howto-add" class="sf-action-btn" style="margin-top:4px;">
                            <span class="dashicons dashicons-plus-alt2"></span> <?php esc_html_e('Add Step', 'rankforge'); ?>
                        </button>
                    </div>

                    <!-- Recipe . NB: ingredient / composition fields are
 deliberately EXCLUDED per product policy — only name,
 description, times, calories and instruction steps. -->
                    <?php
                    // ISO-8601 durations (PT#M) and "# calories" are how the JSON is
                    // stored; parse them back to plain numbers for the inputs.
                    $recipe_prep = (!empty($manual_data['prepTime']) && preg_match('/PT(\d+)M/', (string) $manual_data['prepTime'], $rm)) ? $rm[1] : '';
                    $recipe_cook = (!empty($manual_data['cookTime']) && preg_match('/PT(\d+)M/', (string) $manual_data['cookTime'], $rm2)) ? $rm2[1] : '';
                    $recipe_cal  = (!empty($manual_data['nutrition']['calories']) && preg_match('/(\d+)/', (string) $manual_data['nutrition']['calories'], $rm3)) ? $rm3[1] : '';
                    ?>
                    <div class="sf-schema-group" data-schema="Recipe" style="display:none;">
                        <p style="margin:0 0 6px;"><label class="sf-schema-label"><?php esc_html_e('Dish Name', 'rankforge'); ?></label>
                        <input type="text" class="widefat sf-schema-field" data-key="name" value="<?php echo esc_attr($manual_data['name'] ?? $post->post_title); ?>"></p>

                        <p style="margin:0 0 6px;"><label class="sf-schema-label"><?php esc_html_e('Description', 'rankforge'); ?></label>
                        <textarea class="widefat sf-schema-field" data-key="description" rows="2" placeholder="<?php echo esc_attr($meta_desc); ?>"><?php echo esc_textarea($manual_data['description'] ?? ''); ?></textarea></p>

                        <p style="margin:0 0 6px;"><label class="sf-schema-label"><?php esc_html_e('Image URL', 'rankforge'); ?></label>
                        <input type="url" class="widefat sf-schema-field" data-key="image" placeholder="<?php echo esc_attr($default_image); ?>" value="<?php echo esc_attr(is_string($manual_data['image'] ?? '') ? ($manual_data['image'] ?? '') : ''); ?>"></p>

                        <div style="display:flex;gap:6px;margin-bottom:6px;">
                            <div style="flex:1;"><label class="sf-schema-label"><?php esc_html_e('Prep Time (min)', 'rankforge'); ?></label>
                            <input type="number" min="0" class="widefat sf-schema-field" data-key="prepTime" value="<?php echo esc_attr($recipe_prep); ?>"></div>
                            <div style="flex:1;"><label class="sf-schema-label"><?php esc_html_e('Cook Time (min)', 'rankforge'); ?></label>
                            <input type="number" min="0" class="widefat sf-schema-field" data-key="cookTime" value="<?php echo esc_attr($recipe_cook); ?>"></div>
                            <div style="flex:1;"><label class="sf-schema-label"><?php esc_html_e('Calories (kcal)', 'rankforge'); ?></label>
                            <input type="number" min="0" class="widefat sf-schema-field" data-key="calories" value="<?php echo esc_attr($recipe_cal); ?>"></div>
                        </div>

                        <label class="sf-schema-label"><?php esc_html_e('Instructions (steps)', 'rankforge'); ?></label>
                        <div id="sf-recipe-steps">
                            <?php
                            $r_steps = [];
                            if (!empty($manual_data['recipeInstructions']) && is_array($manual_data['recipeInstructions'])) {
                                foreach ($manual_data['recipeInstructions'] as $rs) {
                                    $r_steps[] = is_array($rs) ? ($rs['text'] ?? '') : (string) $rs;
                                }
                            }
                            if (empty($r_steps)) $r_steps = [''];
                            foreach ($r_steps as $ri => $rstep): ?>
                            <div class="sf-recipe-step" style="display:flex;gap:4px;align-items:center;margin-bottom:4px;">
                                <span style="font-size:14px;font-weight:600;color:#64748b;min-width:18px;"><?php echo esc_html( $ri + 1 ); ?>.</span>
                                <input type="text" class="widefat sf-recipe-prop" value="<?php echo esc_attr($rstep); ?>" placeholder="<?php esc_attr_e('Step description', 'rankforge'); ?>">
                                <button type="button" class="sf-recipe-remove" title="<?php esc_attr_e('Remove', 'rankforge'); ?>" style="background:none;border:none;color:#dc2626;cursor:pointer;font-size:16px;line-height:1;padding:0;">&times;</button>
                            </div>
                            <?php endforeach; ?>
                        </div>
                        <button type="button" id="sf-recipe-add" class="sf-action-btn" style="margin-top:4px;">
                            <span class="dashicons dashicons-plus-alt2"></span> <?php esc_html_e('Add Step', 'rankforge'); ?>
                        </button>
                    </div>

                    <!-- LocalBusiness -->
                    <div class="sf-schema-group" data-schema="LocalBusiness" style="display:none;">
                        <p style="font-size:14px;color:#64748b;margin:0;">
                            <?php esc_html_e('LocalBusiness schema is managed in', 'rankforge'); ?>
                            <a href="<?php echo esc_url( admin_url('admin.php?page=rankforge-settings') ); ?>" style="color:#FF6B35;"><?php esc_html_e('Local SEO settings', 'rankforge'); ?></a>.
                        </p>
                    </div>
                </div>

                <!-- AC5: Google rich-results validation. Lists required
 fields the active type is missing; offending inputs also get
 a red outline (.sf-invalid). Advisory only — never blocks save. -->
                <div id="sf-schema-validation" class="sf-schema-validation" style="display:none;"></div>

                <!-- JSON Preview toggle -->
                <div style="margin-top:10px;">
                    <button type="button" id="sf-schema-json-toggle" class="sf-action-btn">
                        <span class="dashicons dashicons-editor-code"></span> <?php esc_html_e('JSON Preview', 'rankforge'); ?>
                    </button>
                    <button type="button" id="sf-schema-reset" class="sf-action-btn" style="color:#dc2626;border-color:#dc2626;">
                        <span class="dashicons dashicons-dismiss"></span> <?php esc_html_e('Reset', 'rankforge'); ?>
                    </button>
                </div>
                <div id="sf-schema-json-preview" style="display:none;margin-top:8px;">
                    <textarea id="sf-schema-json-output" readonly rows="8" style="width:100%;font-family:monospace;font-size:14px;background:#1e293b;color:#e2e8f0;border:1px solid #334155;border-radius:6px;padding:8px;resize:vertical;"></textarea>
                </div>

                <!-- Hidden field for schema data -->
                <input type="hidden" name="rankforge_manual_schema" id="sf-schema-hidden" value="<?php echo esc_attr($manual_schema); ?>">
            </div>
        </details>

        <style>
        .sf-schema-builder summary::-webkit-details-marker{color:#FF6B35;}
        .sf-schema-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:10px;font-size:14px;font-weight:600;background:#f1f5f9;color:#64748b;border:1px solid #e2e8f0;}
        .sf-schema-badge::before{content:'';width:6px;height:6px;border-radius:50%;background:#94a3b8;flex-shrink:0;}
        .sf-schema-badge--active{background:#ecfdf5;color:#059669;border-color:#bbf7d0;}
        .sf-schema-badge--active::before{background:#059669;}
        .sf-schema-label{display:block;font-size:14px;font-weight:600;color:#64748b;margin-bottom:2px;}
        .sf-faq-pair input,.sf-faq-pair textarea,.sf-howto-step input,.sf-recipe-step input{font-size:14px!important;padding:6px 8px!important;}
        .sf-schema-field.sf-invalid,.sf-recipe-prop.sf-invalid{border-color:#dc2626!important;box-shadow:0 0 0 1px #dc2626;}
        .sf-schema-validation{margin-top:10px;padding:8px 12px;background:#fffbeb;border:1px solid #fde68a;border-radius:8px;font-size:13px;line-height:1.5;color:#92400e;}
        .sf-schema-validation strong{color:#b45309;}
        </style>

        <script>
        jQuery(function($) {
            var postId = <?php echo (int) $post->ID; ?>;
            var currentType = '<?php echo esc_js($saved_type ?: ''); ?>';
            var autoType = '<?php echo esc_js($auto_type); ?>';

            function getActiveType() {
                var sel = $('#sf-schema-type').val();
                return sel === 'auto' ? autoType : sel;
            }

            function showFieldGroup(type) {
                $('.sf-schema-group').hide();
                $('.sf-schema-group').each(function() {
                    var types = $(this).data('schema').split(',');
                    if (types.indexOf(type) !== -1) $(this).show();
                });
                // R3 (2026-04-28): refresh HowTo step feedback whenever the
                // visible group flips. Hidden when type !== HowTo.
                renderHowToFeedback(type);
            }

            // R3 (2026-04-28) F5: live preview of auto-extracted HowTo
            // steps. Reads `howto_step_count` + `howto_step_names` from
            // RankForgeLive (frozen at page load — post content can't be
            // edited from the SEO metabox so the count is accurate). We
            // emit warn/notice/success but never block save: the renderer
            // already silently skips invalid HowTo (commit fc60364), so
            // this is purely informational.
            function renderHowToFeedback(type) {
                var $fb = $('#sf-howto-step-feedback');
                if (!$fb.length) return;
                if (type !== 'HowTo') {
                    $fb.hide().empty();
                    return;
                }
                var live  = window.RankForgeLive || {};
                var count = parseInt(live.howto_step_count || 0, 10);
                var names = live.howto_step_names || [];

                var bg, border, color, icon, title, body;
                if (count === 0) {
                    bg = '#fef2f2'; border = '#fecaca'; color = '#991b1b'; icon = '⚠';
                    title = <?php echo wp_json_encode(__('No HowTo steps detected', 'rankforge')); ?>;
                    body  = <?php echo wp_json_encode(__('Google requires at least 2 steps for a valid HowTo. Add H2 or H3 headings to your content (e.g. "Step 1: Mix flour", "Step 2: Knead 10 min") or use [rankforge_step] shortcodes — schema will be skipped on the frontend until then.', 'rankforge')); ?>;
                } else if (count === 1) {
                    bg = '#fef2f2'; border = '#fecaca'; color = '#991b1b'; icon = '⚠';
                    title = <?php echo wp_json_encode(__('Only 1 HowTo step detected', 'rankforge')); ?>;
                    body  = <?php echo wp_json_encode(__('Google requires at least 2 steps. Add at least one more H2/H3 heading describing a step.', 'rankforge')); ?>;
                } else if (count > 10) {
                    bg = '#fffbeb'; border = '#fde68a'; color = '#92400e'; icon = 'ℹ';
                    title = (<?php echo wp_json_encode( /* translators: %d: number of HowTo steps detected */ __('%d steps detected', 'rankforge')); ?>).replace('%d', count);
                    body  = <?php echo wp_json_encode(__('Google recommends 10 or fewer steps for the richest HowTo snippet — consider grouping related steps.', 'rankforge')); ?>;
                } else {
                    bg = '#ecfdf5'; border = '#bbf7d0'; color = '#047857'; icon = '✓';
                    title = (<?php echo wp_json_encode( /* translators: %d: number of HowTo steps detected */ __('%d steps detected', 'rankforge')); ?>).replace('%d', count);
                    body  = <?php echo wp_json_encode(__('Preview', 'rankforge')); ?> + ': ' + names.slice(0, 3).map(function(n){ return '"' + n + '"'; }).join(', ') + (names.length > 3 ? ', …' : '');
                }

                $fb.css({
                    background: bg, border: '1px solid ' + border, color: color, display: 'block'
                });
                var safeTitle = $('<div>').text(title).html();
                var safeBody  = $('<div>').text(body).html();
                $fb.html(
                    '<div style="font-weight:600;margin-bottom:4px;">' + icon + ' ' + safeTitle + '</div>' +
                    '<div>' + safeBody + '</div>'
                );
            }

            function buildJsonLd() {
                var type = getActiveType();
                var schema = {'@context': 'https://schema.org'};

                if (type === 'Article' || type === 'BlogPosting' || type === 'NewsArticle') {
                    schema['@type'] = type;
                    var $g = $('[data-schema="Article,BlogPosting"]');
                    var $def = $g.find('.sf-schema-defaults');
                    var getFld = function(key, fallback) {
                        var v = ($g.find('[data-key="'+key+'"]').val() || '').toString().trim();
                        return v || (fallback || '');
                    };
                    schema.headline = getFld('headline');
                    var descVal = getFld('description');
                    if (descVal) schema.description = descVal;
                    var img = getFld('image', $def.data('image') || '');
                    if (img) schema.image = img;
                    var authorName = getFld('author', $def.data('author-name') || '');
                    var authorUrl  = getFld('authorUrl', $def.data('author-url') || '');
                    var author = {'@type': 'Person', 'name': authorName};
                    if (authorUrl) author.url = authorUrl;
                    schema.author = author;
                    schema.datePublished = getFld('datePublished', $def.data('date-published') || '');
                    var dm = getFld('dateModified', $def.data('date-modified') || '');
                    if (dm) schema.dateModified = dm;
                    var pubName = getFld('publisher', $def.data('publisher-name') || '');
                    var pubLogo = getFld('publisherLogo', $def.data('publisher-logo') || '');
                    var publisher = {'@type': 'Organization', 'name': pubName};
                    if (pubLogo) publisher.logo = {'@type':'ImageObject', 'url': pubLogo};
                    schema.publisher = publisher;
                    var section = getFld('articleSection', $def.data('article-section') || '');
                    if (section) schema.articleSection = section;
                    var kw = getFld('keywords', $def.data('keywords') || '');
                    if (kw) {
                        var kwArr = kw.split(',').map(function(s){ return s.trim(); }).filter(Boolean);
                        if (kwArr.length) schema.keywords = kwArr.length === 1 ? kwArr[0] : kwArr;
                    }
                    if (typeof document !== 'undefined' && document.documentElement && document.documentElement.lang) {
                        schema.inLanguage = document.documentElement.lang;
                    }
                } else if (type === 'FAQ') {
                    schema['@type'] = 'FAQPage';
                    schema.mainEntity = [];
                    $('.sf-faq-pair').each(function() {
                        var q = $(this).find('.sf-faq-q').val();
                        var a = $(this).find('.sf-faq-a').val();
                        if (q || a) {
                            schema.mainEntity.push({
                                '@type': 'Question',
                                'name': q,
                                'acceptedAnswer': {'@type': 'Answer', 'text': a}
                            });
                        }
                    });
                } else if (type === 'Product') {
                    schema['@type'] = 'Product';
                    schema.name = $('[data-schema="Product"] [data-key="name"]').val() || '';
                    schema.description = $('[data-schema="Product"] [data-key="description"]').val() || '';
                    var brand = $('[data-schema="Product"] [data-key="brand"]').val();
                    if (brand) schema.brand = {'@type': 'Brand', 'name': brand};
                    schema.offers = {
                        '@type': 'Offer',
                        'price': $('[data-schema="Product"] [data-key="price"]').val() || '',
                        'priceCurrency': $('[data-schema="Product"] [data-key="currency"]').val() || 'USD',
                        'availability': 'https://schema.org/' + ($('[data-schema="Product"] [data-key="availability"]').val() || 'InStock')
                    };
                } else if (type === 'HowTo') {
                    schema['@type'] = 'HowTo';
                    schema.name = $('[data-schema="HowTo"] [data-key="name"]').val() || '';
                    schema.description = $('[data-schema="HowTo"] [data-key="howto-description"]').val() || '';
                    schema.step = [];
                    $('.sf-howto-step').each(function() {
                        var t = $(this).find('.sf-step-text').val();
                        if (t) {
                            schema.step.push({'@type': 'HowToStep', 'text': t});
                        }
                    });
                } else if (type === 'Recipe') {
 // Recipe. recipeIngredient is intentionally NOT
                    // emitted (product policy excludes composition fields).
                    schema['@type'] = 'Recipe';
                    var $r = $('[data-schema="Recipe"]');
                    var $rdef = $('.sf-schema-defaults');
                    var rget = function(key) {
                        var $f = $r.find('[data-key="'+key+'"]');
                        return (($f.val() || '').toString().trim()) || (($f.attr('placeholder') || '').toString().trim());
                    };
                    schema.name = rget('name');
                    var rDesc = rget('description'); if (rDesc) schema.description = rDesc;
                    var rImg = rget('image'); if (rImg) schema.image = rImg;
                    var rAuthor = ($rdef.data('author-name') || '').toString();
                    if (rAuthor) schema.author = {'@type': 'Person', 'name': rAuthor};
                    var rDate = ($rdef.data('date-published') || '').toString();
                    if (rDate) schema.datePublished = rDate;
                    var prep = parseInt($r.find('[data-key="prepTime"]').val(), 10);
                    if (prep > 0) schema.prepTime = 'PT' + prep + 'M';
                    var cook = parseInt($r.find('[data-key="cookTime"]').val(), 10);
                    if (cook > 0) schema.cookTime = 'PT' + cook + 'M';
                    if (prep > 0 && cook > 0) schema.totalTime = 'PT' + (prep + cook) + 'M';
                    var cal = parseInt($r.find('[data-key="calories"]').val(), 10);
                    if (cal > 0) schema.nutrition = {'@type': 'NutritionInformation', 'calories': cal + ' calories'};
                    var rSteps = [];
                    $('#sf-recipe-steps .sf-recipe-prop').each(function() {
                        var t = ($(this).val() || '').trim();
                        if (t) rSteps.push({'@type': 'HowToStep', 'text': t});
                    });
                    if (rSteps.length) schema.recipeInstructions = rSteps;
                } else {
                    schema['@type'] = type || 'WebPage';
                }

                return schema;
            }

 // AC5: Google rich-results validation. Required keys per type;
            // a field counts as filled if it has a value OR a non-empty placeholder
            // (placeholders carry the smart-mapped defaults). Repeater types are
            // checked separately. Purely advisory — never blocks save.
            var SCHEMA_REQUIRED = {
                'Article':     [['headline', '<?php echo esc_js(__('Headline', 'rankforge')); ?>'], ['image', '<?php echo esc_js(__('Image', 'rankforge')); ?>'], ['datePublished', '<?php echo esc_js(__('Date Published', 'rankforge')); ?>']],
                'BlogPosting': [['headline', '<?php echo esc_js(__('Headline', 'rankforge')); ?>'], ['image', '<?php echo esc_js(__('Image', 'rankforge')); ?>'], ['datePublished', '<?php echo esc_js(__('Date Published', 'rankforge')); ?>']],
                'NewsArticle': [['headline', '<?php echo esc_js(__('Headline', 'rankforge')); ?>'], ['image', '<?php echo esc_js(__('Image', 'rankforge')); ?>'], ['datePublished', '<?php echo esc_js(__('Date Published', 'rankforge')); ?>']],
                'Product':     [['name', '<?php echo esc_js(__('Product Name', 'rankforge')); ?>'], ['price', '<?php echo esc_js(__('Price', 'rankforge')); ?>']],
                'Recipe':      [['name', '<?php echo esc_js(__('Dish Name', 'rankforge')); ?>'], ['image', '<?php echo esc_js(__('Image', 'rankforge')); ?>']]
            };
            function fieldFilled($f) {
                return !!((($f.val() || '').toString().trim()) || (($f.attr('placeholder') || '').toString().trim()));
            }
            function validateSchema() {
                var type = getActiveType();
                $('.sf-schema-field, .sf-recipe-prop, .sf-faq-q, .sf-faq-a, .sf-step-text').removeClass('sf-invalid');
                var $group = $('.sf-schema-group').filter(function() {
                    return ($(this).data('schema') || '').toString().split(',').indexOf(type) !== -1;
                });
                var missing = [];
                (SCHEMA_REQUIRED[type] || []).forEach(function(pair) {
                    var $f = $group.find('[data-key="' + pair[0] + '"]');
                    if ($f.length && !fieldFilled($f)) { $f.addClass('sf-invalid'); missing.push(pair[1]); }
                });
                if (type === 'Recipe') {
                    var rOk = $('#sf-recipe-steps .sf-recipe-prop').filter(function(){ return ($(this).val() || '').trim(); }).length > 0;
                    if (!rOk) { $('#sf-recipe-steps .sf-recipe-prop').addClass('sf-invalid'); missing.push('<?php echo esc_js(__('At least one instruction step', 'rankforge')); ?>'); }
                } else if (type === 'FAQ') {
                    var fOk = $('.sf-faq-pair').filter(function(){ return ($(this).find('.sf-faq-q').val() || '').trim() && ($(this).find('.sf-faq-a').val() || '').trim(); }).length > 0;
                    if (!fOk) missing.push('<?php echo esc_js(__('At least one complete question & answer', 'rankforge')); ?>');
                } else if (type === 'HowTo') {
                    var hN = $('.sf-howto-step .sf-step-text').filter(function(){ return ($(this).val() || '').trim(); }).length;
                    if (hN < 2) missing.push('<?php echo esc_js(__('At least 2 steps', 'rankforge')); ?>');
                }
                var $v = $('#sf-schema-validation');
                if (missing.length) {
                    var safe = missing.map(function(m){ return $('<span>').text(m).html(); }).join(', ');
                    $v.html('<strong>&#9888; <?php echo esc_js(__('Google may not show rich results — missing required fields:', 'rankforge')); ?></strong> ' + safe).show();
                } else {
                    $v.hide().empty();
                }
                return missing;
            }

            function updateJsonPreview() {
                var json = buildJsonLd();
                $('#sf-schema-json-output').val(JSON.stringify(json, null, 2));
                $('#sf-schema-hidden').val(JSON.stringify(json));
                validateSchema();
            }

            // Init: show correct field group + run validation (advisory only —
            // validateSchema never writes the hidden field, so an untouched
            // auto post is not silently persisted as a manual override).
            showFieldGroup(getActiveType());
            validateSchema();

            // Type dropdown change
            $('#sf-schema-type').on('change', function() {
                showFieldGroup(getActiveType());
                updateJsonPreview();
            });

            // Real-time JSON update on field edit
            $(document).on('input change', '.sf-schema-field, .sf-faq-q, .sf-faq-a, .sf-step-text, .sf-recipe-prop', function() {
                updateJsonPreview();
            });

            // JSON preview toggle
            $('#sf-schema-json-toggle').on('click', function() {
                var $p = $('#sf-schema-json-preview');
                if ($p.is(':hidden')) {
                    updateJsonPreview();
                    $p.slideDown(150);
                } else {
                    $p.slideUp(150);
                }
            });

 // Reset button — custom confirm modal
            $('#sf-schema-reset').on('click', function() {
                function doReset(){
                    $('#sf-schema-type').val('auto').trigger('change');
                    $('#sf-schema-hidden').val('');
                    $('#sf-schema-badges').html('<span class="sf-schema-badge"><?php echo esc_js(__('Auto', 'rankforge')); ?>: ' + autoType + '</span>');
                }
                var msg = '<?php echo esc_js(__('Reset schema to auto-detection? Manual overrides will be removed.', 'rankforge')); ?>';
                var opts = { title: '<?php echo esc_js(__('Reset schema?', 'rankforge')); ?>', okText: '<?php echo esc_js(__('Reset', 'rankforge')); ?>', variant: 'danger' };
                if (window.sfModal && window.sfModal.confirm) { window.sfModal.confirm(msg, opts).then(function(ok){ if (ok) doReset(); }); }
                else if (confirm(msg)) { doReset(); }
            });

            // AI Detect
            $('#sf-schema-detect').on('click', function() {
                var $btn = $(this);
                $btn.addClass('loading');
                $('#sf-schema-loading').show();
                $.ajax({
                    url: (typeof rankforgeAdmin !== 'undefined' ? rankforgeAdmin.restUrl : '/wp-json/rankforge/v1') + '/schema/detect/' + postId,
                    method: 'POST',
                    beforeSend: function(xhr) {
                        xhr.setRequestHeader('X-WP-Nonce', typeof rankforgeAdmin !== 'undefined' ? rankforgeAdmin.restNonce : '');
                    }
                }).done(function(resp) {
                    // Support both response formats:
                    // Format A (simple): { type, fields, source }
                    // Format B (ai_detect_and_generate): { detected_types, json_ld, source }
                    var detectedType = resp.type || (resp.detected_types && resp.detected_types[0]) || '';
                    if (!detectedType) return;

                    // Set type dropdown
                    $('#sf-schema-type').val(detectedType);
                    if ($('#sf-schema-type').val() !== detectedType) {
                        $('#sf-schema-type').val('auto');
                    }
                    showFieldGroup(detectedType);

                    // Update badges
                    var types = resp.detected_types || [detectedType];
                    var badges = '';
                    $.each(types, function(i, t) {
                        badges += '<span class="sf-schema-badge sf-schema-badge--active">' + t + '</span> ';
                    });
                    $('#sf-schema-badges').html(badges);

                    // Try to populate from fields (Format A) or json_ld (Format B)
                    var f = resp.fields || null;
                    if (!f && resp.json_ld && resp.json_ld.length > 0) {
                        // Extract fields from the first matching json_ld block
                        var ld = resp.json_ld[0];
                        if (detectedType === 'Article' || detectedType === 'BlogPosting') {
                            f = {
                                headline: ld.headline || '',
                                author: (ld.author && ld.author.name) || ld.author || '',
                                datePublished: ld.datePublished || '',
                                description: ld.description || ''
                            };
                        } else if (detectedType === 'FAQ' || (ld['@type'] && ld['@type'] === 'FAQPage')) {
                            // Find FAQ block
                            var faqLd = ld;
                            $.each(resp.json_ld, function(i, block) {
                                if (block['@type'] === 'FAQPage') faqLd = block;
                            });
                            if (faqLd.mainEntity) {
                                f = {questions: []};
                                $.each(faqLd.mainEntity, function(i, q) {
                                    f.questions.push({
                                        question: q.name || '',
                                        answer: (q.acceptedAnswer && q.acceptedAnswer.text) || ''
                                    });
                                });
                            }
                        } else if (detectedType === 'Product') {
                            f = {
                                name: ld.name || '',
                                description: ld.description || '',
                                price: (ld.offers && ld.offers.price) || '',
                                currency: (ld.offers && ld.offers.priceCurrency) || 'USD',
                                availability: (ld.offers && ld.offers.availability) ? ld.offers.availability.replace('https://schema.org/', '') : 'InStock',
                                brand: (ld.brand && ld.brand.name) || ''
                            };
                        } else if (detectedType === 'HowTo') {
                            f = {
                                name: ld.name || '',
                                description: ld.description || '',
                                steps: []
                            };
                            if (ld.step) {
                                $.each(ld.step, function(i, s) { f.steps.push({text: s.text || ''}); });
                            }
                        }
                    }

                    // Populate form fields
                    if (f) {
                        if (detectedType === 'Article' || detectedType === 'BlogPosting') {
                            if (f.headline) $('[data-key="headline"]').val(f.headline);
                            if (f.author) $('[data-key="author"]').val(f.author);
                            if (f.datePublished) $('[data-key="datePublished"]').val(f.datePublished);
                            if (f.description) $('[data-key="description"]').val(f.description);
                        } else if (detectedType === 'FAQ' && f.questions) {
                            var $list = $('#sf-faq-list').empty();
                            $.each(f.questions, function(i, qa) {
                                $list.append(faqPairHtml(i + 1, qa.question || '', qa.answer || ''));
                            });
                        } else if (detectedType === 'Product') {
                            if (f.name) $('[data-schema="Product"] [data-key="name"]').val(f.name);
                            if (f.description) $('[data-schema="Product"] [data-key="description"]').val(f.description);
                            if (f.price) $('[data-schema="Product"] [data-key="price"]').val(f.price);
                            if (f.currency) $('[data-schema="Product"] [data-key="currency"]').val(f.currency);
                            if (f.availability) $('[data-schema="Product"] [data-key="availability"]').val(f.availability);
                            if (f.brand) $('[data-schema="Product"] [data-key="brand"]').val(f.brand);
                        } else if (detectedType === 'HowTo') {
                            if (f.name) $('[data-schema="HowTo"] [data-key="name"]').val(f.name);
                            if (f.description) $('[data-schema="HowTo"] [data-key="howto-description"]').val(f.description);
                            if (f.steps) {
                                var $steps = $('#sf-howto-steps').empty();
                                $.each(f.steps, function(i, step) {
                                    $steps.append(stepHtml(i + 1, step.text || step || ''));
                                });
                            }
                        }
                    }
                    updateJsonPreview();
                }).fail(function() {
                    var msg = '<?php echo esc_js(__('Schema detection failed. Try again later.', 'rankforge')); ?>';
                    if (window.sfModal && window.sfModal.alert) window.sfModal.alert(msg, { title: '<?php echo esc_js(__('Detection failed', 'rankforge')); ?>', variant: 'danger' });
                    else alert(msg);
                }).always(function() {
                    $btn.removeClass('loading');
                    $('#sf-schema-loading').hide();
                });
            });

            // FAQ: add / remove
            function faqPairHtml(num, q, a) {
                return '<div class="sf-faq-pair" style="margin-bottom:8px;padding:8px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:6px;">' +
                    '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;">' +
                    '<span class="sf-schema-label" style="margin:0;">Q' + num + '</span>' +
                    '<button type="button" class="sf-faq-remove" title="Remove" style="background:none;border:none;color:#dc2626;cursor:pointer;font-size:16px;line-height:1;padding:0;">&times;</button></div>' +
                    '<input type="text" class="widefat sf-faq-q" placeholder="Question" value="' + $('<span>').text(q).html() + '" style="margin-bottom:4px;">' +
                    '<textarea class="widefat sf-faq-a" rows="2" placeholder="Answer">' + $('<span>').text(a).html() + '</textarea></div>';
            }
            $('#sf-faq-add').on('click', function() {
                var num = $('.sf-faq-pair').length + 1;
                $('#sf-faq-list').append(faqPairHtml(num, '', ''));
                updateJsonPreview();
            });
            $(document).on('click', '.sf-faq-remove', function() {
                $(this).closest('.sf-faq-pair').remove();
                // Renumber
                $('.sf-faq-pair').each(function(i) {
                    $(this).find('.sf-schema-label').text('Q' + (i + 1));
                });
                updateJsonPreview();
            });

            // HowTo: add / remove steps
            function stepHtml(num, text) {
                return '<div class="sf-howto-step" style="display:flex;gap:4px;align-items:center;margin-bottom:4px;">' +
                    '<span style="font-size:14px;font-weight:600;color:#64748b;min-width:18px;">' + num + '.</span>' +
                    '<input type="text" class="widefat sf-step-text" value="' + $('<span>').text(text).html() + '" placeholder="Step description">' +
                    '<button type="button" class="sf-step-remove" title="Remove" style="background:none;border:none;color:#dc2626;cursor:pointer;font-size:16px;line-height:1;padding:0;">&times;</button></div>';
            }
            $('#sf-howto-add').on('click', function() {
                var num = $('.sf-howto-step').length + 1;
                $('#sf-howto-steps').append(stepHtml(num, ''));
                updateJsonPreview();
            });
            $(document).on('click', '.sf-step-remove', function() {
                $(this).closest('.sf-howto-step').remove();
                $('.sf-howto-step').each(function(i) {
                    $(this).find('span:first').text((i + 1) + '.');
                });
                updateJsonPreview();
            });

 // Recipe: add / remove instruction steps 
            function recipeStepHtml(num, text) {
                return '<div class="sf-recipe-step" style="display:flex;gap:4px;align-items:center;margin-bottom:4px;">' +
                    '<span style="font-size:14px;font-weight:600;color:#64748b;min-width:18px;">' + num + '.</span>' +
                    '<input type="text" class="widefat sf-recipe-prop" value="' + $('<span>').text(text).html() + '" placeholder="<?php echo esc_js(__('Step description', 'rankforge')); ?>">' +
                    '<button type="button" class="sf-recipe-remove" title="<?php echo esc_js(__('Remove', 'rankforge')); ?>" style="background:none;border:none;color:#dc2626;cursor:pointer;font-size:16px;line-height:1;padding:0;">&times;</button></div>';
            }
            $('#sf-recipe-add').on('click', function() {
                var num = $('.sf-recipe-step').length + 1;
                $('#sf-recipe-steps').append(recipeStepHtml(num, ''));
                updateJsonPreview();
            });
            $(document).on('click', '.sf-recipe-remove', function() {
                $(this).closest('.sf-recipe-step').remove();
                $('.sf-recipe-step').each(function(i) {
                    $(this).find('span:first').text((i + 1) + '.');
                });
                updateJsonPreview();
            });

            // Serialize on form submit
            $('form#post').on('submit', function() {
                // Only set hidden field if user has interacted with schema builder
                if ($('#sf-schema-type').val() !== 'auto' || currentType) {
                    updateJsonPreview();
                }
            });

            // Initial JSON preview if manual schema exists
            <?php if (!empty($manual_schema)): ?>
            updateJsonPreview();
            <?php endif; ?>
        });
        </script>
        <?php
    }
}

} // end: if (!class_exists('RANKFORGE_Schema_Builder_View'))
