<?php
declare(strict_types=1);
// ==================================================
// Nuclear Fancy Pants Edition™ — NUCLEAR+++
// File: /home/ploi/automarketer-ai.com/public/wp-content/plugins/automarketer-ai/includes/class-seo-meta-box.php
// Description: SEO meta box — post editor sidebar panel for scores, issues, and AI fix-it - NUCLEAR+++
// Last Edited: 2026-05-02
// ==================================================

namespace AutoMarketerAI;

defined('ABSPATH') || exit;

class SeoMetaBox
{
    // --------------------------------------------------
    // Register hooks
    // --------------------------------------------------
    public static function register(): void
    {
        add_action('add_meta_boxes',          [self::class, 'addMetaBox']);
        add_action('save_post',               [self::class, 'saveFields'], 10, 2);
        add_action('admin_enqueue_scripts',   [self::class, 'enqueueAssets']);
        add_filter('the_content',              [self::class, 'injectRelatedLinksIntoContent']);
        add_action('wp_ajax_amai_seo_fix_it', [self::class, 'handleFixIt']);
        add_action('wp_ajax_amai_seo_rescan', [self::class, 'handleRescan']);
        add_action('wp_ajax_amai_seo_save_field', [self::class, 'handleSaveField']);
        add_action('wp_ajax_amai_seo_inject_links', [self::class, 'handleInjectLinks']);
        add_action('wp_ajax_amai_seo_inject_internal_links', [self::class, 'handleInjectInternalLinks']);
        add_action('wp_ajax_amai_seo_suggest_links', [self::class, 'handleSuggestInternalLinks']);
        add_action('wp_ajax_amai_seo_unsplash_search',    [self::class, 'handleUnsplashSearch']);
        add_action('wp_ajax_amai_seo_unsplash_set',       [self::class, 'handleUnsplashSet']);
        add_action('wp_ajax_amai_seo_featured_image_ai',  [self::class, 'handleFeaturedImageAi']);
        add_action('wp_ajax_amai_seo_media_library',      [self::class, 'handleMediaLibrary']);
        add_action('wp_ajax_amai_seo_set_media_featured', [self::class, 'handleSetMediaFeatured']);
    }

    // --------------------------------------------------
    // Register meta box on all public post types
    // --------------------------------------------------
    public static function addMetaBox(): void
    {
        $post_types = get_post_types(['public' => true], 'names');
        foreach ($post_types as $post_type) {
            add_meta_box(
                'amai-seo-meta-box',
                'AutoMarketer AI SEO',
                [self::class, 'render'],
                $post_type,
                'normal',
                'high'
            );
            add_meta_box(
                'amai-seo-sidebar-widget',
                'Content Score',
                [self::class, 'renderSidebarWidget'],
                $post_type,
                'side',
                'high'
            );
        }
    }

    // --------------------------------------------------
    // Enqueue assets on post editor screens only
    // --------------------------------------------------
    public static function enqueueAssets(string $hook): void
    {
        if ($hook !== 'post.php' && $hook !== 'post-new.php') {
            return;
        }
        $post_id_ctx = isset($_GET['post']) ? (int) $_GET['post'] : 0;

        wp_enqueue_style(
            'amai-seo-meta-box',
            AMAI_PLUGIN_URL . 'assets/css/seo-meta-box.css',
            [],
            (string) filemtime(AMAI_PLUGIN_DIR . 'assets/css/seo-meta-box.css')
        );
        wp_enqueue_style(
            'amai-featured-image-picker',
            AMAI_PLUGIN_URL . 'assets/css/featured-image-picker.css',
            ['amai-seo-meta-box'],
            (string) filemtime(AMAI_PLUGIN_DIR . 'assets/css/featured-image-picker.css')
        );

        wp_enqueue_script(
            'amai-seo-meta-box',
            AMAI_PLUGIN_URL . 'assets/js/amai-seo-meta-box.js',
            [],
            (string) filemtime(AMAI_PLUGIN_DIR . 'assets/js/amai-seo-meta-box.js'),
            true
        );

        wp_localize_script('amai-seo-meta-box', 'amaiSeo', [
            'ajaxurl' => admin_url('admin-ajax.php'),
            'nonce'   => wp_create_nonce('amai_seo'),
            'postId'     => (int) ($post_id_ctx),
            'postStatus' => get_post_status($post_id_ctx) ?: 'draft',
            'postUrl'     => (string) get_permalink($post_id_ctx),
            'siteName'    => (string) get_bloginfo('name'),
            'siteIconUrl'       => (string) (get_site_icon_url(16) ?: ''),
            'featuredImageUrl' => (string) (($thumb_id = get_post_thumbnail_id($post_id_ctx)) ? (wp_get_attachment_image_url($thumb_id, 'large') ?: '') : ''),
            'hasImageProvider' => (function (): bool {
                foreach ((array) get_option('amai_active_modules', []) as $id) {
                    if (is_string($id) && str_starts_with($id, 'img-')) {
                        return true;
                    }
                }
                return false;
            })(),
            'postTitle'       => (string) get_the_title($post_id_ctx),
        ]);
    }

