<?php
/**
 * DBA_SEO_Sniper_Router — Routes keyword to the correct bot + theme
 *
 * Pipeline:
 *   1. Match keyword → theme/subject → responsible bot
 *   2. Crawl SERP → analyze competitors → build brief
 *   3. Store brief as transient → force theme → trigger bot
 *   4. POST-GENERATION: verify featured image, word count, self-plagiarism
 *   5. Report detailed results back to admin
 *
 * Progress is written to transient at each step for real-time polling.
 *
 * @since 2.16.0
 */
if ( ! defined( 'ABSPATH' ) ) exit;

class DBA_SEO_Sniper_Router {

    const PROGRESS_KEY = 'dba_sniper_progress';

    /* ══════════════════════════════════════════════════════════════
     *  PROGRESS TRACKING — written to transient, polled by JS
     * ══════════════════════════════════════════════════════════════ */

    private static function progress( $step, $total, $label, $detail = '' ) {
        $pct = round( ( $step / $total ) * 100 );
        set_transient( self::PROGRESS_KEY, array(
            'step'    => $step,
            'total'   => $total,
            'pct'     => $pct,
            'label'   => $label,
            'detail'  => $detail,
            'time'    => current_time( 'mysql' ),
        ), 600 );
        error_log( "DBA Sniper [{$step}/{$total}] {$label}" . ( $detail ? " — {$detail}" : '' ) );
    }

    public static function get_progress() {
        return get_transient( self::PROGRESS_KEY ) ?: array( 'pct' => 0, 'label' => 'Waiting...', 'step' => 0, 'total' => 0 );
    }

    public static function clear_progress() {
        delete_transient( self::PROGRESS_KEY );
    }

    /* ══════════════════════════════════════════════════════════════
     *  BOT CATALOG
     * ══════════════════════════════════════════════════════════════ */

    public static function get_all_bots() {
        $bots = array();

        // 1. LuxStrat EN
        $bots['luxstrat'] = array(
            'label' => 'LuxStrat EN — Luxembourg Structuring', 'short' => 'LuxStrat EN',
            'status' => class_exists( 'DBA_LuxStrat_Generator' ) ? 'active' : 'not_loaded',
            'wired' => true, 'languages' => array( 'en' ), 'themes' => array(),
        );
        if ( class_exists( 'DBA_LuxStrat_Registry' ) ) {
            foreach ( DBA_LuxStrat_Registry::get_themes() as $slug => $cfg ) {
                $bots['luxstrat']['themes'][ $slug ] = $cfg['short_label'] ?? $cfg['label'] ?? $slug;
            }
        }

        // 2. LuxStrat-Intl (FR, IT, …)
        if ( class_exists( 'DBA_LuxStrat_Intl_Loader' ) ) {
            foreach ( DBA_LuxStrat_Intl_Loader::get_languages() as $code => $lang_cfg ) {
                $bot_id = 'luxstrat-intl-' . $code;
                $bots[ $bot_id ] = array(
                    'label' => 'LuxStrat ' . strtoupper( $code ) . ' — ' . ( $lang_cfg['label'] ?? $code ),
                    'short' => 'LuxStrat ' . strtoupper( $code ),
                    'status' => class_exists( 'DBA_LuxStrat_Intl_Generator' ) ? 'active' : 'not_loaded',
                    'wired' => true, 'languages' => array( $code ), 'themes' => array(),
                    'lang_config' => $lang_cfg,
                );
                foreach ( $lang_cfg['themes'] ?? array() as $slug => $cfg ) {
                    $bots[ $bot_id ]['themes'][ $slug ] = ( $lang_cfg['flag'] ?? '' ) . ' ' . ( $cfg['short_label'] ?? $slug );
                }
            }
        }

        // 3. GeoBot EU
        $bots['geobot'] = array(
            'label' => 'GeoBot EU — France, Italy, Spain, Portugal', 'short' => 'GeoBot EU',
            'status' => class_exists( 'DBA_GeoBot_Generator' ) ? 'active' : 'not_loaded',
            'wired' => false, 'languages' => array( 'en' ), 'themes' => array(),
        );
        if ( class_exists( 'DBA_GeoBot_Registry' ) ) {
            foreach ( DBA_GeoBot_Registry::get_countries() as $slug => $cfg ) {
                $bots['geobot']['themes'][ $slug ] = $cfg['label'] ?? ucfirst( str_replace( '_', ' ', $slug ) );
            }
        }

        // 4. Eastern Europe
        $bots['eeurope'] = array(
            'label' => 'EEurope — Eastern European Countries', 'short' => 'EEurope',
            'status' => class_exists( 'DBA_EEurope_Registry' ) ? 'active' : 'not_loaded',
            'wired' => false, 'languages' => array( 'en' ), 'themes' => array(),
        );
        if ( class_exists( 'DBA_EEurope_Registry' ) ) {
            foreach ( DBA_EEurope_Registry::get_countries() as $slug => $cfg ) {
                $bots['eeurope']['themes'][ $slug ] = $cfg['label'] ?? ucfirst( str_replace( '_', ' ', $slug ) );
            }
        }

        // 5. LATAM
        $bots['latam'] = array(
            'label' => 'LATAM — Latin American Countries', 'short' => 'LATAM',
            'status' => class_exists( 'DBA_LATAM_Registry' ) ? 'active' : 'not_loaded',
            'wired' => false, 'languages' => array( 'es', 'pt', 'en' ), 'themes' => array(),
        );
        if ( class_exists( 'DBA_LATAM_Registry' ) ) {
            foreach ( DBA_LATAM_Registry::get_countries() as $slug => $cfg ) {
                $bots['latam']['themes'][ $slug ] = $cfg['label'] ?? ucfirst( str_replace( '_', ' ', $slug ) );
            }
        }

        // 6. Startup Hub
        $bots['startup-hub'] = array(
            'label' => 'Startup Hub — US Startup Ecosystem', 'short' => 'Startup Hub',
            'status' => class_exists( 'DBA_Startup_Hub_Generator' ) ? 'active' : 'not_loaded',
            'wired' => false, 'languages' => array( 'en' ), 'themes' => array(),
        );
        if ( class_exists( 'DBA_Startup_Hub_Registry' ) && method_exists( 'DBA_Startup_Hub_Registry', 'get_themes' ) ) {
            foreach ( DBA_Startup_Hub_Registry::get_themes() as $slug => $cfg ) {
                $bots['startup-hub']['themes'][ $slug ] = $cfg['short_label'] ?? $cfg['label'] ?? $slug;
            }
        }

        // 7. NewsBot
        $bots['newsbot'] = array(
            'label' => 'NewsBot — PE / Capital Markets US', 'short' => 'NewsBot',
            'status' => class_exists( 'DBA_NewsBot_Generator' ) ? 'active' : 'not_loaded',
            'wired' => false, 'languages' => array( 'en' ), 'themes' => array(),
        );

        return $bots;
    }

    /* ══════════════════════════════════════════════════════════════
     *  RESOLVE — auto-detect best bot + theme for keyword
     * ══════════════════════════════════════════════════════════════ */

    public static function resolve( $keyword, $language ) {
        $kw_lower = mb_strtolower( trim( $keyword ) );
        $bots     = self::get_all_bots();
        $best     = null;
        $best_score = 0;

        foreach ( $bots as $bot_id => $bot ) {
            if ( $bot['status'] !== 'active' ) continue;
            $lang_match = in_array( $language, $bot['languages'] ?? array() );

            foreach ( $bot['themes'] ?? array() as $slug => $label ) {
                $score = 0;
                if ( $kw_lower === mb_strtolower( $slug ) ) $score += 200;
                if ( stripos( $label, $kw_lower ) !== false ) $score += 80;
                if ( stripos( $kw_lower, mb_strtolower( $slug ) ) !== false ) $score += 60;
                if ( stripos( mb_strtolower( $slug ), $kw_lower ) !== false ) $score += 60;
                $score += self::score_theme_keywords( $kw_lower, $bot_id, $slug );
                if ( $lang_match && $score > 0 ) $score += 50;
                if ( ! empty( $bot['wired'] ) && $score > 0 ) $score += 30;

                if ( $score > $best_score ) {
                    $best_score = $score;
                    $best = array(
                        'bot_id' => $bot_id, 'theme_slug' => $slug, 'theme_label' => $label,
                        'bot_label' => $bot['short'] ?? $bot['label'],
                        'lang' => $lang_match ? $language : ( $bot['languages'][0] ?? 'en' ),
                        'wired' => ! empty( $bot['wired'] ), 'score' => $score,
                    );
                }
            }
        }

        if ( ! $best ) {
            return new WP_Error( 'no_match', 'No bot/theme found for "' . $keyword . '" (' . $language . ').' );
        }
        return $best;
    }

    private static function score_theme_keywords( $kw_lower, $bot_id, $theme_slug ) {
        $score = 0;
        $keywords = array();

        if ( $bot_id === 'luxstrat' && class_exists( 'DBA_LuxStrat_Registry' ) ) {
            $theme = DBA_LuxStrat_Registry::get_theme( $theme_slug );
            $keywords = $theme['keywords'] ?? array();
        }
        if ( strpos( $bot_id, 'luxstrat-intl-' ) === 0 && class_exists( 'DBA_LuxStrat_Intl_Loader' ) ) {
            $lang_code = str_replace( 'luxstrat-intl-', '', $bot_id );
            $lang_cfg  = DBA_LuxStrat_Intl_Loader::get_language( $lang_code );
            if ( $lang_cfg && isset( $lang_cfg['themes'][ $theme_slug ]['keywords'] ) ) {
                $keywords = $lang_cfg['themes'][ $theme_slug ]['keywords'];
            }
        }

        foreach ( $keywords as $tk ) {
            $tk_lower = mb_strtolower( $tk );
            if ( $kw_lower === $tk_lower ) { $score += 100; break; }
            if ( strpos( $tk_lower, $kw_lower ) !== false ) $score += 40;
            if ( strpos( $kw_lower, $tk_lower ) !== false ) $score += 25;
        }
        return $score;
    }

    /* ══════════════════════════════════════════════════════════════
     *  EXECUTE — Full Sniper pipeline with progress tracking
     * ══════════════════════════════════════════════════════════════ */