    // --------------------------------------------------
    // Render the meta box
    // --------------------------------------------------
    public static function render(\WP_Post $post): void
    {
        $post_id  = $post->ID;
        $data     = SeoData::getAll($post_id);
        $result   = SeoAnalyzer::analyze($post_id);
        $issues   = $result['issues'];
        $score_s  = (int) ($result['score_seo']);
        $score_r  = (int) ($result['score_readability']);
        $keyword      = (string) ($data['focus_keyword']   ?? '');
        $cornerstone  = (string) SeoData::get($post->ID, 'cornerstone');
        $title    = (string) ($data['meta_title']       ?? '');
        $desc     = (string) ($data['meta_description'] ?? '');
        $canonical = (string) ($data['canonical_url']  ?? '');
        $robots   = (string) ($data['robots']           ?? 'index,follow');
        $schema   = (string) ($data['schema_type']      ?? '');

        $errors      = array_filter($issues, static fn(array $i): bool => $i['severity'] === 'error');
        $warnings    = array_filter($issues, static fn(array $i): bool => $i['severity'] === 'warning');
        $suggestions = array_filter($issues, static fn(array $i): bool => $i['severity'] === 'suggestion');

        wp_nonce_field('amai_seo_save_' . $post_id, 'amai_seo_nonce');
        ?>
        <div id="amai-seo-box" data-post-id="<?php echo esc_attr((string) $post_id); ?>">

            <?php // ── Score strip ───────────────────────────────── ?>
            <div class="amai-seo-scores">
                <div class="amai-seo-score-card">
                    <div class="amai-seo-score-dial <?php echo esc_attr(self::scoreClass($score_s)); ?>"><?php echo esc_html((string) $score_s); ?></div>
                    <div class="amai-seo-score-label">SEO</div>
                </div>
                <div class="amai-seo-score-card">
                    <div class="amai-seo-score-dial <?php echo esc_attr(self::scoreClass($score_r)); ?>"><?php echo esc_html((string) $score_r); ?></div>
                    <div class="amai-seo-score-label">Readability</div>
                </div>
                <div class="amai-seo-score-summary">
                    <?php if (count($errors) > 0): ?>
                        <span class="amai-seo-badge amai-seo-badge--error"><?php echo count($errors); ?> error<?php echo count($errors) !== 1 ? 's' : ''; ?></span>
                    <?php endif; ?>
                    <?php if (count($warnings) > 0): ?>
                        <span class="amai-seo-badge amai-seo-badge--warning"><?php echo count($warnings); ?> warning<?php echo count($warnings) !== 1 ? 's' : ''; ?></span>
                    <?php endif; ?>
                    <?php if (count($suggestions) > 0): ?>
                        <span class="amai-seo-badge amai-seo-badge--suggestion"><?php echo count($suggestions); ?> suggestion<?php echo count($suggestions) !== 1 ? 's' : ''; ?></span>
                    <?php endif; ?>
                    <?php if (count($issues) === 0): ?>
                        <span class="amai-seo-badge amai-seo-badge--perfect">All good</span>
                    <?php endif; ?>
                </div>
            </div>

            <?php // ── Focus keyword ─────────────────────────────── ?>
            <div class="amai-seo-field">
                <label class="amai-seo-label" for="amai_focus_keyword">Focus keyword
                    <?php if ($keyword !== ''): ?>
                        <span class="amai-seo-auto-tag">auto-detected</span>
                    <?php endif; ?>
                </label>
                <input
                    type="text"
                    id="amai_focus_keyword"
                    name="amai_focus_keyword"
                    class="amai-seo-input"
                    value="<?php echo esc_attr($keyword); ?>"
                    placeholder="Leave blank to auto-detect"
                />
            </div>

            <?php // ── Cornerstone content ────────────────────────────────── ?>
            <div class="amai-seo-field amai-seo-field--cornerstone">
                <label class="amai-seo-cornerstone-label">
                    <input
                        type="checkbox"
                        id="amai_cornerstone"
                        name="amai_cornerstone"
                        value="1"
                        <?php checked($cornerstone, '1'); ?>
                    />
                    <span class="amai-seo-cornerstone-text">Cornerstone content</span>
                    <span class="amai-seo-cornerstone-hint">This is one of your most important posts. Mark it so your other posts link back to it more often.</span>
                </label>
            </div>

            <?php // ── SEO title ─────────────────────────────────── ?>
            <div class="amai-seo-field">
                <label class="amai-seo-label" for="amai_meta_title">SEO title
                    <span class="amai-seo-char-count" id="amai-title-count"><?php echo mb_strlen($title); ?>/60</span>
                </label>
                <input
                    type="text"
                    id="amai_meta_title"
                    name="amai_meta_title"
                    class="amai-seo-input"
                    value="<?php echo esc_attr($title); ?>"
                    placeholder="50-60 characters recommended"
                    maxlength="100"
                />
                <div class="amai-seo-char-bar"><div class="amai-seo-char-bar-fill" id="amai-title-bar" style="width:<?php echo esc_attr((string) min(100, (int) (mb_strlen($title) / 60 * 100))); ?>%"></div></div>
            </div>

            <?php // ── Meta description ──────────────────────────── ?>
            <div class="amai-seo-field">
                <label class="amai-seo-label" for="amai_meta_description">Meta description
                    <span class="amai-seo-char-count" id="amai-desc-count"><?php echo mb_strlen($desc); ?>/160</span>
                </label>
                <textarea
                    id="amai_meta_description"
                    name="amai_meta_description"
                    class="amai-seo-input amai-seo-textarea"
                    placeholder="150-160 characters recommended"
                    maxlength="200"
                    rows="3"
                ><?php echo esc_textarea($desc); ?></textarea>
                <div class="amai-seo-char-bar"><div class="amai-seo-char-bar-fill" id="amai-desc-bar" style="width:<?php echo esc_attr((string) min(100, (int) (mb_strlen($desc) / 160 * 100))); ?>%"></div></div>
            </div>

            <?php // ── Advanced toggle ───────────────────────────── ?>
            <div class="amai-seo-advanced-toggle" id="amai-advanced-toggle">Advanced settings</div>
            <div class="amai-seo-advanced" id="amai-advanced-panel" style="display:none">

                <div class="amai-seo-field">
                    <label class="amai-seo-label" for="amai_canonical_url">Canonical URL</label>
                    <input
                        type="url"
                        id="amai_canonical_url"
                        name="amai_canonical_url"
                        class="amai-seo-input"
                        value="<?php echo esc_attr($canonical); ?>"
                        placeholder="Leave blank to use default permalink"
                    />
                </div>

                <div class="amai-seo-field">
                    <label class="amai-seo-label" for="amai_robots">Robots</label>
                    <select id="amai_robots" name="amai_robots" class="amai-seo-select">
                        <?php foreach (['index,follow', 'noindex,follow', 'index,nofollow', 'noindex,nofollow'] as $val): ?>
                            <option value="<?php echo esc_attr($val); ?>" <?php selected($robots, $val); ?>><?php echo esc_html($val); ?></option>
                        <?php endforeach; ?>
                    </select>
                </div>

                <div class="amai-seo-field">
                    <label class="amai-seo-label" for="amai_schema_type">Schema type</label>
                    <select id="amai_schema_type" name="amai_schema_type" class="amai-seo-select">
                        <option value="">-- Auto --</option>
                        <?php foreach (['Article', 'BlogPosting', 'Product', 'FAQPage', 'LocalBusiness', 'WebPage', 'Service'] as $val): ?>
                            <option value="<?php echo esc_attr($val); ?>" <?php selected($schema, $val); ?>><?php echo esc_html($val); ?></option>
                        <?php endforeach; ?>
                    </select>
                </div>

            </div>

            <?php // ── Issues list ───────────────────────────────── ?>
            <?php if (count($issues) > 0): ?>
                <div class="amai-seo-issues">
                    <div class="amai-seo-issues-title">Issues</div>
                    <?php foreach ($issues as $issue): ?>
                        <?php if ($issue['message'] === '') continue; ?>
                        <div class="amai-seo-issue amai-seo-issue--<?php echo esc_attr($issue['severity']); ?>">
                            <span class="amai-seo-issue-dot"></span>
                            <span class="amai-seo-issue-msg"><?php echo esc_html($issue['message']); ?></span>
                            <?php if (isset($issue['issue_index']) && $issue['issue_index'] !== ''): ?>
                                <button
                                    type="button"
                                    class="amai-seo-fix-btn amai-seo-fix-btn--inline"
                                    data-field="<?php echo esc_attr($issue['field']); ?>"
                                    data-issue-index="<?php echo esc_attr((string) ($issue['issue_index'] ?? '')); ?>"
                                    data-fix-mode="<?php echo esc_attr($issue['fix_mode'] ?? ''); ?>"
                                >Fix</button>
                            <?php elseif (in_array($issue['message'], [
                                'No internal links found. Link to at least 2 related posts or pages.',
                                'Only one internal link found. Add at least one more.',
                            ], true)): ?>
                                <button
                                    type="button"
                                    class="amai-seo-suggest-links-btn"
                                    data-post-id="<?php echo esc_attr((string) $post_id); ?>"
                                >Suggest links</button>
                            <?php elseif (in_array($issue['message'], [
                                'One or more images are missing alt text.',
                                'No image alt text contains your focus keyword.',
                            ], true)): ?>
                                <button
                                    type="button"
                                    class="amai-seo-tip-btn"
                                    data-tip="<?php
                                        $tips = [
                                            'One or more images are missing alt text.' => 'Click any image in the editor, then add a description in the Alt Text field in the right sidebar. Alt text helps search engines and screen readers.',
                                            'No image alt text contains your focus keyword.' => 'Edit the alt text on your most relevant image to naturally include your focus keyword. Click the image in the editor to access the Alt Text field.',
                                        ];
                                        echo esc_attr($tips[$issue['message']] ?? '');
                                    ?>"
                                >How to fix</button>
                            <?php endif; ?>
                        </div>
                    <?php endforeach; ?>
                </div>
            <?php endif; ?>

            <?php // ── Footer ────────────────────────────────────── ?>
            <div class="amai-seo-footer">
                <button type="button" class="amai-seo-rescan-btn" id="amai-rescan-btn">Re-analyze</button>
                <span class="amai-seo-footer-note" id="amai-seo-status"></span>
            </div>

        </div>
        <?php
    }