    public static function execute( $keyword, $language, $bot_id, $theme_slug ) {
        $keyword = trim( $keyword );
        $total_steps = 13;

        self::progress( 1, $total_steps, '🔍 Crawling Google top 20 via Serper API...', $keyword );

        $bots = self::get_all_bots();
        if ( ! isset( $bots[ $bot_id ] ) ) return new WP_Error( 'unknown_bot', 'Bot "' . $bot_id . '" not found.' );
        $bot = $bots[ $bot_id ];
        if ( $bot['status'] !== 'active' ) return new WP_Error( 'bot_not_loaded', 'Bot "' . $bot_id . '" is not loaded.' );
        if ( empty( $bot['wired'] ) ) return new WP_Error( 'bot_not_wired', 'Bot "' . ( $bot['short'] ?? $bot_id ) . '" is not wired for Sniper.' );

        // Step 1: Crawl SERP
        $serp_data = DBA_SEO_Sniper_Crawler::crawl_serp( $keyword, $language );
        if ( is_wp_error( $serp_data ) ) { self::clear_progress(); return $serp_data; }
        $serp = $serp_data['organic'] ?? $serp_data; // backward compat

        self::progress( 2, $total_steps, '📊 Analyzing competitors...', count( $serp ) . ' results found' );

        // Step 2: Analyze (includes relevance filtering)
        $analysis = DBA_SEO_Sniper_Analyzer::analyze( $keyword, $serp, $language );
        $comp_count = count( $analysis['competitors'] ?? array() );
        $filtered_count = count( $analysis['filtered_out'] ?? array() );

        self::progress( 3, $total_steps, '🚫 Relevance filter complete', $comp_count . ' relevant, ' . $filtered_count . ' filtered out' );

        self::progress( 4, $total_steps, '🎯 Building competitive intelligence brief...', 'Target: ' . ( $analysis['metrics']['target_word_count'] ?? '?' ) . ' words' );

        // Step 3: Build and store brief
        $brief = self::build_brief( $keyword, $language, $analysis );
        set_transient( 'dba_sniper_brief', $brief, 1800 );

        self::progress( 5, $total_steps, '🤖 Routing to ' . ( $bot['short'] ?? $bot_id ) . '...', 'Theme: ' . $theme_slug );

        // Step 4: Trigger bot
        self::progress( 6, $total_steps, '✍️ GPT-4 generating article...', 'This takes 30-90 seconds' );

        $bot_result = null;
        if ( $bot_id === 'luxstrat' ) {
            $bot_result = self::trigger_luxstrat_en( $theme_slug, $analysis );
        } elseif ( strpos( $bot_id, 'luxstrat-intl-' ) === 0 ) {
            $lang_code = str_replace( 'luxstrat-intl-', '', $bot_id );
            $bot_result = self::trigger_luxstrat_intl( $lang_code, $theme_slug, $bot, $analysis );
        } else {
            self::clear_progress();
            return new WP_Error( 'not_implemented', 'Trigger for "' . $bot_id . '" not implemented.' );
        }

        if ( is_wp_error( $bot_result ) ) {
            self::progress( $total_steps, $total_steps, '❌ Generation failed', $bot_result->get_error_message() );
            return $bot_result;
        }

        $post_id = $bot_result['post_id'] ?? 0;
        if ( ! $post_id ) {
            self::clear_progress();
            return new WP_Error( 'no_post', 'Bot returned success but no post ID.' );
        }

        // ══════════════════════════════════════════════════════════
        //  POST-GENERATION VERIFICATION
        // ══════════════════════════════════════════════════════════

        self::progress( 9, $total_steps, '🔎 Verifying generated post #' . $post_id . '...' );

        $verification = self::verify_post( $post_id, $keyword, $theme_slug, $language );

        self::progress( 11, $total_steps, '🛡️ Self-plagiarism check...', 'Comparing against last 10 theme posts' );

        $self_plag = self::check_self_plagiarism( $post_id, $theme_slug, $bot_id, $language );
        $verification['self_plagiarism'] = $self_plag;

        self::progress( 13, $total_steps, '✅ Complete!', 'Post #' . $post_id . ' created' );

        // Merge verification into result
        $bot_result['verification'] = $verification;
        return $bot_result;
    }

    /* ══════════════════════════════════════════════════════════════
     *  POST-GENERATION VERIFICATION
     * ══════════════════════════════════════════════════════════════ */

    /**
     * Verify the generated post has all expected elements.
     */
    private static function verify_post( $post_id, $keyword, $theme_slug, $language ) {
        $post = get_post( $post_id );
        if ( ! $post ) return array( 'error' => 'Post not found' );

        $content    = $post->post_content;
        $word_count = str_word_count( wp_strip_all_tags( $content ) );
        $has_image  = has_post_thumbnail( $post_id );
        $thumb_id   = get_post_thumbnail_id( $post_id );
        $thumb_url  = $thumb_id ? wp_get_attachment_url( $thumb_id ) : '';

        // Check Yoast SEO
        $yoast_title = get_post_meta( $post_id, '_yoast_wpseo_title', true );
        $yoast_desc  = get_post_meta( $post_id, '_yoast_wpseo_metadesc', true );
        $yoast_focus = get_post_meta( $post_id, '_yoast_wpseo_focuskw', true );
        $has_yoast   = ! empty( $yoast_title ) || ! empty( $yoast_desc ) || ! empty( $yoast_focus );

        // Count headings
        preg_match_all( '/<h2[^>]*>/i', $content, $h2_matches );
        $h2_count = count( $h2_matches[0] );

        // Keyword in title
        $kw_in_title = stripos( $post->post_title, $keyword ) !== false;

        // FAQ section
        $has_faq = stripos( $content, 'faq' ) !== false || preg_match( '/<h[23][^>]*>.*?(frequently|faq|question)/i', $content );

        $checks = array(
            'featured_image' => array( 'pass' => $has_image, 'label' => 'DALL-E Featured Image', 'detail' => $has_image ? 'Set (' . basename( $thumb_url ) . ')' : 'MISSING — no featured image set' ),
            'word_count'     => array( 'pass' => $word_count >= 800, 'label' => 'Word Count', 'detail' => $word_count . ' words' ),
            'headings'       => array( 'pass' => $h2_count >= 3, 'label' => 'H2 Structure', 'detail' => $h2_count . ' H2 headings' ),
            'yoast_seo'      => array( 'pass' => $has_yoast, 'label' => 'Yoast SEO Meta', 'detail' => $has_yoast ? 'Title + description + focus keyword set' : 'MISSING — no Yoast meta found' ),
            'keyword_title'  => array( 'pass' => $kw_in_title, 'label' => 'Keyword in Title', 'detail' => $kw_in_title ? 'Found' : 'NOT found in title' ),
            'faq_section'    => array( 'pass' => (bool) $has_faq, 'label' => 'FAQ Section', 'detail' => $has_faq ? 'Present' : 'Not detected' ),
            'post_status'    => array( 'pass' => true, 'label' => 'Post Status', 'detail' => $post->post_status ),
        );

        $pass_count = count( array_filter( $checks, function( $c ) { return $c['pass']; } ) );
        $total      = count( $checks );

        return array(
            'post_id'    => $post_id,
            'title'      => $post->post_title,
            'word_count' => $word_count,
            'checks'     => $checks,
            'pass_count' => $pass_count,
            'total'      => $total,
            'score'      => round( ( $pass_count / $total ) * 100 ),
        );
    }

    /* ══════════════════════════════════════════════════════════════
     *  SELF-PLAGIARISM CHECK — Gate C
     *
     *  Compares new post against last 10 posts in the same theme
     *  using Jaccard trigram shingling (same method as Gate A).
     * ══════════════════════════════════════════════════════════════ */

    private static function check_self_plagiarism( $post_id, $theme_slug, $bot_id, $language ) {
        $post = get_post( $post_id );
        if ( ! $post ) return array( 'status' => 'skip', 'reason' => 'Post not found' );

        $new_content = wp_strip_all_tags( $post->post_content );
        if ( mb_strlen( $new_content ) < 200 ) return array( 'status' => 'skip', 'reason' => 'Content too short to check' );

        // Find recent posts in the same theme/category
        $existing_ids = self::get_theme_posts( $theme_slug, $bot_id, $language, 10, $post_id );

        if ( empty( $existing_ids ) ) {
            return array( 'status' => 'pass', 'reason' => 'No existing posts to compare (first post in this theme)', 'max_similarity' => 0, 'comparisons' => array() );
        }

        $new_shingles = self::make_shingles( $new_content, 3 );
        $comparisons  = array();
        $max_sim      = 0;

        foreach ( $existing_ids as $eid ) {
            $existing = get_post( $eid );
            if ( ! $existing ) continue;

            $existing_content  = wp_strip_all_tags( $existing->post_content );
            $existing_shingles = self::make_shingles( $existing_content, 3 );
            $sim = self::jaccard( $new_shingles, $existing_shingles );

            $comparisons[] = array(
                'post_id'    => $eid,
                'title'      => $existing->post_title,
                'similarity' => round( $sim * 100, 1 ),
            );

            if ( $sim > $max_sim ) $max_sim = $sim;
        }

        // Sort by similarity descending
        usort( $comparisons, function( $a, $b ) { return $b['similarity'] <=> $a['similarity']; } );

        $threshold = 0.35; // 35% similarity = too close
        $status = $max_sim >= $threshold ? 'fail' : 'pass';

        if ( $status === 'fail' ) {
            error_log( 'DBA Sniper Gate C: SELF-PLAGIARISM DETECTED — post #' . $post_id . ' is ' . round( $max_sim * 100, 1 ) . '% similar to existing post' );
        }

        return array(
            'status'         => $status,
            'max_similarity' => round( $max_sim * 100, 1 ),
            'threshold'      => round( $threshold * 100 ),
            'compared'       => count( $comparisons ),
            'comparisons'    => array_slice( $comparisons, 0, 5 ), // Top 5 most similar
            'reason'         => $status === 'pass'
                ? 'Max similarity ' . round( $max_sim * 100, 1 ) . '% (below ' . round( $threshold * 100 ) . '% threshold)'
                : 'WARNING: ' . round( $max_sim * 100, 1 ) . '% similarity with existing post (threshold: ' . round( $threshold * 100 ) . '%)',
        );
    }