    // --------------------------------------------------
    // Save meta box fields on post save
    // --------------------------------------------------
    public static function saveFields(int $post_id, \WP_Post $post): void
    {
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return;
        }
        if (wp_is_post_revision($post_id)) {
            return;
        }
        if (!isset($_POST['amai_seo_nonce'])) {
            return;
        }
        if (!wp_verify_nonce(
            sanitize_text_field(wp_unslash($_POST['amai_seo_nonce'])),
            'amai_seo_save_' . $post_id
        )) {
            return;
        }
        if (!current_user_can('edit_post', $post_id)) {
            return;
        }

        $fields = [
            'focus_keyword'    => sanitize_text_field(wp_unslash((string) ($_POST['amai_focus_keyword']    ?? ''))),
            'meta_title'       => sanitize_text_field(wp_unslash((string) ($_POST['amai_meta_title']       ?? ''))),
            'meta_description' => sanitize_textarea_field(wp_unslash((string) ($_POST['amai_meta_description'] ?? ''))),
            'canonical_url'    => esc_url_raw(wp_unslash((string) ($_POST['amai_canonical_url']             ?? ''))),
            'robots'           => sanitize_text_field(wp_unslash((string) ($_POST['amai_robots']            ?? 'index,follow'))),
            'schema_type'      => sanitize_text_field(wp_unslash((string) ($_POST['amai_schema_type']       ?? ''))),
            'cornerstone'      => isset($_POST['amai_cornerstone']) ? '1' : '0',
        ];

        SeoData::setMany($post_id, $fields);