    /**
     * Get recent post IDs in the same theme.
     */
    private static function get_theme_posts( $theme_slug, $bot_id, $language, $limit = 10, $exclude_id = 0 ) {
        // Strategy: search by category/tag matching the theme slug
        $args = array(
            'post_type'      => 'post',
            'post_status'    => array( 'publish', 'draft' ),
            'posts_per_page' => $limit,
            'fields'         => 'ids',
            'orderby'        => 'date',
            'order'          => 'DESC',
        );

        if ( $exclude_id ) $args['post__not_in'] = array( $exclude_id );

        // Try category match first
        $cat = get_category_by_slug( $theme_slug );
        if ( $cat ) {
            $args['cat'] = $cat->term_id;
            $ids = get_posts( $args );
            if ( ! empty( $ids ) ) return $ids;
        }

        // Try tag match
        $tag = get_term_by( 'slug', $theme_slug, 'post_tag' );
        if ( $tag ) {
            $args['tag_id'] = $tag->term_id;
            unset( $args['cat'] );
            $ids = get_posts( $args );
            if ( ! empty( $ids ) ) return $ids;
        }

        // Fallback: search by Sniper metadata
        unset( $args['cat'], $args['tag_id'] );
        $args['meta_query'] = array(
            array( 'key' => '_dba_sniper_theme', 'value' => $theme_slug ),
        );
        $ids = get_posts( $args );
        if ( ! empty( $ids ) ) return $ids;

        // Last fallback: search by title keywords
        unset( $args['meta_query'] );
        $search_term = str_replace( '_', ' ', $theme_slug );
        $args['s'] = $search_term;
        return get_posts( $args );
    }

    /**
     * Create character n-gram shingles from text.
     */
    private static function make_shingles( $text, $n = 3 ) {
        $text = mb_strtolower( preg_replace( '/\s+/', ' ', trim( $text ) ) );
        $words = explode( ' ', $text );
        $shingles = array();
        for ( $i = 0; $i <= count( $words ) - $n; $i++ ) {
            $shingles[] = implode( ' ', array_slice( $words, $i, $n ) );
        }
        return array_unique( $shingles );
    }

    /**
     * Jaccard similarity between two shingle sets.
     */
    private static function jaccard( $a, $b ) {
        if ( empty( $a ) || empty( $b ) ) return 0;
        $inter = count( array_intersect( $a, $b ) );
        $union = count( array_unique( array_merge( $a, $b ) ) );
        return $union > 0 ? $inter / $union : 0;
    }

    /* ══════════════════════════════════════════════════════════════
     *  REPAIR — re-run missing elements on an existing post
     * ══════════════════════════════════════════════════════════════ */

    public static function repair_post( $post_id ) {
        $post = get_post( $post_id );
        if ( ! $post ) return new WP_Error( 'not_found', 'Post #' . $post_id . ' not found.' );

        $repairs = array();

        // 1. Check & repair featured image (DALL-E)
        if ( ! has_post_thumbnail( $post_id ) ) {
            $repairs['featured_image'] = array( 'action' => 'attempted' );

            // Try to generate via DALL-E if available
            if ( class_exists( 'DBA_Image_Generator' ) ) {
                $title = $post->post_title;
                $img_result = DBA_Image_Generator::generate_and_attach( $title, $post_id );
                if ( ! is_wp_error( $img_result ) ) {
                    $repairs['featured_image'] = array( 'action' => 'generated', 'image_id' => $img_result );
                } else {
                    $repairs['featured_image'] = array( 'action' => 'failed', 'error' => $img_result->get_error_message() );
                }
            } elseif ( function_exists( 'dba_generate_featured_image' ) ) {
                $img_id = dba_generate_featured_image( $post->post_title, $post_id );
                if ( $img_id && ! is_wp_error( $img_id ) ) {
                    $repairs['featured_image'] = array( 'action' => 'generated', 'image_id' => $img_id );
                } else {
                    $repairs['featured_image'] = array( 'action' => 'failed', 'error' => is_wp_error( $img_id ) ? $img_id->get_error_message() : 'Unknown error' );
                }
            } else {
                $repairs['featured_image'] = array( 'action' => 'skipped', 'error' => 'No DALL-E generator class found. Check that DBA_Image_Generator or dba_generate_featured_image is loaded.' );
            }
        } else {
            $repairs['featured_image'] = array( 'action' => 'ok', 'detail' => 'Already has featured image' );
        }

        // 2. Check & repair Yoast SEO
        $yoast_title = get_post_meta( $post_id, '_yoast_wpseo_title', true );
        if ( empty( $yoast_title ) ) {
            // Set basic Yoast meta from post title
            update_post_meta( $post_id, '_yoast_wpseo_title', $post->post_title . ' - Damalion' );
            $excerpt = wp_trim_words( wp_strip_all_tags( $post->post_content ), 25, '...' );
            update_post_meta( $post_id, '_yoast_wpseo_metadesc', $excerpt );

            // Try to find the main keyword from Sniper data
            $sniper_theme = get_post_meta( $post_id, '_dba_sniper_theme', true );
            if ( $sniper_theme ) {
                update_post_meta( $post_id, '_yoast_wpseo_focuskw', str_replace( '_', ' ', $sniper_theme ) );
            }

            $repairs['yoast_seo'] = array( 'action' => 'repaired', 'detail' => 'Set title, description, and focus keyword' );
        } else {
            $repairs['yoast_seo'] = array( 'action' => 'ok', 'detail' => 'Already has Yoast meta' );
        }

        // 3. Tag the post with Sniper metadata
        if ( ! get_post_meta( $post_id, '_dba_sniper_post', true ) ) {
            update_post_meta( $post_id, '_dba_sniper_post', 1 );
            $repairs['metadata'] = array( 'action' => 'tagged' );
        } else {
            $repairs['metadata'] = array( 'action' => 'ok' );
        }

        return $repairs;
    }

    /* ══════════════════════════════════════════════════════════════
     *  UNIVERSAL BRIEF INJECTION — call this from ANY bot
     *
     *  Usage (one line in any generator's prompt-building):
     *    $brief = DBA_SEO_Sniper_Router::get_brief_for_prompt( $theme_slug, 'luxstrat' );
     *    if ( $brief ) $user_prompt .= "\n\n" . $brief;
     *
     *  Priority: transient (Sniper-triggered) > stored (cron fallback)
     * ══════════════════════════════════════════════════════════════ */

    public static function get_brief_for_prompt( $theme_slug, $bot_id = '' ) {
        // 1. Check transient first (Sniper-triggered, live analysis, 30 min TTL)
        $transient_brief = get_transient( 'dba_sniper_brief' );
        if ( $transient_brief ) {
            error_log( 'DBA Sniper: 🎯 Injecting LIVE brief for theme "' . $theme_slug . '" (' . strlen( $transient_brief ) . ' chars)' );
            return $transient_brief;
        }

        // 2. Fall back to stored analysis (persistent, from last Sniper analysis)
        $stored_brief = self::get_stored_brief( $theme_slug, $bot_id );
        if ( $stored_brief ) {
            error_log( 'DBA Sniper: 📦 Injecting STORED brief for theme "' . $theme_slug . '" (' . strlen( $stored_brief ) . ' chars)' );
            return $stored_brief;
        }

        return '';
    }

    /* ══════════════════════════════════════════════════════════════
     *  STORED BRIEF — persistent competitive intel for cron runs
     *
     *  Any bot calls Router::get_stored_brief( $theme_slug ) at
     *  generation time. Returns the brief text if Sniper has
     *  previously analyzed a keyword for that theme, or empty string.
     *
     *  This is the PERSISTENT counterpart to the transient brief.
     *  Transient = Sniper-triggered (30 min TTL).
     *  Stored   = always available for cron-triggered generation.
     * ══════════════════════════════════════════════════════════════ */

    /**
     * Get stored competitive brief for a theme slug.
     *
     * Scans dba_sniper_keywords for any keyword whose last_bot/last_theme
     * matches the given theme. Returns the most recent analysis brief.
     *
     * @param string $theme_slug  Theme slug (e.g., 'raif', 'soparfi').
     * @param string $bot_id      Bot identifier (e.g., 'luxstrat', 'eeurope').
     * @return string  Brief text or empty string.
     */
    public static function get_stored_brief( $theme_slug, $bot_id = '' ) {
        $keywords = get_option( 'dba_sniper_keywords', array() );
        if ( empty( $keywords ) ) return '';

        $best_analysis = null;
        $best_date     = '';

        foreach ( $keywords as $kw_key => $data ) {
            $analysis = $data['last_analysis'] ?? null;
            if ( ! $analysis || empty( $analysis['structural_advantages'] ) ) continue;

            // Match by theme
            $matches_theme = false;

            // Direct theme match
            if ( ( $data['last_theme'] ?? '' ) === $theme_slug ) {
                $matches_theme = true;
            }

            // Keyword contains theme slug (fallback)
            if ( ! $matches_theme && stripos( $data['keyword'] ?? '', str_replace( '_', ' ', $theme_slug ) ) !== false ) {
                $matches_theme = true;
            }

            // Bot match (if specified)
            if ( $matches_theme && ! empty( $bot_id ) && ! empty( $data['last_bot'] ) ) {
                if ( $data['last_bot'] !== $bot_id ) {
                    $matches_theme = false;
                }
            }

            if ( $matches_theme ) {
                $date = $analysis['date'] ?? '';
                if ( $date > $best_date ) {
                    $best_date     = $date;
                    $best_analysis = $analysis;
                    $best_analysis['keyword'] = $data['keyword'] ?? '';
                }
            }
        }

        if ( ! $best_analysis ) return '';

        return self::build_brief_from_stored( $best_analysis );
    }