        // Auto-inject focus keyword into img alt attributes in post content
        $kw = trim($fields['focus_keyword']);
        if ($kw !== '') {
            $raw = get_post_field('post_content', $post_id, 'raw');
            if (is_string($raw) && $raw !== '') {
                $updated = preg_replace_callback(
                    '/<img(\s[^>]*?)>/i',
                    static function (array $m) use ($kw): string {
                        $tag = $m[1];
                        if (preg_match('/\salt=/i', $tag)) {
                            $tag = preg_replace('/\salt=(["\'])[^"\']*(\1)/i', ' alt=' . '"' . esc_attr($kw) . '"', $tag);
                        } else {
                            $tag .= ' alt="' . esc_attr($kw) . '"';
                        }
                        return '<img' . $tag . '>';
                    },
                    $raw
                );
                if (is_string($updated) && $updated !== $raw) {
                    $updated = \AutoMarketerAI\LinkPreserver::preserve($post_id, $updated);
                    wp_update_post(['ID' => $post_id, 'post_content' => $updated]);
                }
            }
        }
        // Auto-inject focus keyword into featured image alt text
        if ($kw !== '') {
            $thumb_id = (int) get_post_thumbnail_id($post_id);
            if ($thumb_id > 0) {
                update_post_meta($thumb_id, '_wp_attachment_image_alt', $kw);
            }
        }
    }

    // --------------------------------------------------
    // Render sidebar score widget
    // --------------------------------------------------
    public static function renderSidebarWidget(\WP_Post $post): void
    {
        $result  = SeoAnalyzer::analyze($post->ID);
        $score_s = (int) ($result['score_seo']);
        $score_r = (int) ($result['score_readability']);
        $issues  = $result['issues'];
        $fixable = array_filter($issues, static fn(array $i): bool => isset($i['issue_index']) && $i['issue_index'] !== '');
        $count   = count($fixable);
        ?>
        <div id="amai-seo-sidebar-widget">
            <div class="amai-sw-scores">
                <div class="amai-sw-score-wrap">
                    <div class="amai-sw-tooltip-wrap">
                        <div class="amai-sw-dial <?php echo $score_s >= 70 ? 'amai-sw-dial--good' : ($score_s >= 40 ? 'amai-sw-dial--ok' : 'amai-sw-dial--poor'); ?>" id="amai-sw-dial-seo"><?php echo $score_s; ?></div>
                        <div class="amai-sw-tooltip">How well this post is set up to rank in search - keyword use, meta fields, links, and structure.</div>
                    </div>
                    <div class="amai-sw-label">Content Score</div>
                    <div class="amai-sw-score-meta">out of 100</div>
                    <div class="amai-sw-dial-zone <?php echo $score_s >= 70 ? 'amai-sw-dial-zone--good' : ($score_s >= 40 ? 'amai-sw-dial-zone--ok' : ($score_s > 0 ? 'amai-sw-dial-zone--poor' : 'amai-sw-dial-zone--empty')); ?>"><?php echo $score_s >= 70 ? 'Good' : ($score_s >= 40 ? 'Fair' : ($score_s > 0 ? 'Needs work' : '--')); ?></div>
                </div>
                <div class="amai-sw-score-wrap">
                    <div class="amai-sw-tooltip-wrap">
                        <div class="amai-sw-dial <?php echo $score_r >= 70 ? 'amai-sw-dial--good' : ($score_r >= 40 ? 'amai-sw-dial--ok' : 'amai-sw-dial--poor'); ?>" id="amai-sw-dial-read"><?php echo $score_r; ?></div>
                        <div class="amai-sw-tooltip">How easy this post is to read - sentence length, paragraph length, and use of transition words.</div>
                    </div>
                    <div class="amai-sw-label">Readability</div>
                    <div class="amai-sw-score-meta">out of 100</div>
                    <div class="amai-sw-dial-zone <?php echo $score_r >= 70 ? 'amai-sw-dial-zone--good' : ($score_r >= 40 ? 'amai-sw-dial-zone--ok' : ($score_r > 0 ? 'amai-sw-dial-zone--poor' : 'amai-sw-dial-zone--empty')); ?>"><?php echo $score_r >= 70 ? 'Good' : ($score_r >= 40 ? 'Fair' : ($score_r > 0 ? 'Needs work' : '--')); ?></div>
                </div>
            </div>
            <?php
            $is_published = $post->post_status === 'publish';
            if ($score_s === 0 && $score_r === 0) {
                $grade_sentence = 'Write your post and we\'ll score it.';
            } elseif ($score_s >= 70 && $score_r >= 70 && $count === 0) {
                $grade_sentence = $is_published ? 'This post is in great shape.' : 'This post is ready to publish.';
            } elseif ($score_s >= 70 && $score_r >= 70) {
                $grade_sentence = $is_published ? 'Looking good - ' . $count . ' thing' . ($count !== 1 ? 's' : '') . ' left to tighten up.' : 'Almost there - fix ' . $count . ' thing' . ($count !== 1 ? 's' : '') . ' and you\'re good to go.';
            } elseif ($score_s >= 40 && $score_r >= 40) {
                $grade_sentence = 'Good start - a few things to clean up.';
            } else {
                $grade_sentence = 'This post needs some work before it\'s ready.';
            }
            ?>
            <div class="amai-sw-grade" id="amai-sw-grade"><?php echo esc_html($grade_sentence); ?></div>
            <div class="amai-sw-issue-count" id="amai-sw-issue-count"><?php echo $count > 0 ? $count . ' issue' . ($count !== 1 ? 's' : '') . ' to fix' : 'All good'; ?></div>
            <button type="button" id="amai-sw-optimize-btn" class="amai-sw-btn"<?php echo $count === 0 ? ' style="display:none;"' : ''; ?>>
                Auto-Optimize
            </button>
            <div class="amai-sw-optimize-hint" id="amai-sw-optimize-hint"<?php echo $count === 0 ? ' style="display:none;"' : ''; ?>>AI fixes this post's issues one by one. Review anything left when it's done.</div>
            <div class="amai-sw-status" id="amai-sw-status"></div>
            <button type="button" id="amai-sw-preview-btn" class="amai-sw-btn amai-sw-btn--secondary">
                Google Preview
            </button>
            <button type="button" id="amai-sw-fb-preview-btn" class="amai-sw-btn amai-sw-btn--secondary">
                Facebook Preview
            </button>
        </div>

        <?php // ── Google snippet modal ──────────────────── ?>
        <div id="amai-snippet-modal" style="display:none;" aria-modal="true" role="dialog">
            <div class="amai-snm-backdrop"></div>
            <div class="amai-snm-panel">
                <div class="amai-snm-header">
                    <span class="amai-snm-title">Google snippet preview</span>
                    <button type="button" class="amai-snm-close" id="amai-snm-close" aria-label="Close">&times;</button>
                </div>
                <div class="amai-snm-tabs">
                    <button type="button" class="amai-snm-tab amai-snm-tab--active" data-device="desktop">Desktop</button>
                    <button type="button" class="amai-snm-tab" data-device="tablet">Tablet</button>
                    <button type="button" class="amai-snm-tab" data-device="mobile">Mobile</button>
                </div>
                <div class="amai-snm-viewport">
                    <div class="amai-snm-browser">
                        <div class="amai-snm-browser-bar">
                            <span class="amai-snm-browser-dot"></span>
                            <span class="amai-snm-browser-dot"></span>
                            <span class="amai-snm-browser-dot"></span>
                            <span class="amai-snm-browser-url" id="amai-snm-url"></span>
                        </div>
                        <div class="amai-snm-serp">
                            <div class="amai-snm-serp-site-row">
                                <img class="amai-snm-favicon" id="amai-snm-favicon" src="" alt="" />
                                <div class="amai-snm-site-info">
                                    <span class="amai-snm-sitename" id="amai-snm-sitename"></span>
                                    <span class="amai-snm-breadcrumb" id="amai-snm-breadcrumb"></span>
                                </div>
                            </div>
                            <div class="amai-snm-serp-title" id="amai-snm-title"></div>
                            <div class="amai-snm-serp-desc" id="amai-snm-desc"></div>
                        </div>
                    </div>
                </div>
                <div class="amai-snm-hint">This is how your post may appear in Google search results. Actual display varies.</div>
            </div>
        </div>

        <?php // ── Featured image picker modal ────────────── ?>
        <div id="amai-fip-modal" aria-modal="true" role="dialog" aria-label="Set featured image">
            <div class="amai-fip-backdrop" id="amai-fip-backdrop"></div>
            <div class="amai-fip-panel">
                <div class="amai-fip-header">
                    <div>
                        <span class="amai-fip-eyebrow">Featured Image</span>
                        <span class="amai-fip-title">Choose an image</span>
                    </div>
                    <button type="button" class="amai-fip-close" id="amai-fip-close" aria-label="Close">&times;</button>
                </div>
                <div class="amai-fip-tabs">
                    <button type="button" class="amai-fip-tab is-active" data-tab="media">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>
                        Media Library
                    </button>
                    <button type="button" class="amai-fip-tab" data-tab="unsplash">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>
                        Unsplash
                    </button>
                    <button type="button" class="amai-fip-tab" data-tab="ai">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
                        AI Generate
                    </button>
                </div>
                <div class="amai-fip-body">
                    <div class="amai-fip-pane is-active" data-pane="media">
                        <div class="amai-fip-media-toolbar">
                            <button type="button" class="amai-fip-upload-btn" id="amai-fip-upload-btn">
                                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
                                Upload file
                            </button>
                            <input type="text" class="amai-fip-media-search" id="amai-fip-media-search" placeholder="Search media library...">
                        </div>
                        <div class="amai-fip-media-grid" id="amai-fip-media-grid">
                            <div class="amai-fip-media-loading">Loading...</div>
                        </div>
                    </div>
                    <div class="amai-fip-pane" data-pane="unsplash">
                        <div class="amai-fip-unsplash-bar">
                            <input type="text" class="amai-fip-unsplash-input" id="amai-fip-unsplash-input" placeholder="Search Unsplash... e.g. small business office">
                            <button type="button" class="amai-fip-unsplash-search-btn" id="amai-fip-unsplash-btn">Search</button>
                        </div>
                        <div class="amai-fip-unsplash-grid" id="amai-fip-unsplash-grid">
                            <div class="amai-fip-unsplash-hint" style="grid-column:1/-1">Search for a photo above to get started.</div>
                        </div>
                    </div>
                    <div class="amai-fip-pane" data-pane="ai">
                        <div class="amai-fip-ai-center" id="amai-fip-ai-center">
                            <div class="amai-fip-ai-icon">
                                <svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
                            </div>
                            <div class="amai-fip-ai-title">Generate with AI</div>
                            <div class="amai-fip-ai-sub">We'll create a unique image based on your post title and focus keyword. Takes about 15-30 seconds.</div>
                            <div class="amai-fip-ai-prompt-label">Image prompt</div>
                            <div class="amai-fip-ai-prompt-preview" id="amai-fip-ai-prompt">Loading...</div>
                            <button type="button" class="amai-fip-ai-generate-btn" id="amai-fip-ai-btn">
                                <span class="amai-fip-ai-btn-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg></span>
                                <div class="amai-fip-ai-spinner"></div>
                                Generate image
                            </button>
                            <div class="amai-fip-ai-preview" id="amai-fip-ai-preview">
                                <img id="amai-fip-ai-preview-img" src="" alt="AI generated image">
                                <div class="amai-fip-ai-preview-label">AI generated - click Set image to use this</div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="amai-fip-footer">
                    <span class="amai-fip-status" id="amai-fip-status"></span>
                    <button type="button" class="amai-fip-cancel-btn" id="amai-fip-cancel">Cancel</button>
                    <button type="button" class="amai-fip-set-btn" id="amai-fip-set-btn" disabled>
                        <span class="amai-fip-set-label">Set image</span>
                        <div class="amai-fip-set-spinner"></div>
                    </button>
                </div>
            </div>
        </div>

        <?php // ── Facebook OG preview modal ──────────────────── ?>
        <div id="amai-fb-modal" style="display:none;" aria-modal="true" role="dialog">
            <div class="amai-fbm-backdrop"></div>
            <div class="amai-fbm-panel">
                <div class="amai-fbm-header">
                    <span class="amai-fbm-title">Facebook link preview</span>
                    <button type="button" class="amai-fbm-close" id="amai-fbm-close" aria-label="Close">&times;</button>
                </div>
                <div class="amai-fbm-viewport">
                    <div class="amai-fbm-card">
                        <div class="amai-fbm-image-wrap">
                            <img class="amai-fbm-image" id="amai-fbm-image" src="" alt="" />
                            <div class="amai-fbm-image-empty" id="amai-fbm-image-empty">No featured image set</div>
                        </div>
                        <div class="amai-fbm-card-body">
                            <div class="amai-fbm-domain" id="amai-fbm-domain"></div>
                            <div class="amai-fbm-card-title" id="amai-fbm-card-title"></div>
                            <div class="amai-fbm-card-desc" id="amai-fbm-card-desc"></div>
                        </div>
                    </div>
                </div>
                <div class="amai-fbm-hint">This is how your post may appear when shared on Facebook. Actual display varies.</div>
            </div>
        </div>
        <?php
    }

    // --------------------------------------------------
    // AJAX -- Generate and inject per-post outbound links via AI
    // --------------------------------------------------
    // --------------------------------------------------
    // AJAX -- Suggest internal links for current post
    // --------------------------------------------------
    public static function handleSuggestInternalLinks(): void
    {
        check_ajax_referer('amai_seo', 'nonce');
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }
        $post_id = (int) ($_POST['post_id'] ?? 0);
        if ($post_id <= 0 || !current_user_can('edit_post', $post_id)) {
            wp_send_json_error(['message' => 'Invalid post.'], 400);
        }
        $post = get_post($post_id);
        if (!$post instanceof \WP_Post) {
            wp_send_json_error(['message' => 'Post not found.'], 404);
        }

        // Gate: require at least 10 other published posts
        $published = (int) wp_count_posts('post')->publish;
        if ($post->post_status === 'publish') {
            $published = max(0, $published - 1);
        }
        if ($published < 10) {
            wp_send_json_error(['message' => 'Not enough published posts to suggest links.'], 422);
        }

        $focus_keyword = trim((string) \AutoMarketerAI\SeoData::get($post_id, 'focus_keyword'));
        $post_title    = strtolower(wp_strip_all_tags($post->post_title));

        // Fetch up to 40 other published posts
        $candidates = get_posts([
            'post_type'      => 'post',
            'post_status'    => 'publish',
            'posts_per_page' => 40,
            'post__not_in'   => [$post_id],
            'orderby'        => 'date',
            'order'          => 'DESC',
        ]);

        if (empty($candidates)) {
            wp_send_json_error(['message' => 'No other published posts found.'], 422);
        }

        // Score each candidate by keyword/title relevance
        $kw_words = $focus_keyword !== ''
            ? array_filter(explode(' ', strtolower($focus_keyword)))
            : array_filter(explode(' ', $post_title));
        $kw_words = array_values(array_unique($kw_words));

        $scored = [];
        foreach ($candidates as $candidate) {
            if (!$candidate instanceof \WP_Post) {
                continue;
            }
            $ctitle   = strtolower(wp_strip_all_tags($candidate->post_title));
            $cexcerpt = strtolower(wp_strip_all_tags(get_the_excerpt($candidate)));
            $haystack = $ctitle . ' ' . $cexcerpt;
            $score    = 0;
            foreach ($kw_words as $word) {
                if (strlen($word) >= 3 && str_contains($haystack, $word)) {
                    $score++;
                }
            }
            // Bonus: full keyword phrase match in title
            if ($focus_keyword !== '' && str_contains($ctitle, strtolower($focus_keyword))) {
                $score += 3;
            }
            // Cornerstone bonus -- prioritise pillar posts in suggestions
            $is_cornerstone = (string) \AutoMarketerAI\SeoData::get($candidate->ID, 'cornerstone');
            if ($is_cornerstone === '1') {
                $score += 5;
            }
            $scored[] = [
                'score'  => $score,
                'title'  => $candidate->post_title,
                'url'    => (string) get_permalink($candidate->ID),
                'anchor' => $candidate->post_title,
            ];
        }

        // Sort by score desc, then alpha by title
        usort($scored, static function (array $a, array $b): int {
            if ($b['score'] !== $a['score']) {
                return $b['score'] <=> $a['score'];
            }
            return strcmp($a['title'], $b['title']);
        });

        // Return top 5, strip score from output
        $results = array_slice($scored, 0, 5);
        $output  = array_map(
            static fn(array $r): array => ['title' => $r['title'], 'url' => $r['url'], 'anchor' => $r['anchor']],
            $results
        );

        wp_send_json_success(['suggestions' => $output]);
    }

    public static function handleInjectLinks(): void
    {
        check_ajax_referer('amai_seo', 'nonce');
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }
        $post_id = (int) ($_POST['post_id'] ?? 0);
        if ($post_id <= 0 || !current_user_can('edit_post', $post_id)) {
            wp_send_json_error(['message' => 'Invalid post.'], 400);
        }
        $post = get_post($post_id);
        if (!$post instanceof \WP_Post) {
            wp_send_json_error(['message' => 'Post not found.'], 404);
        }
        $focus_keyword = (string) \AutoMarketerAI\SeoData::get($post_id, 'focus_keyword');
        $updated = \AutoMarketerAI\OutboundLinks::generateForPost($post->post_content, $post_id, $focus_keyword);
        if ($updated === $post->post_content) {
            wp_send_json_error(['message' => 'Could not find suitable link opportunities in this post. Try again or add links manually.'], 422);
        }
        wp_send_json_success(['field' => 'outbound_links', 'value' => $updated]);
    }

    // --------------------------------------------------
    // AJAX -- Inject 2 internal links directly into post content
    // --------------------------------------------------
    public static function handleInjectInternalLinks(): void
    {
        check_ajax_referer('amai_seo', 'nonce');
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }
        $post_id = (int) ($_POST['post_id'] ?? 0);
        if ($post_id <= 0 || !current_user_can('edit_post', $post_id)) {
            wp_send_json_error(['message' => 'Invalid post.'], 400);
        }
        $post = get_post($post_id);
        if (!$post instanceof \WP_Post) {
            wp_send_json_error(['message' => 'Post not found.'], 404);
        }
        $links = self::buildRelatedLinks($post);
        if ($links === null) {
            wp_send_json_error(['message' => 'Not enough published posts to suggest links.'], 422);
        }
        if (empty($links)) {
            wp_send_json_error(['message' => 'No suitable related posts found.'], 422);
        }
        update_post_meta($post_id, '_amai_related_links', $links);
        wp_send_json_success(['field' => 'internal_links', 'value' => '']);
    }

    // Returns array of ['url' => string, 'title' => string] for up to 2 related posts.
    // Returns null if not enough published posts. Returns [] if no candidates found.
    public static function buildRelatedLinks(\WP_Post $post): ?array
    {
        $post_id   = $post->ID;
        $published = (int) wp_count_posts('post')->publish;
        if ($post->post_status === 'publish') {
            $published = max(0, $published - 1);
        }
        if ($published < 10) {
            return null;
        }

        // Count existing internal links in meta
        $existing_meta = get_post_meta($post_id, '_amai_related_links', true);
        $_ic = is_array($existing_meta) ? count($existing_meta) : 0;
        if ($_ic >= 2) {
            return [];
        }
        $_needed = 2 - $_ic;

        $focus_keyword = trim((string) \AutoMarketerAI\SeoData::get($post_id, 'focus_keyword'));
        $post_title    = strtolower(wp_strip_all_tags($post->post_title));
        $candidates    = get_posts([
            'post_type'      => 'post',
            'post_status'    => 'publish',
            'posts_per_page' => 40,
            'post__not_in'   => [$post_id],
            'orderby'        => 'date',
            'order'          => 'DESC',
        ]);
        if (empty($candidates)) {
            return [];
        }

        $kw_source = $focus_keyword !== '' ? $focus_keyword : $post_title;
        $kw_words  = array_values(array_unique(array_filter(
            explode(' ', strtolower($kw_source)),
            static fn(string $w): bool => strlen($w) >= 4
        )));

        $scored = [];
        foreach ($candidates as $candidate) {
            if (!$candidate instanceof \WP_Post) {
                continue;
            }
            $ctitle   = strtolower(wp_strip_all_tags($candidate->post_title));
            $cexcerpt = strtolower(wp_strip_all_tags(get_the_excerpt($candidate)));
            $haystack = $ctitle . ' ' . $cexcerpt;
            $score    = 0;
            foreach ($kw_words as $word) {
                if (str_contains($haystack, $word)) {
                    $score++;
                }
            }
            if ($focus_keyword !== '' && str_contains($ctitle, strtolower($focus_keyword))) {
                $score += 3;
            }
            $is_cornerstone = (string) \AutoMarketerAI\SeoData::get($candidate->ID, 'cornerstone');
            if ($is_cornerstone === '1') {
                $score += 5;
            }
            $scored[] = [
                'score' => $score,
                'title' => $candidate->post_title,
                'url'   => (string) get_permalink($candidate->ID),
            ];
        }

        usort($scored, static function (array $a, array $b): int {
            return $b['score'] <=> $a['score'];
        });

        $already_linked = [];
        preg_match_all('/<a[^>]+href=["\']+([^"\' >]+)["\']/i', $post->post_content, $al_matches);
        foreach ($al_matches[1] as $_h) {
            $already_linked[] = rtrim($_h, '/');
        }
        if (is_array($existing_meta)) {
            foreach ($existing_meta as $em) {
                $already_linked[] = rtrim((string) ($em['url'] ?? ''), '/');
            }
        }

        $result = [];
        foreach ($scored as $candidate) {
            if (count($result) >= $_needed) {
                break;
            }
            if (in_array(rtrim($candidate['url'], '/'), $already_linked, true)) {
                continue;
            }
            $result[] = ['url' => $candidate['url'], 'title' => $candidate['title']];
        }
        return $result;
    }

    // Injects _amai_related_links meta into post content on front-end output.
    public static function injectRelatedLinksIntoContent(string $content): string
    {
        if (!is_singular('post')) {
            return $content;
        }
        $post_id = get_the_ID();
        if (!$post_id) {
            return $content;
        }
        $links = get_post_meta($post_id, '_amai_related_links', true);
        if (!is_array($links) || empty($links)) {
            return $content;
        }
        $items = '';
        foreach ($links as $link) {
            $href  = esc_url((string) ($link['url'] ?? ''));
            $title = esc_html((string) ($link['title'] ?? ''));
            if ($href === '' || $title === '') {
                continue;
            }
            $items .= '<span class="amai-fr-item"><span class="amai-fr-arrow">&#8594;</span><a href="' . $href . '" class="amai-fr-link">' . $title . '</a></span>';
        }
        if ($items === '') {
            return $content;
        }
        $css = '<style>.amai-fr-box{background:#FDF6F3;border:1.5px solid #EDD5C8;border-radius:12px;padding:18px 22px;margin:32px 0;font-family:inherit}.amai-fr-label{font-size:.7em;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:#D97757;margin-bottom:12px}.amai-fr-item{display:flex;align-items:baseline;gap:8px;font-size:.925em;font-weight:500;padding:5px 0}.amai-fr-arrow{color:#D97757;font-size:1em;flex-shrink:0;margin-right:2px}.amai-fr-link{text-decoration:none;color:#1F2937;transition:color .15s;line-height:1.4}.amai-fr-link:hover{color:#D97757}</style>';
        $blocks = $css . '<div class="amai-fr-box"><div class="amai-fr-label">Further Reading</div>' . $items . '</div>';
        // Insert after first </p> that is not an H tag
        $home   = home_url();
        $offset = 0;
        while (($pos = strpos($content, '</p>', $offset)) !== false) {
            $segment       = substr($content, $offset, $pos - $offset + 4);
            $segment_trim  = ltrim($segment);
            if (!preg_match('/^<h[1-6][\s>]/i', $segment_trim) && strlen(wp_strip_all_tags($segment)) > 60) {
                $insert_at = $pos + 4;
                return substr($content, 0, $insert_at) . "
" . $blocks . substr($content, $insert_at);
            }
            $offset = $pos + 4;
        }
        // Fallback: append at end
        return $content . "
" . $blocks;
    }

    // --------------------------------------------------
    // Build post content with up to 2 internal links injected
    // Shared by AJAX handler and BgOptimizer
    // --------------------------------------------------
    // AJAX — Fix It: call Core to rewrite a field
    // --------------------------------------------------
    // --------------------------------------------------
    // Featured image picker -- media library grid
    // --------------------------------------------------
    public static function handleMediaLibrary(): void
    {
        check_ajax_referer('amai_seo', 'nonce');
        if (!current_user_can('upload_files')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }
        $search = sanitize_text_field(wp_unslash((string) ($_POST['search'] ?? '')));
        $args   = [
            'post_type'      => 'attachment',
            'post_mime_type' => 'image',
            'post_status'    => 'inherit',
            'posts_per_page' => 24,
            'orderby'        => 'date',
            'order'          => 'DESC',
        ];
        if ($search !== '') {
            $args['s'] = $search;
        }
        $query = new \WP_Query($args);
        $items = [];
        foreach ($query->posts as $att) {
            if (!($att instanceof \WP_Post)) {
                continue;
            }
            $thumb = (string) (wp_get_attachment_image_url($att->ID, 'thumbnail') ?: '');
            $full  = (string) (wp_get_attachment_image_url($att->ID, 'large') ?: '');
            if ($thumb === '') {
                continue;
            }
            $items[] = [
                'id'    => $att->ID,
                'thumb' => $thumb,
                'full'  => $full !== '' ? $full : $thumb,
                'title' => (string) get_the_title($att->ID),
            ];
        }
        wp_send_json_success(['items' => $items]);
    }

    // --------------------------------------------------
    // Featured image picker -- set existing media as featured
    // --------------------------------------------------
    public static function handleSetMediaFeatured(): void
    {
        check_ajax_referer('amai_seo', 'nonce');
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }
        $post_id       = (int) ($_POST['post_id']       ?? 0);
        $attachment_id = (int) ($_POST['attachment_id'] ?? 0);
        if ($post_id <= 0 || $attachment_id <= 0) {
            wp_send_json_error(['message' => 'Missing parameters.'], 400);
        }
        if (!current_user_can('edit_post', $post_id)) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }
        set_post_thumbnail($post_id, $attachment_id);
        $url = (string) (wp_get_attachment_image_url($attachment_id, 'large') ?: '');
        wp_send_json_success(['url' => $url, 'attachment_id' => $attachment_id]);
    }

    // --------------------------------------------------
    // Featured image picker -- Unsplash search
    // --------------------------------------------------
    public static function handleUnsplashSearch(): void
    {
        check_ajax_referer('amai_seo', 'nonce');
        if (!current_user_can('upload_files')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }
        $query = sanitize_text_field(wp_unslash((string) ($_POST['query'] ?? '')));
        if ($query === '') {
            wp_send_json_error(['message' => 'Query is required.'], 400);
        }
        $results = Unsplash::searchMultiple($query, 9);
        wp_send_json_success(['results' => $results]);
    }

    // --------------------------------------------------
    // Featured image picker -- Unsplash: download + set as featured
    // --------------------------------------------------
    public static function handleUnsplashSet(): void
    {
        check_ajax_referer('amai_seo', 'nonce');
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }
        $post_id           = (int) ($_POST['post_id'] ?? 0);
        $url               = esc_url_raw(wp_unslash((string) ($_POST['url']               ?? '')));
        $photographer      = sanitize_text_field(wp_unslash((string) ($_POST['photographer']      ?? '')));
        $profile_url       = esc_url_raw(wp_unslash((string) ($_POST['profile_url']       ?? '')));
        $download_location = esc_url_raw(wp_unslash((string) ($_POST['download_location'] ?? '')));
        if ($post_id <= 0 || $url === '') {
            wp_send_json_error(['message' => 'Missing parameters.'], 400);
        }
        if (!current_user_can('edit_post', $post_id)) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }
        if ($download_location !== '') {
            Unsplash::trackDownload($download_location);
        }
        $attachment_id = Unsplash::downloadToMedia($url, $photographer, $post_id, $profile_url);
        if ($attachment_id === 0) {
            wp_send_json_error(['message' => 'Failed to download image. Please try another.'], 502);
        }
        set_post_thumbnail($post_id, $attachment_id);
        $local_url = (string) (wp_get_attachment_image_url($attachment_id, 'large') ?: '');
        wp_send_json_success(['url' => $local_url, 'attachment_id' => $attachment_id]);
    }

    // --------------------------------------------------
    // Featured image picker -- AI generate + set as featured
    // --------------------------------------------------
    public static function handleFeaturedImageAi(): void
    {
        check_ajax_referer('amai_seo', 'nonce');
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }
        $post_id = (int) ($_POST['post_id'] ?? 0);
        if ($post_id <= 0) {
            wp_send_json_error(['message' => 'Missing post ID.'], 400);
        }
        if (!current_user_can('edit_post', $post_id)) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }
        $post    = get_post($post_id);
        $title   = $post instanceof \WP_Post ? (string) $post->post_title : '';
        $keyword = (string) (SeoData::get($post_id, 'focus_keyword') ?? '');
        $prompt  = 'A high-quality professional photograph for a blog post titled "' . $title . '"';
        if ($keyword !== '') {
            $prompt .= ', about ' . $keyword;
        }
        $prompt .= '. Bright, clean, modern style. No text or logos.';
        $local_url = ImageProvider::resolveForPost($prompt);
        if ($local_url === null) {
            wp_send_json_error(['message' => 'Image generation failed. Check that an image provider is active and configured.'], 502);
        }
        $attachment_id = (int) attachment_url_to_postid($local_url);
        if ($attachment_id > 0) {
            set_post_thumbnail($post_id, $attachment_id);
        }
        wp_send_json_success(['url' => $local_url, 'attachment_id' => $attachment_id]);
    }

    public static function handleFixIt(): void
    {
        check_ajax_referer('amai_seo', 'nonce');

        if (!current_user_can('edit_posts')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }

        $post_id    = (int) ($_POST['post_id']    ?? 0);
        $field      = sanitize_key((string) ($_POST['field']       ?? ''));
        $issue_index = (int) ($_POST['issue_index'] ?? -1);
        $fix_mode   = sanitize_key((string) ($_POST['fix_mode']   ?? ''));

        if ($post_id <= 0 || $field === '' || $issue_index < 0) {
            wp_send_json_error(['message' => 'Missing parameters.'], 400);
        }
        $prompts         = get_transient('amai_seo_prompts_' . $post_id);
        $transient_entry = (is_array($prompts) && isset($prompts[$issue_index])) ? $prompts[$issue_index] : null;
        $fix_prompt      = is_array($transient_entry) ? (string) ($transient_entry['prompt'] ?? '') : '';
        $allowed_indexes = (is_array($transient_entry) && !empty($transient_entry['allowed_indexes']))
            ? array_map('intval', $transient_entry['allowed_indexes'])
            : [];
        if ($fix_prompt === '') {
            wp_send_json_error(['message' => 'Fix prompt not found. Please re-analyze and try again.'], 400);
        }

        if (!current_user_can('edit_post', $post_id)) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }

        $result = self::callCoreFixIt($fix_prompt);
        if ($result === null) {
            wp_send_json_error(['message' => 'AI generation failed. Please try again.'], 502);
        }

        // Save to the correct destination based on field
        if ($field === 'post_title') {
            wp_update_post(['ID' => $post_id, 'post_title' => $result]);
        } elseif ($field === 'post_content') {
            if ($fix_mode === 'paragraphs') {
                // Paragraph-surgical mode -- apply patches server-side, return full content as direct
                $clean   = trim(preg_replace('/^```json|```$/m', '', $result) ?? $result);
                $patches = json_decode($clean, true);
                if (!is_array($patches)) {
                    wp_send_json_error(['message' => 'Invalid patch response from AI.'], 502);
                    return;
                }
                // Strip any patches outside the allowed thin-section indexes
                if (!empty($allowed_indexes)) {
                    $patches = array_values(array_filter($patches, function ($p) use ($allowed_indexes) {
                        return isset($p['index']) && in_array((int) $p['index'], $allowed_indexes, true);
                    }));
                }
                if (empty($patches)) {
                    wp_send_json_error(['message' => 'AI returned no valid patches. Please try again.'], 502);
                    return;
                }
                // Build index->fixed map
                $patch_map = [];
                foreach ($patches as $p) {
                    if (isset($p['index']) && !empty($p['fixed'] ?? $p['html'] ?? '')) {
                        $patch_map[(int) $p['index']] = $p['fixed'] ?? $p['html'];
                    }
                }
                // Apply patches to actual post_content by counting <p> tags
                // Falls back to newline-splitting for plain-text posts with no <p> tags
                $post        = get_post($post_id);
                $raw_content = $post->post_content ?? '';
                $has_p_tags  = (bool) preg_match('/<p(\s[^>]*)?>.*?<\/p>/is', $raw_content);
                if ($has_p_tags) {
                    $counter = 0;
                    $updated = preg_replace_callback('/<p(\s[^>]*)?>([\s\S]*?)<\/p>/i', function ($m) use (&$counter, $patch_map) {
                        $idx = $counter++;
                        return isset($patch_map[$idx]) ? $patch_map[$idx] : $m[0];
                    }, $raw_content);
                } else {
                    // Plain-text post -- split on double newlines, patch by index, rejoin
                    $raw_content = str_replace("\r", '', $raw_content);
                    $chunks      = preg_split('/\n{2,}/', $raw_content, -1, PREG_SPLIT_NO_EMPTY) ?: [];
                    foreach ($patch_map as $pidx => $fixed_html) {
                        if (isset($chunks[$pidx])) {
                            $chunks[$pidx] = trim(preg_replace('/<\/?p[^>]*>/i', '', $fixed_html));
                        }
                    }
                    $updated = implode("\n\n", $chunks);
                }
                if ($updated === null || $updated === $raw_content) {
                    wp_send_json_error(['message' => 'Patch application produced no changes. Please try again.'], 502);
                    return;
                }
                $updated = str_replace("\r", '', $updated);
                wp_send_json_success(['field' => $field, 'mode' => 'direct', 'value' => $updated]);
                return;
            }
            $result = str_replace("\r", '', $result);
            // Direct mode -- return to JS for TinyMCE, user saves via WP Update button
            $result = self::wrapBareParagraphs($result);
            $result = self::enforceWordLimit($result, 1800);
            wp_send_json_success(['field' => $field, 'mode' => 'direct', 'value' => $result]);
            return;
        } elseif ($field === 'post_content_prepend') {
            // Prepend AI result (an H2 tag) to the existing post content
            $post     = get_post($post_id);
            $h2_clean = trim(preg_replace('/^```html?|```$/m', '', $result) ?? $result);
            $h2_clean = strip_tags($h2_clean, '<h2>');
            if (preg_match('/<h2[^>]*>.*?<\/h2>/is', $h2_clean, $m)) {
                $new_content = $m[0] . "\n" . $post->post_content;
                $new_content = \AutoMarketerAI\LinkPreserver::preserve($post_id, $new_content);
                wp_update_post(['ID' => $post_id, 'post_content' => $new_content]);
                $result = $m[0];
            }
        } elseif ($field === 'post_slug') {
            $slug = sanitize_title($result);
            wp_update_post(['ID' => $post_id, 'post_name' => $slug]);
            $result = $slug;
        } elseif ($field === 'post_first_para') {
            // JS splices this into the first paragraph - no server save needed
        } elseif ($field === 'post_tags') {
            $raw_tags   = array_map('trim', explode(',', $result));
            $clean_tags = array_filter($raw_tags, function (string $t): bool {
                return $t !== '';
            });
            wp_set_post_tags($post_id, array_values($clean_tags), false);
            $result = implode(', ', $clean_tags);
        }
        // Note: meta fields (meta_title, meta_description, canonical_url) are NOT
        // saved here. JS saves them via amai_seo_save_field only when user clicks Accept.
        // Validate meta_description -- if over 200 chars it is likely AI reasoning, not a description.
        if ($field === 'meta_description' && mb_strlen($result) > 200) {
            wp_send_json_error(['message' => 'AI returned an invalid meta description. Please try again.'], 502);
            return;
        }
        // Validate meta_title -- if over 100 chars it is likely AI reasoning.
        if ($field === 'meta_title' && mb_strlen($result) > 100) {
            wp_send_json_error(['message' => 'AI returned an invalid SEO title. Please try again.'], 502);
            return;
        }
        wp_send_json_success(['field' => $field, 'value' => $result]);
    }

    // --------------------------------------------------
    // AJAX -- Save a single SEO field and return fresh scores + issues
    // --------------------------------------------------
    public static function handleSaveField(): void
    {
        check_ajax_referer('amai_seo', 'nonce');

        if (!current_user_can('edit_posts')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }

        $post_id = (int) ($_POST['post_id'] ?? 0);
        $field   = sanitize_key((string) ($_POST['field'] ?? ''));
        $raw_value = wp_unslash((string) ($_POST['value'] ?? ''));
        $value   = in_array($field, ['post_content', 'post_first_para'], true)
            ? wp_kses_post($raw_value)
            : sanitize_textarea_field($raw_value);

        if ($post_id <= 0 || $field === '') {
            wp_send_json_error(['message' => 'Missing parameters.'], 400);
        }

        if (!current_user_can('edit_post', $post_id)) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }

        if ($field === 'post_content') {
            $clean = str_replace("\r", '', $value);
            $clean = \AutoMarketerAI\LinkPreserver::preserve($post_id, $clean);
            wp_update_post(['ID' => $post_id, 'post_content' => $clean]);
        } elseif ($field === 'post_first_para') {
            $post = get_post($post_id);
            if ($post instanceof \WP_Post) {
                $updated = preg_replace('/<p(\\b[^>]*)>.*?<\/p>/is', '<p$1>' . $value . '</p>', $post->post_content, 1);
                $updated = \AutoMarketerAI\LinkPreserver::preserve($post_id, $updated);
                wp_update_post(['ID' => $post_id, 'post_content' => $updated]);
            }
        } else {
            if ($field === 'focus_keyword' && mb_strlen($value) > 100) {
                wp_send_json_error(['message' => 'Invalid keyword value.'], 400);
            }
            SeoData::set($post_id, $field, $value);
        }
        $result = SeoAnalyzer::analyze($post_id);
        wp_send_json_success($result);
    }

    // --------------------------------------------------
    // AJAX — Rescan: re-run analyzer and return fresh scores + issues
    // --------------------------------------------------
    public static function handleRescan(): void
    {
        check_ajax_referer('amai_seo', 'nonce');

        if (!current_user_can('edit_posts')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
        }

        $post_id = (int) ($_POST['post_id'] ?? 0);
        if ($post_id <= 0 || !current_user_can('edit_post', $post_id)) {
            wp_send_json_error(['message' => 'Invalid post.'], 400);
        }

        $result = SeoAnalyzer::analyze($post_id);
        wp_send_json_success($result);
    }

    // --------------------------------------------------
    // Call Core /api/seo/fix-field
    // --------------------------------------------------
    // --------------------------------------------------
    // Wrap bare text blocks between block-level tags in <p> tags.
    // AI sometimes returns text without <p> wrappers between headings.
    // --------------------------------------------------
    // --------------------------------------------------
    // Enforce a hard word count ceiling by trimming back to the last </p>
    // at or before the limit. Strips tags for counting only.
    // --------------------------------------------------
    private static function enforceWordLimit(string $html, int $maxWords): string
    {
        $stripped  = wp_strip_all_tags($html);
        $wordCount = str_word_count($stripped);
        if ($wordCount <= $maxWords) {
            return $html;
        }
        $parts = preg_split('!</p>!i', $html);
        if ($parts === false || count($parts) <= 1) {
            return $html;
        }
        $result = '';
        foreach ($parts as $part) {
            if ($part === '') {
                continue;
            }
            $candidate = $result . $part . '</p>';
            $count = str_word_count(wp_strip_all_tags($candidate));
            if ($count > $maxWords && $result !== '') {
                break;
            }
            $result = $candidate;
        }
        return $result !== '' ? rtrim($result) : $html;
    }

    private static function wrapBareParagraphs(string $html): string
    {
        $block_tags = 'p|h[1-6]|ul|ol|li|blockquote|div|pre|table';
        // Split on block-level open/close tags, keeping delimiters
        $parts = preg_split('/(<(?:' . $block_tags . ')[^>]*>|<\/(?:' . $block_tags . ')>)/i', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
        if ($parts === false) {
            return $html;
        }
        $result    = '';
        $in_block  = false;
        foreach ($parts as $part) {
            if (preg_match('/^<(?:' . $block_tags . ')[^>]*>$/i', $part)) {
                $in_block = true;
                $result  .= $part;
            } elseif (preg_match('/^<\/(?:' . $block_tags . ')>$/i', $part)) {
                $in_block = false;
                $result  .= $part;
            } else {
                // Text node -- wrap each newline-separated chunk in <p> if not inside a block
                if (!$in_block) {
                    $chunks = preg_split('/\n{1,}/', $part) ?: [$part];
                    foreach ($chunks as $chunk) {
                        $trimmed = trim($chunk);
                        if ($trimmed !== '') {
                            $result .= '<p>' . $trimmed . '</p>';
                        }
                    }
                } else {
                    $result .= $part;
                }
            }
        }
        return $result;
    }

    public static function callCoreFixIt(string $fix_prompt): ?string
    {
        if (!defined('AMAI_HMAC_SECRET') || AMAI_HMAC_SECRET === '') {
            return null;
        }
        if (!defined('AMAI_CORE_URL') || AMAI_CORE_URL === '') {
            return null;
        }

        $method    = 'POST';
        $path      = '/api/seo/fix-field';
        $url       = rtrim(AMAI_CORE_URL, '/') . $path;
        $timestamp = (string) time();
        $nonce     = bin2hex(random_bytes(16));
        $tenant    = amai_tenant_id();
        $key_id    = 'WP_PROXY';

        $payload   = ['prompt' => $fix_prompt];
        $body      = (string) json_encode($payload);
        $tenantSecret = (string) get_option('amai_tenant_hmac_secret', '');
        if ($tenantSecret === '') {
            return null;
        }
        $secret    = hex2bin($tenantSecret);
        $body_b64  = base64_encode(hash('sha256', $body, true));
        $canonical = implode('|', [$method, $path, $timestamp, $nonce, $tenant, $key_id, $body_b64]);
        $signature = rtrim(base64_encode(hash_hmac('sha256', $canonical, $secret, true)), "\n");

        $response = wp_remote_post($url, [
            'timeout' => 360,
            'headers' => [
                'Content-Type'       => 'application/json',
                'X-Amai-Tenant'      => $tenant,
                'X-Amai-Key-Id'      => $key_id,
                'X-Amai-Timestamp'   => $timestamp,
                'X-Amai-Nonce'       => $nonce,
                'X-Amai-Body-SHA256' => $body_b64,
                'X-Amai-Signature'   => $signature,
            ],
            'body' => $body,
        ]);

        if (is_wp_error($response)) {
            return null;
        }

        $code = (int) wp_remote_retrieve_response_code($response);
        $data = json_decode(wp_remote_retrieve_body($response), true);

        if ($code !== 200 || !is_array($data) || empty($data['result'])) {
            return null;
        }

        return trim((string) $data['result']);
    }

    // --------------------------------------------------
    // Score CSS class helper
    // --------------------------------------------------
    private static function scoreClass(int $score): string
    {
        if ($score >= 70) {
            return 'amai-seo-score-dial--good';
        }
        if ($score >= 40) {
            return 'amai-seo-score-dial--ok';
        }
        return 'amai-seo-score-dial--poor';
    }

    // --------------------------------------------------
    // Check if any issue for a given field has a fix prompt
    // --------------------------------------------------
    private static function hasFixPrompt(array $issues, string $field): bool
    {
        foreach ($issues as $issue) {
            if ($issue['field'] === $field && isset($issue['issue_index']) && $issue['issue_index'] !== '') {
                return true;
            }
        }
        return false;
    }

    // --------------------------------------------------
    // Get the first fix prompt for a given field
    // --------------------------------------------------
    private static function getFixPrompt(array $issues, string $field): string
    {
        foreach ($issues as $issue) {
            if ($issue['field'] === $field && isset($issue['issue_index']) && $issue['issue_index'] !== '') {
                return (string) ($issue['issue_index'] ?? '');
            }
        }
        return '';
    }
}