    /**
     * Build a GPT-ready brief from stored analysis data.
     *
     * Unlike build_brief() which uses the full live analysis,
     * this uses the compact persisted version from dba_sniper_keywords.
     */
    private static function build_brief_from_stored( $analysis ) {
        $keyword = $analysis['keyword'] ?? 'unknown';
        $metrics = $analysis['metrics'] ?? array();
        $comps   = $analysis['top_competitors'] ?? array();
        $bp      = $analysis['blueprint'] ?? array();

        $brief  = "═══ SEO SNIPER — COMPETITIVE INTELLIGENCE (stored) ═══\n";
        $brief .= "Target keyword: \"{$keyword}\"\n";
        $brief .= "Last analyzed: " . ( $analysis['date'] ?? 'unknown' ) . "\n";
        $brief .= "Competitors analyzed: " . ( $analysis['competitor_count'] ?? count( $comps ) ) . "\n\n";

        // Our position
        if ( ! empty( $analysis['our_position'] ) ) {
            $brief .= "OUR POSITION: #" . $analysis['our_position'] . "\n";
            $brief .= "GOAL: Outrank everyone above us.\n\n";
        } else {
            $brief .= "WE ARE NOT RANKING. This article must break into top 10.\n\n";
        }

        // Competitors
        if ( ! empty( $comps ) ) {
            $brief .= "TOP COMPETITORS:\n";
            foreach ( $comps as $c ) {
                $brief .= sprintf( "  #%d %s — %d words, %d H2s, density %.1f%%, %s, %s\n",
                    $c['position'] ?? 0, $c['domain'] ?? '?', $c['word_count'] ?? 0,
                    $c['h2_count'] ?? 0, $c['keyword_density'] ?? 0,
                    ! empty( $c['has_faq'] ) ? 'has FAQ' : 'no FAQ',
                    ! empty( $c['has_table'] ) ? 'has tables' : 'no tables'
                );
            }
        }

        // Word count
        $target_wc = $bp['target_word_count'] ?? ( $metrics['target_word_count'] ?? 1800 );
        $avg_wc    = $metrics['avg_word_count_top5'] ?? 1500;
        $brief .= "\nWORD COUNT: Top 5 average {$avg_wc} words. YOUR TARGET: {$target_wc} words (20% more).\n";
        $brief .= "KEYWORD DENSITY: " . ( $bp['keyword_density'] ?? ( $metrics['avg_keyword_density'] ?? 1.0 ) ) . "%. Match this.\n";

        // Heading topics
        $headings = $analysis['heading_union'] ?? array();
        if ( ! empty( $headings ) ) {
            $brief .= "\nTOPICS COMPETITORS COVER (cover ALL plus more):\n";
            foreach ( $headings as $h ) {
                $brief .= "  • " . $h['heading'] . " (" . $h['frequency'] . " competitors)\n";
            }
        }

        // ═══ STRUCTURAL WEAKNESSES → ACTIONABLE INSTRUCTIONS ═══
        $advantages = $analysis['structural_advantages'] ?? array();
        if ( ! empty( $advantages ) ) {
            $brief .= "\nCOMPETITOR WEAKNESSES — YOU MUST EXPLOIT THESE:\n";
            foreach ( $advantages as $a ) {
                $type = $a['type'] ?? '';
                $brief .= "  ★ " . $a['label'] . ": " . $a['detail'] . "\n";

                // Add EXPLICIT structural instructions for each weakness type
                if ( $type === 'faq_schema' ) {
                    $brief .= "    → REQUIRED: Add an FAQ section at the end of the article with 4-6 questions.\n";
                    $brief .= "    → Format each Q&A as: <h3>Question?</h3> followed by <p>Answer paragraph.</p>\n";
                    $brief .= "    → After the FAQ HTML, add a FAQPage JSON-LD schema inside a script tag with type application/ld+json.\n";
                    $brief .= "    → The schema must use @type FAQPage with mainEntity array of Question objects, each containing name and acceptedAnswer with @type Answer and text.\n";
                }
                if ( $type === 'comparison_table' ) {
                    $brief .= "    → REQUIRED: Add at least one HTML comparison table using table, thead, tbody, tr, th, td tags.\n";
                    $brief .= "    → Table should compare key features, costs, timelines, or requirements relevant to the topic.\n";
                    $brief .= "    → Make the table data-rich with at least 4 rows and 3 columns.\n";
                }
                if ( $type === 'schema_markup' ) {
                    $brief .= "    → REQUIRED: Include Article schema JSON-LD at the end of the content inside a script tag with type application/ld+json.\n";
                    $brief .= "    → The schema must use @type Article with headline, description, and author of @type Organization name Damalion.\n";
                }
            }
        }

        // Keyword placement
        if ( ! empty( $metrics['keyword_in_title_pct'] ) && $metrics['keyword_in_title_pct'] >= 70 ) {
            $brief .= "\nKEYWORD MUST appear in: title, H1, at least one H2, and first paragraph.\n";
        }

        $brief .= "\n═══ END COMPETITIVE INTELLIGENCE ═══\n";
        return $brief;
    }

    /* ══════════════════════════════════════════════════════════════
     *  BOT TRIGGERS
     * ══════════════════════════════════════════════════════════════ */

    private static function trigger_luxstrat_en( $theme_slug, $analysis ) {
        self::progress( 7, 13, '⚙️ LuxStrat EN running full pipeline...', 'GPT-4 + validation + plagiarism + DALL-E + Yoast' );

        set_transient( 'dba_sniper_force_theme', $theme_slug, 600 );
        $generator = DBA_LuxStrat_Generator::get_instance();
        $result    = $generator->run( true );
        delete_transient( 'dba_sniper_force_theme' );
        delete_transient( 'dba_sniper_brief' );

        if ( is_wp_error( $result ) ) return $result;

        $post_id = is_array( $result ) ? ( $result['post_id'] ?? 0 ) : $result;

        // Tag with Sniper metadata for self-plagiarism tracking
        if ( $post_id ) {
            update_post_meta( $post_id, '_dba_sniper_post', 1 );
            update_post_meta( $post_id, '_dba_sniper_theme', $theme_slug );
            update_post_meta( $post_id, '_dba_sniper_bot', 'luxstrat' );
            update_post_meta( $post_id, '_dba_sniper_date', current_time( 'mysql' ) );
        }

        return array( 'post_id' => $post_id, 'bot' => 'luxstrat', 'theme' => $theme_slug, 'language' => 'en', 'analysis' => $analysis );
    }

    private static function trigger_luxstrat_intl( $lang_code, $theme_slug, $bot, $analysis ) {
        self::progress( 7, 13, '⚙️ LuxStrat ' . strtoupper( $lang_code ) . ' running full pipeline...', 'GPT-4 + validation + plagiarism + DALL-E + Yoast' );

        set_transient( 'dba_sniper_force_theme_' . $lang_code, $theme_slug, 600 );
        $lang_config = $bot['lang_config'] ?? DBA_LuxStrat_Intl_Loader::get_language( $lang_code );
        if ( ! $lang_config ) return new WP_Error( 'no_lang_config', 'Language config not found for "' . $lang_code . '".' );

        $result = DBA_LuxStrat_Intl_Generator::run( $lang_code, $lang_config, true );
        delete_transient( 'dba_sniper_force_theme_' . $lang_code );
        delete_transient( 'dba_sniper_brief' );

        if ( is_wp_error( $result ) ) return $result;

        $post_id = is_array( $result ) ? ( $result['post_id'] ?? 0 ) : $result;

        if ( $post_id ) {
            update_post_meta( $post_id, '_dba_sniper_post', 1 );
            update_post_meta( $post_id, '_dba_sniper_theme', $theme_slug );
            update_post_meta( $post_id, '_dba_sniper_bot', 'luxstrat-intl-' . $lang_code );
            update_post_meta( $post_id, '_dba_sniper_date', current_time( 'mysql' ) );
        }

        return array( 'post_id' => $post_id, 'bot' => 'luxstrat-intl-' . $lang_code, 'theme' => $theme_slug, 'language' => $lang_code, 'analysis' => $analysis );
    }

    /* ══════════════════════════════════════════════════════════════
     *  BUILD BRIEF
     * ══════════════════════════════════════════════════════════════ */

    private static function build_brief( $keyword, $language, $analysis ) {
        $bp = $analysis['blueprint'] ?? array();
        $metrics = $analysis['metrics'] ?? array();
        $comps = $analysis['competitors'] ?? array();

        $brief  = "═══ SEO SNIPER — COMPETITIVE INTELLIGENCE ═══\n";
        $brief .= "Target keyword: \"{$keyword}\"\n";
        $brief .= "Analyzed: " . count( $comps ) . " competitor pages from Google top 20.\n\n";

        if ( ! empty( $analysis['our_position'] ) ) {
            $brief .= "OUR CURRENT POSITION: #" . $analysis['our_position'] . "\n";
            $brief .= "GOAL: Outrank everyone above us.\n\n";
        } else {
            $brief .= "WE ARE NOT RANKING. This article must break into top 10.\n\n";
        }

        $brief .= "TOP COMPETITORS:\n";
        foreach ( array_slice( $comps, 0, 5 ) as $c ) {
            $brief .= sprintf( "  #%d %s — %d words, %d H2s, density %.1f%%, %s, %s\n",
                $c['position'] ?? 0, $c['domain'] ?? '?', $c['word_count'] ?? 0,
                count( $c['headings']['h2'] ?? array() ), $c['keyword_density'] ?? 0,
                ( $c['has_faq'] ?? false ) ? 'has FAQ' : 'no FAQ',
                ( $c['has_table'] ?? false ) ? 'has tables' : 'no tables'
            );
        }

        $target_wc = $bp['target_word_count'] ?? 1800;
        $avg_wc    = $metrics['avg_word_count_top5'] ?? 1500;
        $brief .= "\nWORD COUNT: Top 5 average {$avg_wc} words. YOUR TARGET: {$target_wc} words (20% more).\n";
        $brief .= "KEYWORD DENSITY: Competitors average " . ( $bp['keyword_density'] ?? 1.0 ) . "%. Match this.\n";

        $headings = $analysis['heading_union'] ?? array();
        if ( ! empty( $headings ) ) {
            $brief .= "\nTOPICS COMPETITORS COVER (your article must cover ALL plus more):\n";
            foreach ( array_slice( $headings, 0, 10 ) as $h ) {
                $brief .= "  • " . $h['heading'] . " (" . $h['frequency'] . " competitors)\n";
            }
        }

        $advantages = $analysis['structural_advantages'] ?? array();
        if ( ! empty( $advantages ) ) {
            $brief .= "\nCOMPETITOR WEAKNESSES — YOU MUST EXPLOIT THESE:\n";
            foreach ( $advantages as $a ) {
                $type = $a['type'] ?? '';
                $brief .= "  ★ " . $a['label'] . ": " . $a['detail'] . "\n";

                // Explicit structural instructions per weakness type
                if ( $type === 'faq_schema' ) {
                    $brief .= "    → REQUIRED: Add an FAQ section at the end with 4-6 questions.\n";
                    $brief .= "    → Format: <h3>Question?</h3> followed by <p>Answer paragraph.</p>\n";
                    $brief .= "    → After FAQ HTML, add FAQPage JSON-LD schema inside a script tag with type application/ld+json.\n";
                    $brief .= "    → Schema must use @type FAQPage with mainEntity array of Question objects, each with name and acceptedAnswer containing @type Answer and text.\n";
                }
                if ( $type === 'comparison_table' ) {
                    $brief .= "    → REQUIRED: Add at least one HTML comparison table (table, thead, tbody tags).\n";
                    $brief .= "    → Compare key features, costs, timelines, or requirements. At least 4 rows, 3 columns.\n";
                }
                if ( $type === 'schema_markup' ) {
                    $brief .= "    → REQUIRED: Include Article schema JSON-LD at end of content inside a script tag with type application/ld+json.\n";
                    $brief .= "    → Schema must use @type Article with headline, description, and author of @type Organization name Damalion.\n";
                }
            }
        }

        if ( ! empty( $metrics['keyword_in_title_pct'] ) && $metrics['keyword_in_title_pct'] >= 70 ) {
            $brief .= "\nKEYWORD MUST appear in: title, H1, at least one H2, and first paragraph.\n";
        }

        $brief .= "\n═══ END COMPETITIVE INTELLIGENCE ═══\n";
        return $brief;
    }
}
