<?php
/**
 * DBA Kaizen Center v2.18.0
 * Centralized Learning Engine Dashboard
 *
 * Integration: ONE line in damalion-blog-ai.php:
 *   require_once DBA_PLUGIN_DIR . 'includes/class-kaizen-center.php';
 */
if ( ! defined( 'ABSPATH' ) ) exit;

class DBA_Kaizen_Center {

    private static $bots = null;

    private static function get_bots() {
        if ( self::$bots !== null ) return self::$bots;
        self::$bots = array(
            'core' => array(
                'class'     => 'DBA_Kaizen',
                'method'    => 'get_learning_context',
                'params'    => array(),
                'flush'     => 'flush_cache',
                'meta_auto' => '_dba_auto_created',
                'label'     => 'Core (EU)',
            ),
            'geobot' => array(
                'class'     => 'DBA_GeoBot_Kaizen',
                'method'    => 'get_learning_context',
                'params'    => array( 'country_key' ),
                'flush'     => 'invalidate_cache',
                'meta_auto' => '_dba_geobot_auto_created',
                'label'     => 'GeoBot',
            ),
            'latam' => array(
                'class'     => 'DBA_LATAM_Kaizen',
                'method'    => 'get_learning_context',
                'params'    => array( 'country_key' ),
                'flush'     => 'invalidate_cache',
                'meta_auto' => '_dba_latam_auto_created',
                'label'     => 'LATAM',
            ),
            'luxstrat' => array(
                'class'     => 'DBA_LuxStrat_Kaizen',
                'method'    => 'get_learning_context',
                'params'    => array( 'theme_slug' ),
                'flush'     => 'invalidate_cache',
                'meta_auto' => '_dba_luxstrat_auto_created',
                'label'     => 'LuxStrat',
            ),
            'luxstrat_intl' => array(
                'class'     => 'DBA_LuxStrat_Intl_Kaizen',
                'method'    => 'get_learning_context',
                'params'    => array( 'lang', 'theme_slug' ),
                'flush'     => 'invalidate_cache',
                'meta_auto' => '_dba_luxstrat_fr_auto_created',
                'label'     => 'LuxStrat Intl',
            ),
            'luxstrat_intl_corr' => array(
                'class'     => 'DBA_LuxStrat_Intl_Corrections',
                'method'    => 'build_learning_prompt',
                'params'    => array( 'lang', 'theme_slug' ),
                'flush'     => null,
                'meta_auto' => '_dba_luxstrat_fr_auto_created',
                'label'     => 'LuxStrat Intl Corr.',
            ),
            'startup_hub' => array(
                'class'     => 'DBA_Startup_Hub_Kaizen',
                'method'    => 'get_learning_context',
                'params'    => array(),
                'flush'     => null,
                'meta_auto' => '_dba_startup_hub_auto_created',
                'label'     => 'Startup Hub',
            ),
            'eeurope' => array(
                'class'     => 'DBA_EEurope_Corrections',
                'method'    => 'build_learning_prompt',
                'params'    => array( 'country_key' ),
                'flush'     => null,
                'meta_auto' => '_dba_eeurope_auto_created',
                'label'     => 'Eastern Europe',
            ),
            'comfisc' => array(
                'class'     => 'DBA_Comfisc_Kaizen',
                'method'    => 'get_learning_context',
                'params'    => array( 'lang', 'theme_slug' ),
                'flush'     => 'invalidate_cache',
                'meta_auto' => '_dba_comfisc_fr_auto_created',
                'label'     => 'Comfisc (FR)',
            ),
            'comfisc_corr' => array(
                'class'     => 'DBA_Comfisc_Corrections',
                'method'    => 'build_learning_prompt',
                'params'    => array( 'lang', 'theme_slug' ),
                'flush'     => null,
                'meta_auto' => '_dba_comfisc_fr_auto_created',
                'label'     => 'Comfisc Corr.',
            ),
        );
        return self::$bots;
    }

    public static function register_menu() {
        add_submenu_page(
            'dba-settings',
            'Kaizen Center',
            'Kaizen Center',
            'manage_options',
            'dba-kaizen-center',
            array( __CLASS__, 'render_page' )
        );
    }

    public static function register_ajax() {
        add_action( 'wp_ajax_dba_kc_run',          array( __CLASS__, 'ajax_run' ) );
        add_action( 'wp_ajax_dba_kc_flush',         array( __CLASS__, 'ajax_flush' ) );
        add_action( 'wp_ajax_dba_kc_flush_all',     array( __CLASS__, 'ajax_flush_all' ) );
        add_action( 'wp_ajax_dba_kc_load_posts',    array( __CLASS__, 'ajax_load_posts' ) );
        add_action( 'wp_ajax_dba_kc_scan_post',     array( __CLASS__, 'ajax_scan_post' ) );
        add_action( 'wp_ajax_dba_kc_fix_post',      array( __CLASS__, 'ajax_fix_post' ) );
        add_action( 'wp_ajax_dba_kc_approve_fix',   array( __CLASS__, 'ajax_approve_fix' ) );
        add_action( 'wp_ajax_dba_kc_batch_collect',  array( __CLASS__, 'ajax_batch_collect' ) );
        add_action( 'wp_ajax_dba_kc_check_schedule', array( __CLASS__, 'ajax_check_schedule' ) );
    }

    /* ── AJAX: Run a bot's kaizen ── */
    public static function ajax_run() {
        check_ajax_referer( 'dba_kc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Denied' );

        $bots   = self::get_bots();
        $bot_id = sanitize_text_field( $_POST['bot_id'] );
        $bot    = isset( $bots[ $bot_id ] ) ? $bots[ $bot_id ] : null;
        if ( ! $bot ) wp_send_json_error( 'Unknown bot' );
        if ( ! class_exists( $bot['class'] ) ) wp_send_json_error( $bot['class'] . ' not loaded' );

        $args = array();
        foreach ( $bot['params'] as $p ) {
            $args[] = sanitize_text_field( isset( $_POST[ $p ] ) ? $_POST[ $p ] : '' );
        }

        if ( $bot['flush'] && method_exists( $bot['class'], $bot['flush'] ) ) {
            $fa = ( in_array( $bot_id, array( 'luxstrat_intl', 'comfisc' ), true ) && ! empty( $args[0] ) ) ? array( $args[0] ) : array();
            call_user_func_array( array( $bot['class'], $bot['flush'] ), $fa );
        }

        $t0      = microtime( true );
        $context = call_user_func_array( array( $bot['class'], $bot['method'] ), $args );
        $ms      = round( ( microtime( true ) - $t0 ) * 1000 );

        wp_send_json_success( array(
            'context' => $context ? $context : '(empty - no corrections detected)',
            'length'  => strlen( $context ),
            'elapsed' => $ms,
        ) );
    }

    /* ── AJAX: Flush one cache ── */
    public static function ajax_flush() {
        check_ajax_referer( 'dba_kc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Denied' );

        $bots   = self::get_bots();
        $bot_id = sanitize_text_field( $_POST['bot_id'] );
        $bot    = isset( $bots[ $bot_id ] ) ? $bots[ $bot_id ] : null;
        if ( ! $bot || ! $bot['flush'] || ! class_exists( $bot['class'] ) ) wp_send_json_error( 'Cannot flush' );

        call_user_func( array( $bot['class'], $bot['flush'] ) );
        wp_send_json_success( array( 'flushed' => $bot['label'] ) );
    }

    /* ── AJAX: Flush ALL ── */
    public static function ajax_flush_all() {
        check_ajax_referer( 'dba_kc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Denied' );

        $done = array();
        foreach ( self::get_bots() as $id => $bot ) {
            if ( $bot['flush'] && class_exists( $bot['class'] ) && method_exists( $bot['class'], $bot['flush'] ) ) {
                call_user_func( array( $bot['class'], $bot['flush'] ) );
                $done[] = $bot['label'];
            }
        }
        wp_send_json_success( array( 'flushed' => $done ) );
    }

    /* ── AJAX: Load 10 latest posts by language ── */
    public static function ajax_load_posts() {
        check_ajax_referer( 'dba_kc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Denied' );

        global $wpdb;
        $lang = sanitize_text_field( isset( $_POST['lang'] ) ? $_POST['lang'] : 'all' );

        $icl_table = $wpdb->prefix . 'icl_translations';
        $has_wpml  = ( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(1) FROM information_schema.tables WHERE table_schema = %s AND table_name = %s", DB_NAME, $icl_table ) ) > 0 );

        if ( $lang !== 'all' && $has_wpml ) {
            $wpml_code = $lang;
            if ( $lang === 'nb' ) $wpml_code = 'no';
            if ( $lang === 'zh' ) $wpml_code = 'zh-hans';

            $sql = $wpdb->prepare(
                "SELECT p.ID, p.post_title, p.post_status, p.post_date, t.language_code
                 FROM {$wpdb->posts} p
                 INNER JOIN {$icl_table} t ON t.element_id = p.ID AND t.element_type LIKE %s
                 WHERE p.post_type = 'post'
                   AND p.post_status IN ('publish','draft')
                   AND t.language_code = %s
                 ORDER BY p.post_date DESC
                 LIMIT 10",
                'post_%',
                $wpml_code
            );
            $rows = $wpdb->get_results( $sql );
        } else {
            $rows = $wpdb->get_results(
                "SELECT ID, post_title, post_status, post_date
                 FROM {$wpdb->posts}
                 WHERE post_type = 'post'
                   AND post_status IN ('publish','draft')
                 ORDER BY post_date DESC
                 LIMIT 10"
            );
        }

        if ( empty( $rows ) ) {
            wp_send_json_success( array( 'posts' => array(), 'count' => 0 ) );
            return;
        }

        $bots  = self::get_bots();
        $posts = array();

        foreach ( $rows as $r ) {
            $bot_label = '-';
            foreach ( $bots as $bid => $bot ) {
                if ( get_post_meta( $r->ID, $bot['meta_auto'], true ) ) {
                    $bot_label = $bot['label'];
                    break;
                }
            }

            $det_lang = 'en';
            if ( class_exists( 'DBA_Kaizen_Patterns_I18n' ) ) {
                $det_lang = DBA_Kaizen_Patterns_I18n::detect_language( $r->ID );
            } elseif ( isset( $r->language_code ) && $r->language_code ) {
                $det_lang = $r->language_code;
            }

            $content = get_post_field( 'post_content', $r->ID );
            $wc = str_word_count( strip_tags( $content ) );
            preg_match_all( '/<h2[^>]*>/si', $content, $h2m );

            $posts[] = array(
                'id'    => intval( $r->ID ),
                'title' => $r->post_title,
                'status'=> $r->post_status,
                'date'  => date( 'Y-m-d', strtotime( $r->post_date ) ),
                'bot'   => $bot_label,
                'lang'  => $det_lang,
                'words' => $wc,
                'h2s'   => count( $h2m[0] ),
                'url'   => get_permalink( $r->ID ),
            );
        }

        wp_send_json_success( array( 'posts' => $posts, 'count' => count( $posts ) ) );
    }

    /* ── AJAX: Scan one post ── */
    public static function ajax_scan_post() {
        check_ajax_referer( 'dba_kc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Denied' );

        $post_id = intval( isset( $_POST['post_id'] ) ? $_POST['post_id'] : 0 );
        if ( ! $post_id ) wp_send_json_error( 'No post ID' );

        $post = get_post( $post_id );
        if ( ! $post ) wp_send_json_error( 'Post not found' );

        $lang = 'en';
        $lang_label = 'English';
        if ( class_exists( 'DBA_Kaizen_Patterns_I18n' ) ) {
            $lang = DBA_Kaizen_Patterns_I18n::detect_language( $post_id );
            $langs = DBA_Kaizen_Patterns_I18n::LANGUAGES;
            $lang_label = isset( $langs[ $lang ] ) ? $langs[ $lang ] : $lang;
        }

        $bots = self::get_bots();
        $det_bot = '-';
        foreach ( $bots as $bid => $bot ) {
            if ( get_post_meta( $post_id, $bot['meta_auto'], true ) ) {
                $det_bot = $bot['label'];
                break;
            }
        }

        $patterns = array();
        $score    = null;
        $has_cta  = null;
        if ( class_exists( 'DBA_LuxStrat_Kaizen' ) && method_exists( 'DBA_LuxStrat_Kaizen', 'scan_single_post' ) ) {
            $patterns = DBA_LuxStrat_Kaizen::scan_single_post( $post_id, $lang );
            if ( method_exists( 'DBA_LuxStrat_Kaizen', 'quality_score' ) ) {
                $score = DBA_LuxStrat_Kaizen::quality_score( $post_id, $lang );
            }
            if ( method_exists( 'DBA_LuxStrat_Kaizen', 'has_cta' ) ) {
                $has_cta = DBA_LuxStrat_Kaizen::has_cta( $post->post_content, $lang );
            }
        }

        $wc = str_word_count( strip_tags( $post->post_content ) );
        preg_match_all( '/<h2[^>]*>/si', $post->post_content, $h2m );

        wp_send_json_success( array(
            'title'      => $post->post_title,
            'post_id'    => $post_id,
            'status'     => $post->post_status,
            'date'       => $post->post_date,
            'bot'        => $det_bot,
            'lang'       => $lang,
            'lang_label' => $lang_label,
            'words'      => $wc,
            'h2s'        => count( $h2m[0] ),
            'score'      => $score,
            'has_cta'    => $has_cta,
            'patterns'   => $patterns,
            'url'        => get_permalink( $post_id ),
        ) );
    }


    /* ── AJAX: Fix post — ONE PASS PER REQUEST (avoids Cloudflare timeout) ── */
    public static function ajax_fix_post() {
        @set_time_limit( 300 );
        check_ajax_referer( 'dba_kc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Denied' );

        $post_id    = intval( isset( $_POST['post_id'] ) ? $_POST['post_id'] : 0 );
        $patterns   = isset( $_POST['patterns'] ) ? $_POST['patterns'] : '';
        $pass_num   = intval( isset( $_POST['pass_number'] ) ? $_POST['pass_number'] : 1 );
        if ( ! $post_id ) wp_send_json_error( 'No post ID' );

        $post = get_post( $post_id );
        if ( ! $post ) wp_send_json_error( 'Post not found' );

        if ( ! class_exists( 'DBA_OpenAI' ) ) wp_send_json_error( 'DBA_OpenAI class not loaded' );
        $settings = get_option( 'dba_settings', array() );
        $api_key  = isset( $settings['openai_api_key'] ) ? $settings['openai_api_key'] : '';
        $model    = isset( $settings['openai_model'] ) ? $settings['openai_model'] : 'gpt-4.1';
        if ( empty( $api_key ) ) wp_send_json_error( 'OpenAI API key not configured.' );

        $lang = 'en';
        if ( class_exists( 'DBA_Kaizen_Patterns_I18n' ) ) {
            $lang = DBA_Kaizen_Patterns_I18n::detect_language( $post_id );
        }

        $has_scanner = class_exists( 'DBA_LuxStrat_Kaizen' ) && method_exists( 'DBA_LuxStrat_Kaizen', 'quality_score' );
        $has_cc      = class_exists( 'DBA_Conflict_Center' );

        /* Original metrics (always from actual post in DB) */
        $orig_html = $post->post_content;
        preg_match_all( '/<a\s[^>]*href\s*=/si', $orig_html, $ol );
        $orig_link_count = count( $ol[0] );
        preg_match_all( '/<h2[^>]*>/si', $orig_html, $oh );
        $orig_h2_count = count( $oh[0] );
        $orig_words    = str_word_count( strip_tags( $orig_html ) );
        $orig_score    = $has_scanner ? DBA_LuxStrat_Kaizen::quality_score( $post_id, $lang ) : null;

        /* Parse patterns by severity */
        $high_patterns    = '';
        $med_low_patterns = '';
        if ( is_string( $patterns ) ) {
            $decoded = json_decode( stripslashes( $patterns ), true );
            if ( is_array( $decoded ) ) {
                foreach ( $decoded as $cat => $info ) {
                    $sev  = strtolower( $info['severity'] ?? 'medium' );
                    $line = '- ' . $info['label'] . ' (' . $info['severity'] . '): ' . $info['prompt'] . "\n";
                    if ( $sev === 'high' ) {
                        $high_patterns .= $line;
                    } else {
                        $med_low_patterns .= $line;
                    }
                }
            }
        }
        if ( empty( $high_patterns ) && empty( $med_low_patterns ) ) {
            $high_patterns = "- Remove all AI-sounding language, filler phrases, and formulaic openers.\n";
        }

        /* System prompt */
        $system = 'You are an expert human editor for Damalion.com, a professional services firm. '
            . 'You rewrite AI-generated blog posts to read like a knowledgeable human wrote them. '
            . 'Language: ' . $lang . '. '
            . 'STRICT RULES: '
            . '1) Fix ONLY the specific patterns listed. Do NOT rewrite sections that are already fine. '
            . '2) Keep word count within 10% of original. '
            . '3) Keep ALL HTML tags: h2, h3, p, ul, ol, li, strong, em, a. Do NOT add or remove headings. '
            . '4) Keep EVERY <a href="..."> link EXACTLY as-is. '
            . '5) The last paragraph containing "Damalion" is the CTA. Copy it VERBATIM. '
            . '6) Return ONLY the fixed HTML. No markdown fences, no explanations. '
            . '7) Do NOT introduce new AI patterns: no "it is important to note", "furthermore", "landscape", "leverage", "robust", "pivotal". '
            . '8) Vary sentence openings. '
            . '9) Prefer concrete facts over vague statements.';

        $title = $post->post_title;

        /* ═══════════════════════════════════════════
           PASS 1: HIGH severity only
           ═══════════════════════════════════════════ */
        if ( $pass_num === 1 ) {
            if ( empty( $high_patterns ) ) {
                set_transient( 'dba_kc_pass1_' . $post_id, $orig_html, 3600 );
                wp_send_json_success( array( 'pass' => 1, 'skipped' => true, 'post_id' => $post_id ) );
            }

            $result = self::run_fix_pass( $api_key, $model, $system,
                "PASS 1/3: Fix ONLY HIGH-severity patterns. Leave everything else untouched.\n\n" . $high_patterns,
                $title, $orig_html, $has_cc );

            if ( is_wp_error( $result ) ) {
                $msg = $result->get_error_message();
                if ( strpos( $msg, 'LOCKED:' ) === 0 ) wp_send_json_error( $msg );
                /* On error, store original so pass 2 can continue */
                set_transient( 'dba_kc_pass1_' . $post_id, $orig_html, 3600 );
                wp_send_json_success( array( 'pass' => 1, 'valid' => false, 'issues' => array( $msg ), 'post_id' => $post_id ) );
            }

            $val     = self::validate_fix( $orig_html, $result, $orig_link_count, $orig_h2_count, $orig_words );
            $content = $val['ok'] ? $result : $orig_html;
            set_transient( 'dba_kc_pass1_' . $post_id, $content, 3600 );

            wp_send_json_success( array( 'pass' => 1, 'valid' => $val['ok'], 'issues' => $val['issues'], 'post_id' => $post_id ) );
        }

        /* ═══════════════════════════════════════════
           PASS 2: MEDIUM + LOW severity
           ═══════════════════════════════════════════ */
        if ( $pass_num === 2 ) {
            $input = get_transient( 'dba_kc_pass1_' . $post_id );
            if ( empty( $input ) ) $input = $orig_html;

            if ( empty( $med_low_patterns ) ) {
                set_transient( 'dba_kc_pass2_' . $post_id, $input, 3600 );
                wp_send_json_success( array( 'pass' => 2, 'skipped' => true, 'post_id' => $post_id ) );
            }

            $result = self::run_fix_pass( $api_key, $model, $system,
                "PASS 2/3: Fix ONLY MEDIUM and LOW severity patterns. HIGH patterns were already fixed.\n\n" . $med_low_patterns,
                $title, $input, $has_cc );

            if ( is_wp_error( $result ) ) {
                $msg = $result->get_error_message();
                if ( strpos( $msg, 'LOCKED:' ) === 0 ) wp_send_json_error( $msg );
                set_transient( 'dba_kc_pass2_' . $post_id, $input, 3600 );
                wp_send_json_success( array( 'pass' => 2, 'valid' => false, 'issues' => array( $msg ), 'post_id' => $post_id ) );
            }

            $val     = self::validate_fix( $orig_html, $result, $orig_link_count, $orig_h2_count, $orig_words );
            $content = $val['ok'] ? $result : $input;
            set_transient( 'dba_kc_pass2_' . $post_id, $content, 3600 );

            wp_send_json_success( array( 'pass' => 2, 'valid' => $val['ok'], 'issues' => $val['issues'], 'post_id' => $post_id ) );
        }

        /* ═══════════════════════════════════════════
           PASS 3: VERIFY + FINAL POLISH
           ═══════════════════════════════════════════ */
        if ( $pass_num === 3 ) {
            $input = get_transient( 'dba_kc_pass2_' . $post_id );
            if ( empty( $input ) ) $input = get_transient( 'dba_kc_pass1_' . $post_id );
            if ( empty( $input ) ) $input = $orig_html;

            $new_score  = null;
            $did_polish = false;

            if ( $has_scanner ) {
                /* Temporarily swap content for scoring */
                $backup = $post->post_content;
                wp_update_post( array( 'ID' => $post_id, 'post_content' => $input ), false );
                $new_score  = DBA_LuxStrat_Kaizen::quality_score( $post_id, $lang );
                $new_report = DBA_LuxStrat_Kaizen::scan_single_post( $post_id, $lang );
                wp_update_post( array( 'ID' => $post_id, 'post_content' => $backup ), false );

                /* Check remaining HIGH patterns */
                $remaining = '';
                foreach ( $new_report as $cat => $info ) {
                    if ( strtolower( $info['severity'] ) === 'high' && $info['hits'] > 0 ) {
                        $remaining .= '- ' . $info['label'] . ': ' . $info['prompt'] . "\n";
                    }
                }

                if ( ! empty( $remaining ) ) {
                    $polish = self::run_fix_pass( $api_key, $model, $system,
                        "PASS 3/3 — FINAL POLISH: These HIGH patterns remain. Fix them precisely.\n\n" . $remaining,
                        $title, $input, $has_cc );

                    if ( ! is_wp_error( $polish ) ) {
                        $val3 = self::validate_fix( $orig_html, $polish, $orig_link_count, $orig_h2_count, $orig_words );
                        if ( $val3['ok'] ) {
                            $input      = $polish;
                            $did_polish = true;
                            wp_update_post( array( 'ID' => $post_id, 'post_content' => $input ), false );
                            $new_score = DBA_LuxStrat_Kaizen::quality_score( $post_id, $lang );
                            wp_update_post( array( 'ID' => $post_id, 'post_content' => $backup ), false );
                        }
                    }
                }
            }

            /* Final validation */
            $final_val       = self::validate_fix( $orig_html, $input, $orig_link_count, $orig_h2_count, $orig_words );
            $score_worse     = ( $orig_score !== null && $new_score !== null && $new_score < $orig_score );
            $score_still_low = ( $new_score !== null && $new_score < 60 );
            $auto_approve    = $final_val['ok'] && ! $score_worse && ! $score_still_low;

            $reject_reasons = array();
            if ( ! $final_val['ok'] ) $reject_reasons = array_merge( $reject_reasons, $final_val['issues'] );
            if ( $score_worse ) $reject_reasons[] = 'Score worse: ' . $orig_score . '->' . $new_score;
            if ( $score_still_low ) $reject_reasons[] = 'Score still below 60: ' . $new_score . '/100';

            /* Store final for approval */
            set_transient( 'dba_kc_fix_' . $post_id, $input, 3600 );
            delete_transient( 'dba_kc_pass1_' . $post_id );
            delete_transient( 'dba_kc_pass2_' . $post_id );

            $fixed_words = str_word_count( strip_tags( $input ) );

            wp_send_json_success( array(
                'pass'           => 3,
                'post_id'        => $post_id,
                'original'       => $orig_html,
                'fixed'          => $input,
                'orig_words'     => $orig_words,
                'fixed_words'    => $fixed_words,
                'orig_score'     => $orig_score,
                'new_score'      => $new_score,
                'orig_links'     => $orig_link_count,
                'fixed_links'    => self::count_links( $input ),
                'orig_h2s'       => $orig_h2_count,
                'fixed_h2s'      => self::count_h2s( $input ),
                'cta_intact'     => ( stripos( $input, 'damalion' ) !== false ),
                'model'          => $model,
                'did_polish'     => $did_polish,
                'auto_approve'   => $auto_approve,
                'reject_reasons' => $reject_reasons,
            ) );
        }

        wp_send_json_error( 'Invalid pass number' );
    }

    /* ── Single OpenAI pass ── */
    private static function run_fix_pass( $api_key, $model, $system, $instructions, $title, $html, $has_cc ) {
        if ( $has_cc ) {
            if ( ! DBA_Conflict_Center::acquire( 'kaizen_batch' ) ) {
                $s = DBA_Conflict_Center::get_status();
                return new WP_Error( 'locked', 'LOCKED:' . ( $s['label'] ?? 'unknown' ) );
            }
        }

        $response = wp_remote_post( 'https://api.openai.com/v1/chat/completions', array(
            'timeout' => 90,
            'headers' => array(
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type'  => 'application/json',
            ),
            'body' => wp_json_encode( array(
                'model'       => $model,
                'messages'    => array(
                    array( 'role' => 'system', 'content' => $system ),
                    array( 'role' => 'user',   'content' => $instructions . "\n\n--- TITLE ---\n" . $title . "\n\n--- CONTENT ---\n" . $html ),
                ),
                'max_tokens'  => 8192,
                'temperature' => 0.25,
            ) ),
        ) );

        if ( $has_cc ) DBA_Conflict_Center::release( 'kaizen_batch' );

        if ( is_wp_error( $response ) ) return $response;

        $code = wp_remote_retrieve_response_code( $response );
        if ( $code !== 200 ) {
            return new WP_Error( 'api', 'API ' . $code . ': ' . substr( wp_remote_retrieve_body( $response ), 0, 200 ) );
        }

        $data    = json_decode( wp_remote_retrieve_body( $response ), true );
        $content = $data['choices'][0]['message']['content'] ?? '';
        if ( empty( $content ) ) return new WP_Error( 'empty', 'Empty response' );

        $content = preg_replace( '/^```html?\s*/i', '', $content );
        $content = preg_replace( '/```\s*$/', '', $content );
        return trim( $content );
    }

    /* ── Validate fix against original ── */
    private static function validate_fix( $orig, $fixed, $exp_links, $exp_h2s, $exp_words ) {
        $issues = array();

        $fl = self::count_links( $fixed );
        if ( $fl < $exp_links ) $issues[] = 'Lost ' . ( $exp_links - $fl ) . ' link(s)';

        $fh = self::count_h2s( $fixed );
        if ( $fh < $exp_h2s ) $issues[] = 'Lost ' . ( $exp_h2s - $fh ) . ' H2(s)';

        $fw = str_word_count( strip_tags( $fixed ) );
        if ( $exp_words > 50 ) {
            $pct = abs( $fw - $exp_words ) / $exp_words * 100;
            if ( $pct > 15 ) $issues[] = 'Word drift ' . round( $pct ) . '%';
        }

        if ( stripos( $orig, 'damalion' ) !== false && stripos( $fixed, 'damalion' ) === false ) {
            $issues[] = 'CTA removed';
        }
        if ( strlen( $fixed ) < 100 ) $issues[] = 'Too short';

        return array( 'ok' => empty( $issues ), 'issues' => $issues );
    }

    private static function count_links( $h ) { preg_match_all( '/<a\s[^>]*href\s*=/si', $h, $m ); return count( $m[0] ); }
    private static function count_h2s( $h )   { preg_match_all( '/<h2[^>]*>/si', $h, $m ); return count( $m[0] ); }

    public static function ajax_approve_fix() {
        check_ajax_referer( 'dba_kc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Denied' );

        $post_id = intval( isset( $_POST['post_id'] ) ? $_POST['post_id'] : 0 );
        if ( ! $post_id ) wp_send_json_error( 'No post ID' );

        $fixed = get_transient( 'dba_kc_fix_' . $post_id );
        if ( empty( $fixed ) ) wp_send_json_error( 'Fix expired or not found. Scan and fix again.' );

        /* Save original as revision backup in meta */
        $post = get_post( $post_id );
        if ( ! $post ) wp_send_json_error( 'Post not found' );
        update_post_meta( $post_id, '_dba_kc_pre_fix_content', $post->post_content );
        update_post_meta( $post_id, '_dba_kc_pre_fix_date', current_time( 'mysql' ) );

        /* Update post content */
        $result = wp_update_post( array(
            'ID'           => $post_id,
            'post_content' => $fixed,
        ), true );

        if ( is_wp_error( $result ) ) {
            wp_send_json_error( 'Failed to update: ' . $result->get_error_message() );
        }

        delete_transient( 'dba_kc_fix_' . $post_id );

        wp_send_json_success( array(
            'post_id' => $post_id,
            'message' => 'Post updated! Original saved in meta _dba_kc_pre_fix_content.',
        ) );
    }

    /* ── AJAX: Collect all posts for batch processing ── */
    public static function ajax_batch_collect() {
        check_ajax_referer( 'dba_kc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Denied' );

        global $wpdb;
        $bots      = self::get_bots();
        $max_per   = intval( isset( $_POST['max_per_bot'] ) ? $_POST['max_per_bot'] : 20 );
        $score_max = intval( isset( $_POST['score_below'] ) ? $_POST['score_below'] : 80 );
        $queue     = array();

        $has_scanner = class_exists( 'DBA_LuxStrat_Kaizen' ) && method_exists( 'DBA_LuxStrat_Kaizen', 'scan_single_post' );

        foreach ( $bots as $bid => $bot ) {
            if ( ! class_exists( $bot['class'] ) ) continue;

            $post_ids = $wpdb->get_col( $wpdb->prepare(
                "SELECT post_id FROM {$wpdb->postmeta}
                 WHERE meta_key = %s
                 ORDER BY meta_id DESC
                 LIMIT %d",
                $bot['meta_auto'], $max_per
            ) );

            foreach ( $post_ids as $pid ) {
                $pid = intval( $pid );
                $p   = get_post( $pid );
                if ( ! $p || $p->post_status !== 'publish' ) continue;

                /* Skip if already in queue (dedup across bots) */
                $already = false;
                foreach ( $queue as $qi ) {
                    if ( $qi['id'] === $pid ) { $already = true; break; }
                }
                if ( $already ) continue;

                /* Quick scan to check if post needs fixing */
                $score = 100;
                if ( $has_scanner ) {
                    $det_lang = 'en';
                    if ( class_exists( 'DBA_Kaizen_Patterns_I18n' ) ) {
                        $det_lang = DBA_Kaizen_Patterns_I18n::detect_language( $pid );
                    }
                    $score = DBA_LuxStrat_Kaizen::quality_score( $pid, $det_lang );
                }

                if ( $score < $score_max ) {
                    $queue[] = array(
                        'id'    => $pid,
                        'title' => $p->post_title,
                        'bot'   => $bot['label'],
                        'score' => $score,
                    );
                }
            }
        }

        /* Sort worst scores first */
        usort( $queue, function( $a, $b ) { return $a['score'] - $b['score']; } );

        wp_send_json_success( array(
            'queue' => $queue,
            'total' => count( $queue ),
        ) );
    }

    /* ── AJAX: Check if WP cron has a publish event in next N minutes ── */
    public static function ajax_check_schedule() {
        check_ajax_referer( 'dba_kc_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Denied' );

        global $wpdb;
        $window = 15 * 60; /* 15 minutes */
        $now    = current_time( 'timestamp' );

        /* Check for scheduled (future) posts about to publish */
        $upcoming = $wpdb->get_results( $wpdb->prepare(
            "SELECT ID, post_title, post_date
             FROM {$wpdb->posts}
             WHERE post_status = 'future'
               AND post_type = 'post'
               AND UNIX_TIMESTAMP(post_date) BETWEEN %d AND %d
             ORDER BY post_date ASC
             LIMIT 5",
            $now, $now + $window
        ) );

        /* Check WP cron for DBA scheduled events */
        $cron_busy = false;
        $cron_events = _get_cron_array();
        if ( is_array( $cron_events ) ) {
            foreach ( $cron_events as $ts => $hooks ) {
                if ( $ts > $now + $window ) break;
                if ( $ts < $now ) continue;
                foreach ( $hooks as $hook => $data ) {
                    if ( strpos( $hook, 'dba_' ) === 0 || strpos( $hook, 'publish_' ) === 0 ) {
                        $cron_busy = true;
                        break 2;
                    }
                }
            }
        }

        /* Check Conflict Center global lock */
        $lock_info = null;
        $lock_active = false;
        if ( class_exists( 'DBA_Conflict_Center' ) ) {
            $lock_active = DBA_Conflict_Center::is_any_running();
            if ( $lock_active ) {
                $lock_info = DBA_Conflict_Center::get_status();
            }
        }

        $safe = ( empty( $upcoming ) && ! $cron_busy && ! $lock_active );

        wp_send_json_success( array(
            'safe'       => $safe,
            'upcoming'   => $upcoming,
            'cron_busy'  => $cron_busy,
            'lock_active'=> $lock_active,
            'lock_info'  => $lock_info,
            'window'     => 15,
        ) );
    }

    /* ── Render the admin page ── */
    public static function render_page() {
        $nonce = wp_create_nonce( 'dba_kc_nonce' );
        $bots  = self::get_bots();

        $has_24p  = class_exists( 'DBA_LuxStrat_Kaizen' ) && method_exists( 'DBA_LuxStrat_Kaizen', 'scan_single_post' );
        $has_i18n = class_exists( 'DBA_Kaizen_Patterns_I18n' );

        $languages = array( 'en' => 'English' );
        if ( $has_i18n ) {
            $languages = DBA_Kaizen_Patterns_I18n::LANGUAGES;
        }

        ?>
        <div class="wrap">
        <h1>Kaizen Center <small style="font-size:13px;color:#666;">v2.18.0</small></h1>
        <p>Test every bot's learning engine, scan posts for anti-patterns in 31 languages, flush caches.</p>

        <div style="margin:15px 0;display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
            <button class="button button-primary" id="dba-kc-flush-all">Flush ALL Caches</button>
            <span id="dba-kc-flush-all-msg" style="color:green;font-weight:bold;"></span>
        </div>

        <!-- BOT GRID -->
        <div style="display:grid;gap:16px;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));margin:20px 0;">
        <?php foreach ( $bots as $bid => $bot ) :
            $loaded = class_exists( $bot['class'] );
            $border = $loaded ? '#46b450' : '#dc3232';
            $chip   = $loaded
                ? '<span style="background:#46b450;color:#fff;padding:2px 8px;border-radius:10px;font-size:11px;">LOADED</span>'
                : '<span style="background:#dc3232;color:#fff;padding:2px 8px;border-radius:10px;font-size:11px;">NOT LOADED</span>';
        ?>
            <div class="postbox dba-kc-bot" data-bot="<?php echo esc_attr( $bid ); ?>" style="border-left:4px solid <?php echo $border; ?>;margin:0;">
                <div style="padding:15px;">
                    <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
                        <h3 style="margin:0;font-size:15px;"><?php echo esc_html( $bot['label'] ); ?></h3>
                        <?php echo $chip; ?>
                    </div>
                    <div style="font-size:12px;color:#666;margin-bottom:10px;">
                        <code><?php echo esc_html( $bot['class'] ); ?></code>
                    </div>
                    <?php if ( $loaded ) : ?>
                        <?php if ( ! empty( $bot['params'] ) ) : ?>
                            <div style="margin-bottom:8px;">
                            <?php foreach ( $bot['params'] as $p ) : ?>
                                <input type="text" class="dba-kc-param" data-param="<?php echo esc_attr( $p ); ?>"
                                       placeholder="<?php echo esc_attr( $p ); ?>"
                                       style="width:48%;margin-right:2%;font-size:12px;padding:4px 8px;" value="">
                            <?php endforeach; ?>
                            </div>
                        <?php endif; ?>
                        <div style="display:flex;gap:6px;">
                            <button class="button button-primary button-small dba-kc-run" data-bot="<?php echo esc_attr( $bid ); ?>">Run</button>
                            <?php if ( $bot['flush'] && method_exists( $bot['class'], $bot['flush'] ) ) : ?>
                                <button class="button button-small dba-kc-flush" data-bot="<?php echo esc_attr( $bid ); ?>">Flush</button>
                            <?php endif; ?>
                        </div>
                        <div class="dba-kc-result" data-bot="<?php echo esc_attr( $bid ); ?>" style="display:none;margin-top:10px;">
                            <div style="display:flex;justify-content:space-between;font-size:11px;color:#666;margin-bottom:4px;">
                                <span class="dba-kc-meta"></span>
                                <button class="button button-small dba-kc-copy" style="font-size:11px;padding:0 6px;">Copy</button>
                            </div>
                            <pre class="dba-kc-text" style="background:#1d2327;color:#50c878;padding:12px;overflow:auto;max-height:300px;font-size:11px;line-height:1.5;white-space:pre-wrap;border-radius:4px;margin:0;"></pre>
                        </div>
                    <?php else : ?>
                        <p style="color:#999;font-size:12px;margin:0;">Not loaded.</p>
                    <?php endif; ?>
                </div>
            </div>
        <?php endforeach; ?>
        </div>

        <!-- POST SCANNER -->
        <div class="postbox" style="padding:20px;margin:20px 0;">
            <h2 style="margin-top:0;">Post Pattern Scanner</h2>
            <p>Select a language, posts load automatically. Click <strong>Scan</strong> on any row.</p>
            <div style="display:flex;gap:10px;align-items:center;margin:15px 0;flex-wrap:wrap;">
                <select id="dba-kc-lang" style="min-width:220px;padding:6px;">
                    <option value="all">All languages</option>
                    <?php foreach ( $languages as $code => $label ) : ?>
                        <option value="<?php echo esc_attr( $code ); ?>"><?php echo esc_html( $label ); ?> (<?php echo esc_html( $code ); ?>)</option>
                    <?php endforeach; ?>
                </select>
                <button class="button button-primary" id="dba-kc-load">Load 10 Latest Posts</button>
                <span id="dba-kc-load-msg" style="font-size:12px;color:#666;"></span>
            </div>
            <div id="dba-kc-ptable" style="display:none;">
                <table class="widefat striped" id="dba-kc-plist" style="font-size:13px;">
                    <thead><tr><th>ID</th><th>Title</th><th>Lang</th><th>Bot</th><th>Words</th><th>H2</th><th>Date</th><th>Status</th><th></th></tr></thead>
                    <tbody></tbody>
                </table>
            </div>
            <div id="dba-kc-scan-out" style="display:none;margin-top:20px;border-top:2px solid #2271b1;padding-top:20px;">
                <h3 id="dba-kc-scan-h" style="margin-top:0;"></h3>
                <div id="dba-kc-scan-info" style="background:#f6f7f7;padding:12px;border-radius:4px;margin-bottom:15px;font-size:13px;"></div>
                <div style="display:flex;gap:15px;margin-bottom:15px;">
                    <div id="dba-kc-scan-score" style="flex:1;padding:15px;border-radius:4px;text-align:center;"></div>
                    <div id="dba-kc-scan-cta" style="flex:1;padding:15px;border-radius:4px;text-align:center;"></div>
                </div>
                <div id="dba-kc-scan-pats"></div>

                <!-- FIX WORKFLOW -->
                <div id="dba-kc-fix-bar" style="display:none;margin-top:15px;padding:15px;background:#fff8e1;border-left:4px solid #ffb900;border-radius:4px;">
                    <button class="button button-primary button-large" id="dba-kc-fix-btn">Fix This Post with AI</button>
                    <span id="dba-kc-fix-status" style="margin-left:10px;font-size:13px;color:#666;"></span>
                </div>

                <!-- PREVIEW: Before / After -->
                <div id="dba-kc-preview" style="display:none;margin-top:20px;">
                    <h3 style="margin-top:0;">Before / After Preview</h3>
                    <div id="dba-kc-preview-meta" style="background:#f6f7f7;padding:10px;border-radius:4px;margin-bottom:15px;font-size:13px;"></div>
                    <div style="display:grid;grid-template-columns:1fr 1fr;gap:15px;">
                        <div>
                            <h4 style="color:#dc3232;margin-top:0;">BEFORE (current)</h4>
                            <div id="dba-kc-preview-before" style="background:#fff;border:1px solid #ddd;padding:15px;max-height:500px;overflow:auto;font-size:13px;line-height:1.6;border-radius:4px;"></div>
                        </div>
                        <div>
                            <h4 style="color:#46b450;margin-top:0;">AFTER (fixed)</h4>
                            <div id="dba-kc-preview-after" style="background:#f0fef0;border:1px solid #46b450;padding:15px;max-height:500px;overflow:auto;font-size:13px;line-height:1.6;border-radius:4px;"></div>
                        </div>
                    </div>
                    <div style="margin-top:15px;display:flex;gap:10px;">
                        <button class="button button-primary button-large" id="dba-kc-approve" style="background:#46b450;border-color:#46b450;">Approve and Update Post</button>
                        <button class="button button-large" id="dba-kc-reject">Reject (keep original)</button>
                        <span id="dba-kc-approve-status" style="margin-left:10px;font-size:13px;color:#666;"></span>
                    </div>
                </div>
            </div>
        </div>

        <!-- BATCH PROCESSOR -->
        <div class="postbox" style="padding:20px;margin:20px 0;">
            <h2 style="margin-top:0;">Batch Auto-Fix</h2>
            <p>Scans ALL published posts from every bot, finds those scoring below threshold, and fixes them one by one with AI. Posts are processed sequentially with 15-second delays to avoid server overload.</p>

            <div style="display:flex;gap:15px;align-items:center;flex-wrap:wrap;margin:15px 0;">
                <label style="font-size:13px;">Fix posts scoring below:
                    <select id="dba-kc-batch-threshold" style="padding:4px;">
                        <option value="50">50 (heavy AI only)</option>
                        <option value="70">70 (moderate+)</option>
                        <option value="80" selected>80 (recommended)</option>
                        <option value="90">90 (polish everything)</option>
                    </select>
                </label>
                <label style="font-size:13px;">Max posts per bot:
                    <select id="dba-kc-batch-max" style="padding:4px;">
                        <option value="5">5</option>
                        <option value="10">10</option>
                        <option value="20" selected>20</option>
                        <option value="50">50</option>
                    </select>
                </label>
                <button class="button button-primary button-large" id="dba-kc-batch-start">Collect Posts for Batch Fix</button>
                <button class="button button-large" id="dba-kc-batch-stop" style="display:none;background:#dc3232;color:#fff;border-color:#dc3232;">STOP Batch</button>
            </div>

            <!-- Safety check -->
            <div id="dba-kc-batch-safety" style="display:none;margin:10px 0;padding:12px;border-radius:4px;"></div>

            <!-- Queue preview -->
            <div id="dba-kc-batch-queue" style="display:none;margin:15px 0;">
                <h4 style="margin:0 0 10px;">Posts to fix (<span id="dba-kc-batch-count">0</span>)</h4>
                <table class="widefat striped" id="dba-kc-batch-list" style="font-size:13px;">
                    <thead><tr><th>ID</th><th>Title</th><th>Bot</th><th>Score</th><th>Status</th></tr></thead>
                    <tbody></tbody>
                </table>
                <div style="margin-top:15px;">
                    <button class="button button-primary button-large" id="dba-kc-batch-run" style="background:#46b450;border-color:#46b450;">Run Batch Fix (sequential, 15s delay)</button>
                </div>
            </div>

            <!-- Progress -->
            <div id="dba-kc-batch-progress" style="display:none;margin:15px 0;">
                <div style="display:flex;justify-content:space-between;font-size:13px;margin-bottom:5px;">
                    <span id="dba-kc-batch-progress-text">Processing...</span>
                    <span id="dba-kc-batch-progress-pct">0%</span>
                </div>
                <div style="background:#ddd;border-radius:4px;height:20px;overflow:hidden;">
                    <div id="dba-kc-batch-bar" style="background:#2271b1;height:100%;width:0%;transition:width 0.5s;border-radius:4px;"></div>
                </div>
            </div>

            <!-- Log -->
            <div id="dba-kc-batch-log" style="display:none;margin:15px 0;">
                <h4 style="margin:0 0 10px;">Batch Log</h4>
                <div id="dba-kc-batch-log-entries" style="background:#1d2327;color:#ccc;padding:12px;max-height:400px;overflow:auto;font-size:12px;line-height:1.8;font-family:monospace;border-radius:4px;"></div>
            </div>
        </div>

        <script>
        (function($){
            var nonce='<?php echo esc_js( $nonce ); ?>';
            var ajaxurl='<?php echo esc_js( admin_url( "admin-ajax.php" ) ); ?>';
            function esc(s){var d=document.createElement('div');d.appendChild(document.createTextNode(s||''));return d.innerHTML;}

            /* Run */
            $(document).on('click','.dba-kc-run',function(){
                var b=$(this),bid=b.data('bot'),c=b.closest('.dba-kc-bot');
                var p={action:'dba_kc_run',nonce:nonce,bot_id:bid};
                c.find('.dba-kc-param').each(function(){p[$(this).data('param')]=$(this).val();});
                b.prop('disabled',true).text('...');
                $.post(ajaxurl,p,function(r){
                    b.prop('disabled',false).text('Run');
                    var res=c.find('.dba-kc-result[data-bot="'+bid+'"]').show();
                    if(r.success){res.find('.dba-kc-meta').text(r.data.length+' chars, '+r.data.elapsed+'ms');res.find('.dba-kc-text').css('color','#50c878').text(r.data.context);}
                    else{res.find('.dba-kc-meta').text('ERROR');res.find('.dba-kc-text').css('color','#dc3232').text(r.data||'Error');}
                });
            });
            /* Flush */
            $(document).on('click','.dba-kc-flush',function(){
                var b=$(this);b.prop('disabled',true);
                $.post(ajaxurl,{action:'dba_kc_flush',nonce:nonce,bot_id:b.data('bot')},function(){b.prop('disabled',false).text('Done!');setTimeout(function(){b.text('Flush');},1500);});
            });
            /* Flush All */
            $('#dba-kc-flush-all').on('click',function(){
                var b=$(this);b.prop('disabled',true).text('Flushing...');
                $.post(ajaxurl,{action:'dba_kc_flush_all',nonce:nonce},function(r){
                    b.prop('disabled',false).text('Flush ALL Caches');
                    if(r.success)$('#dba-kc-flush-all-msg').text('Done: '+r.data.flushed.join(', ')).delay(3000).queue(function(n){$(this).text('');n();});
                });
            });
            /* Copy */
            $(document).on('click','.dba-kc-copy',function(){
                var t=$(this).closest('.dba-kc-result').find('.dba-kc-text').text();
                if(navigator.clipboard)navigator.clipboard.writeText(t);
                $(this).text('OK!');setTimeout(function(){$('.dba-kc-copy').text('Copy');},1500);
            });

            /* Load Posts */
            function loadPosts(){
                var lang=$('#dba-kc-lang').val();
                $('#dba-kc-load').prop('disabled',true).text('Loading...');
                $('#dba-kc-load-msg').text('');$('#dba-kc-scan-out').hide();
                $.post(ajaxurl,{action:'dba_kc_load_posts',nonce:nonce,lang:lang},function(r){
                    $('#dba-kc-load').prop('disabled',false).text('Load 10 Latest Posts');
                    if(!r.success){$('#dba-kc-load-msg').text('Error: '+r.data).css('color','red');return;}
                    var ps=r.data.posts;
                    if(!ps.length){$('#dba-kc-load-msg').text('No posts found.').css('color','#999');$('#dba-kc-ptable').hide();return;}
                    $('#dba-kc-load-msg').text(ps.length+' posts').css('color','green');
                    var tb=$('#dba-kc-plist tbody').empty();
                    for(var i=0;i<ps.length;i++){
                        var p=ps[i],sc=p.status==='publish'?'green':'orange';
                        tb.append('<tr><td><strong>'+p.id+'</strong></td><td><a href="'+esc(p.url)+'" target="_blank">'+esc(p.title)+'</a></td><td><code>'+esc(p.lang)+'</code></td><td style="font-size:12px;">'+esc(p.bot)+'</td><td>'+p.words+'</td><td>'+p.h2s+'</td><td style="font-size:12px;">'+p.date+'</td><td style="color:'+sc+';font-weight:bold;font-size:11px;">'+p.status.toUpperCase()+'</td><td><button class="button button-small dba-kc-scan" data-pid="'+p.id+'">Scan</button></td></tr>');
                    }
                    $('#dba-kc-ptable').show();
                });
            }
            $('#dba-kc-load').on('click',loadPosts);
            $('#dba-kc-lang').on('change',loadPosts);

            /* ═══ FIX WORKFLOW ═══ */
            var currentFixPostId = 0;
            var currentPatterns = {};

            /* Store patterns after scan for fix */
            function storePatterns(postId, pats) {
                currentFixPostId = postId;
                currentPatterns = pats;
                var ks = Object.keys(pats);
                if (ks.length > 0) {
                    $('#dba-kc-fix-bar').show();
                    $('#dba-kc-fix-status').text(ks.length + ' patterns found. Click Fix to clean up with AI.');
                } else {
                    $('#dba-kc-fix-bar').hide();
                }
                $('#dba-kc-preview').hide();
            }

            /* ═══ 3-PASS ORCHESTRATOR — one AJAX call per pass ═══ */
            function runPass(postId, pats, passNum, onDone, onError) {
                $.ajax({
                    url: ajaxurl, type: 'POST', timeout: 120000,
                    data: { action:'dba_kc_fix_post', nonce:nonce, post_id:postId, patterns:pats, pass_number:passNum },
                    success: function(r) {
                        if (!r.success) { onError(r.data || 'error'); return; }
                        onDone(r.data);
                    },
                    error: function() { onError('Request timeout on pass ' + passNum); }
                });
            }

            function run3Pass(postId, pats, statusEl, onComplete) {
                var passLog = [];
                statusEl.text('Pass 1/3: fixing HIGH severity...');
                runPass(postId, pats, 1, function(d1) {
                    passLog.push({pass:1, valid:d1.valid!==false, skipped:!!d1.skipped, issues:d1.issues||[]});
                    statusEl.text('Pass 2/3: fixing MEDIUM+LOW...');
                    setTimeout(function(){
                        runPass(postId, pats, 2, function(d2) {
                            passLog.push({pass:2, valid:d2.valid!==false, skipped:!!d2.skipped, issues:d2.issues||[]});
                            statusEl.text('Pass 3/3: verifying & polishing...');
                            setTimeout(function(){
                                runPass(postId, pats, 3, function(d3) {
                                    d3.passLog = passLog;
                                    onComplete(true, d3);
                                }, function(err) {
                                    onComplete(false, {error: 'Pass 3: ' + err, passLog: passLog});
                                });
                            }, 3000);
                        }, function(err) {
                            if (typeof err === 'string' && err.indexOf('LOCKED:') === 0) { onComplete(false, {error: err}); return; }
                            statusEl.text('Pass 2 failed. Running pass 3 on pass 1 result...');
                            passLog.push({pass:2, valid:false, issues:[err]});
                            setTimeout(function(){
                                runPass(postId, pats, 3, function(d3) {
                                    d3.passLog = passLog;
                                    onComplete(true, d3);
                                }, function(err2) { onComplete(false, {error: err2, passLog: passLog}); });
                            }, 3000);
                        });
                    }, 3000);
                }, function(err) {
                    if (typeof err === 'string' && err.indexOf('LOCKED:') === 0) { onComplete(false, {error: err}); return; }
                    onComplete(false, {error: 'Pass 1: ' + err});
                });
            }

            /* Fix button */
            $('#dba-kc-fix-btn').on('click', function(){
                var b = $(this);
                b.prop('disabled', true);
                $('#dba-kc-preview').hide();
                var pats = JSON.stringify(currentPatterns);

                run3Pass(currentFixPostId, pats, $('#dba-kc-fix-status'), function(ok, d) {
                    b.prop('disabled', false).text('Fix This Post with AI');
                    if (!ok) {
                        var err = d.error || 'Unknown error';
                        if (typeof err === 'string' && err.indexOf('LOCKED:') === 0) {
                            $('#dba-kc-fix-status').html('<span style="color:#ffb900;font-weight:bold;">Conflict Center: locked by ' + esc(err.replace('LOCKED:','')) + '</span>');
                        } else {
                            $('#dba-kc-fix-status').html('<span style="color:red;">' + esc(err) + '</span>');
                        }
                        return;
                    }

                    $('#dba-kc-fix-status').html('<span style="color:green;">3-pass fix complete!</span>');

                    /* Pass log */
                    var passHtml = '<div style="margin-bottom:10px;"><strong>Pass Log:</strong><br>';
                    var pl = d.passLog || [];
                    for (var i=0; i<pl.length; i++) {
                        var p = pl[i], c = p.valid?'#46b450':'#dc3232', s = p.skipped?'SKIP':(p.valid?'OK':'REJECTED');
                        passHtml += '<span style="color:'+c+';font-weight:bold;">Pass '+p.pass+': '+s+'</span>';
                        if (p.issues&&p.issues.length) passHtml += ' — ' + p.issues.map(function(x){return esc(x);}).join(', ');
                        passHtml += '<br>';
                    }
                    passHtml += '</div>';

                    var scoreHtml = '';
                    if (d.orig_score!==null && d.new_score!==null) {
                        var imp = d.new_score - d.orig_score;
                        var ic = imp>0?'#46b450':(imp<0?'#dc3232':'#666');
                        scoreHtml = 'Score: <strong>'+d.orig_score+'</strong> &rarr; <strong style="color:'+ic+';">'+d.new_score+'</strong>';
                        if (imp>0) scoreHtml += ' (+'+imp+')';
                        else if (imp<0) scoreHtml += ' ('+imp+' WORSE)';
                    }

                    var valHtml = '';
                    if (d.reject_reasons && d.reject_reasons.length) {
                        valHtml = '<div style="background:#fef0f0;border-left:4px solid #dc3232;padding:10px;margin:10px 0;"><strong>Issues:</strong><br>';
                        for (var v=0;v<d.reject_reasons.length;v++) valHtml += '&bull; '+esc(d.reject_reasons[v])+'<br>';
                        valHtml += '</div>';
                    }

                    $('#dba-kc-preview-meta').html(
                        'Model: <strong>'+esc(d.model)+'</strong>'
                        + ' | Words: '+d.orig_words+' &rarr; '+d.fixed_words
                        + ' | Links: '+d.orig_links+' &rarr; '+d.fixed_links
                        + ' | H2s: '+d.orig_h2s+' &rarr; '+d.fixed_h2s
                        + ' | CTA: '+(d.cta_intact?'<span style="color:green;">OK</span>':'<span style="color:red;">MISSING</span>')
                        + (scoreHtml?' | '+scoreHtml:'')
                        + '<br>'+passHtml+valHtml
                    );
                    $('#dba-kc-preview-before').html(d.original);
                    $('#dba-kc-preview-after').html(d.fixed);
                    $('#dba-kc-preview').show();
                    $('html,body').animate({scrollTop:$('#dba-kc-preview').offset().top-50},300);
                });
            });

            /* Approve */
            $('#dba-kc-approve').on('click', function(){
                if (!confirm('This will REPLACE the post content with the fixed version. The original is saved as backup. Continue?')) return;
                var b = $(this);
                b.prop('disabled', true).text('Updating...');
                $.post(ajaxurl, {
                    action: 'dba_kc_approve_fix',
                    nonce: nonce,
                    post_id: currentFixPostId
                }, function(r) {
                    b.prop('disabled', false).text('Approve and Update Post');
                    if (!r.success) {
                        $('#dba-kc-approve-status').html('<span style="color:red;">' + esc(r.data) + '</span>');
                        return;
                    }
                    $('#dba-kc-approve-status').html('<span style="color:green;font-weight:bold;">Post updated! Original backed up in meta.</span>');
                    $('#dba-kc-fix-bar').hide();
                    /* Re-scan to show new score */
                    setTimeout(function(){
                        $('[data-pid="' + currentFixPostId + '"] .dba-kc-scan').click();
                    }, 1000);
                });
            });

            /* Reject */
            $('#dba-kc-reject').on('click', function(){
                $('#dba-kc-preview').hide();
                $('#dba-kc-fix-status').text('Fix rejected. Original unchanged.');
            });

            /* ═══════════════════════════════════════════
               BATCH PROCESSOR
               ═══════════════════════════════════════════ */
            var batchQueue = [];
            var batchRunning = false;
            var batchStopped = false;
            var batchIdx = 0;
            var BATCH_DELAY = 15000;

            function batchLog(msg, color) {
                var c = color || '#ccc';
                var ts = new Date().toLocaleTimeString();
                $('#dba-kc-batch-log-entries').append('<div style="color:'+c+';">['+ts+'] '+msg+'</div>');
                var el = document.getElementById('dba-kc-batch-log-entries');
                if (el) el.scrollTop = el.scrollHeight;
            }

            function batchProgress(idx, total) {
                var pct = total > 0 ? Math.round((idx / total) * 100) : 0;
                $('#dba-kc-batch-bar').css('width', pct + '%');
                $('#dba-kc-batch-progress-pct').text(pct + '%');
                $('#dba-kc-batch-progress-text').text('Processing ' + idx + ' / ' + total);
            }

            function batchUpdateRow(pid, status, color) {
                $('#dba-kc-batch-list tr[data-bpid="'+pid+'"] td:last').html('<span style="color:'+color+';font-weight:bold;">'+status+'</span>');
            }

            /* Collect */
            $('#dba-kc-batch-start').on('click', function(){
                var b = $(this);
                b.prop('disabled', true).text('Checking schedule...');
                batchQueue = []; batchIdx = 0; batchStopped = false;
                $('#dba-kc-batch-queue').hide();

                $.post(ajaxurl, {action:'dba_kc_check_schedule', nonce:nonce}, function(r){
                    if (!r.success) { b.prop('disabled',false).text('Collect Posts for Batch Fix'); return; }

                    if (!r.data.safe) {
                        var upcoming = r.data.upcoming || [];
                        var msg = '<strong>WARNING:</strong> ';
                        if (upcoming.length > 0) {
                            msg += upcoming.length + ' post(s) scheduled within 15 min. ';
                        }
                        if (r.data.cron_busy) msg += 'DBA cron tasks are running. ';
                        if (r.data.lock_active) {
                            var li = r.data.lock_info || {};
                            msg += '<strong>Conflict Center lock is held by: ' + esc(li.label || li.module || 'unknown') + '.</strong> ';
                        }
                        msg += '<br><strong>Wait 15 minutes</strong> before running batch fix.';
                        $('#dba-kc-batch-safety').html(msg).css({background:'#fef0f0','border-left':'4px solid #dc3232'}).show();
                        b.prop('disabled', false).text('Collect Posts for Batch Fix');
                        return;
                    }

                    $('#dba-kc-batch-safety').html('Schedule is clear - safe to run.').css({background:'#f0fef0','border-left':'4px solid #46b450'}).show();
                    b.text('Scanning all bots...');

                    $.post(ajaxurl, {
                        action: 'dba_kc_batch_collect',
                        nonce: nonce,
                        score_below: $('#dba-kc-batch-threshold').val(),
                        max_per_bot: $('#dba-kc-batch-max').val()
                    }, function(r2){
                        b.prop('disabled', false).text('Collect Posts for Batch Fix');
                        if (!r2.success) { alert(r2.data); return; }

                        batchQueue = r2.data.queue;
                        if (!batchQueue.length) {
                            $('#dba-kc-batch-safety').html('All posts score above threshold - nothing to fix!').css({background:'#f0fef0','border-left':'4px solid #46b450'}).show();
                            return;
                        }

                        $('#dba-kc-batch-count').text(batchQueue.length);
                        var tb = $('#dba-kc-batch-list tbody').empty();
                        for (var i=0; i<batchQueue.length; i++) {
                            var q = batchQueue[i];
                            var sc = q.score < 50 ? '#dc3232' : '#ffb900';
                            tb.append('<tr data-bpid="'+q.id+'"><td>'+q.id+'</td><td>'+esc(q.title)+'</td><td>'+esc(q.bot)+'</td><td style="color:'+sc+';font-weight:bold;">'+q.score+'/100</td><td>Pending</td></tr>');
                        }
                        $('#dba-kc-batch-queue').show();

                        var mins = Math.ceil(batchQueue.length * 3.5);
                        batchLog('Collected ' + batchQueue.length + ' posts. Estimated: ~' + mins + ' min (3-pass per post).', '#2271b1');
                        $('#dba-kc-batch-log').show();
                    });
                });
            });

            /* Run batch */
            $('#dba-kc-batch-run').on('click', function(){
                if (batchRunning || !batchQueue.length) return;
                if (!confirm('Auto-fix ' + batchQueue.length + ' posts (3-pass per post)?\nOriginals are backed up. Posts that fail validation won\'t be updated.\nEstimated: ~' + Math.ceil(batchQueue.length * 3.5) + ' min.\nContinue?')) return;
                batchRunning = true; batchStopped = false; batchIdx = 0;
                $(this).hide();
                $('#dba-kc-batch-stop').show();
                $('#dba-kc-batch-progress').show();
                batchLog('--- BATCH STARTED ---', '#46b450');
                processNext();
            });

            function processNext() {
                if (batchStopped || batchIdx >= batchQueue.length) { batchFinish(); return; }
                var item = batchQueue[batchIdx];
                batchProgress(batchIdx, batchQueue.length);
                batchUpdateRow(item.id, 'Scanning...', '#2271b1');
                batchLog('(' + (batchIdx+1) + '/' + batchQueue.length + ') #' + item.id + ': ' + item.title, '#fff');

                $.post(ajaxurl, {action:'dba_kc_scan_post', nonce:nonce, post_id:item.id}, function(r){
                    if (!r.success || batchStopped) {
                        batchLog('  SKIP: scan error', '#ffb900');
                        batchUpdateRow(item.id, 'Skip', '#999');
                        batchIdx++; batchWait(); return;
                    }
                    var pats = r.data.patterns || {};
                    if (!Object.keys(pats).length) {
                        batchLog('  CLEAN', '#46b450');
                        batchUpdateRow(item.id, 'Clean', '#46b450');
                        batchIdx++; batchWait(); return;
                    }
                    batchLog('  ' + Object.keys(pats).length + ' patterns. 3-pass fix...', '#ffb900');
                    batchUpdateRow(item.id, 'Pass 1/3...', '#ffb900');

                    var statusProxy = {text:function(m){ batchLog('  '+m,'#999'); batchUpdateRow(item.id,m,'#ffb900'); }};
                    run3Pass(item.id, JSON.stringify(pats), statusProxy, function(ok, d) {
                        if (batchStopped) { batchIdx++; batchWait(); return; }
                        if (!ok) {
                            var err = d.error || 'error';
                            if (typeof err === 'string' && err.indexOf('LOCKED:') === 0) {
                                batchLog('  LOCKED: ' + err.replace('LOCKED:','') + '. Waiting 60s...', '#ffb900');
                                batchUpdateRow(item.id, 'Locked (retry)', '#ffb900');
                                setTimeout(processNext, 60000);
                                return;
                            }
                            batchLog('  FAILED: ' + err, '#dc3232');
                            batchUpdateRow(item.id, 'Failed', '#dc3232');
                            batchIdx++; batchWait(); return;
                        }

                        var scoreInfo = '';
                        if (d.orig_score !== null && d.new_score !== null) scoreInfo = ' Score:' + d.orig_score + '->' + d.new_score;

                        if (!d.auto_approve) {
                            var reasons = (d.reject_reasons||[]).join('; ');
                            batchLog('  NEEDS REVIEW: ' + reasons + scoreInfo, '#ffb900');
                            batchUpdateRow(item.id, 'Needs review', '#ffb900');
                            batchIdx++; batchWait(); return;
                        }

                        batchLog('  3-pass done.' + scoreInfo + ' Approving...', '#2271b1');

                        $.post(ajaxurl, {action:'dba_kc_approve_fix', nonce:nonce, post_id:item.id}, function(r3){
                            if (r3.success) {
                                batchLog('  FIXED!', '#46b450');
                                batchUpdateRow(item.id, 'Fixed!', '#46b450');
                            } else {
                                batchLog('  Approve failed', '#dc3232');
                                batchUpdateRow(item.id, 'Error', '#dc3232');
                            }
                            batchIdx++; batchWait();
                        });
                    });
                });
            }

            function batchWait() {
                if (batchStopped || batchIdx >= batchQueue.length) { batchFinish(); return; }
                batchLog('  Waiting 15s...', '#666');
                setTimeout(processNext, BATCH_DELAY);
            }

            function batchFinish() {
                batchRunning = false;
                batchProgress(batchQueue.length, batchQueue.length);
                $('#dba-kc-batch-progress-text').text(batchStopped ? 'STOPPED' : 'Batch complete!');
                $('#dba-kc-batch-stop').hide();
                $('#dba-kc-batch-run').show();
                var done=0, fail=0, skip=0, review=0;
                $('#dba-kc-batch-list tr[data-bpid]').each(function(){
                    var t = $(this).find('td:last').text();
                    if (t.indexOf('Fixed')>=0) done++;
                    else if (t.indexOf('review')>=0) review++;
                    else if (t.indexOf('fail')>=0||t.indexOf('Error')>=0) fail++;
                    else skip++;
                });
                batchLog('--- ' + (batchStopped?'STOPPED':'COMPLETE') + ' --- Fixed:'+done+' Needs review:'+review+' Failed:'+fail+' Skipped:'+skip, batchStopped?'#ffb900':'#46b450');
            }

            $('#dba-kc-batch-stop').on('click', function(){
                batchStopped = true;
                batchLog('STOP requested...', '#dc3232');
            });

            /* Scan */
            $(document).on('click','.dba-kc-scan',function(){
                var b=$(this),pid=b.data('pid');
                $('#dba-kc-plist tbody tr').css('background','');
                b.closest('tr').css('background','#e8f0fe');
                b.prop('disabled',true).text('...');
                $.post(ajaxurl,{action:'dba_kc_scan_post',nonce:nonce,post_id:pid},function(r){
                    b.prop('disabled',false).text('Scan');
                    if(!r.success){alert(r.data);return;}
                    var d=r.data;$('#dba-kc-scan-out').show();
                    $('html,body').animate({scrollTop:$('#dba-kc-scan-out').offset().top-50},300);
                    $('#dba-kc-scan-h').text('Scan: "'+d.title+'"');
                    $('#dba-kc-scan-info').html('ID: <strong>'+d.post_id+'</strong> | Lang: <strong>'+esc(d.lang_label)+' ('+d.lang+')</strong> | Bot: '+esc(d.bot)+' | Words: <strong>'+d.words+'</strong> | H2s: '+d.h2s+' | <a href="'+esc(d.url)+'" target="_blank">View</a>');

                    /* Score */
                    if(d.score!==null){var s=d.score,c=s>=80?'#46b450':(s>=50?'#ffb900':'#dc3232'),bg=s>=80?'#f0fef0':(s>=50?'#fff8e1':'#fef0f0');$('#dba-kc-scan-score').css({background:bg,'border-left':'4px solid '+c}).html('<div style="font-size:11px;color:#666;">QUALITY SCORE</div><div style="font-size:2.5em;font-weight:bold;color:'+c+';">'+s+'/100</div>');}
                    else{$('#dba-kc-scan-score').css({background:'#f6f7f7','border-left':'4px solid #ccc'}).html('<div style="color:#999;">Scanner not available.<br>Upgrade luxstrat/class-luxstrat-kaizen.php</div>');}

                    /* CTA */
                    if(d.has_cta!==null){var cb=d.has_cta?'#46b450':'#dc3232';$('#dba-kc-scan-cta').css({background:d.has_cta?'#f0fef0':'#fef0f0','border-left':'4px solid '+cb}).html('<div style="font-size:11px;color:#666;">CTA CHECK</div><div style="font-size:2em;">'+(d.has_cta?'YES':'MISSING!')+'</div>');}
                    else{$('#dba-kc-scan-cta').css({background:'#f6f7f7','border-left':'4px solid #ccc'}).html('<div style="color:#999;">N/A</div>');}

                    /* Patterns */
                    var pats=d.patterns||{},ks=Object.keys(pats);
                    if(!ks.length){$('#dba-kc-scan-pats').html('<div style="background:#f0fef0;border-left:4px solid #46b450;padding:12px;"><strong>No anti-patterns detected!</strong></div>');storePatterns(pid,{});return;}
                    var sv={'HIGH':'#dc3232','MEDIUM':'#ffb900','LOW':'#0073aa'};
                    var h='<table class="widefat striped"><thead><tr><th>#</th><th>Pattern</th><th>Severity</th><th>Hits</th><th>Phrases</th><th>Fix</th></tr></thead><tbody>';
                    for(var k=0;k<ks.length;k++){var cat=ks[k],p=pats[cat],sev=(p.severity||'').toUpperCase();var ph=(p.top_phrases||[]).map(function(x){return '<code>'+esc(x)+'</code>';}).join(', ')||'<em>special</em>';h+='<tr><td>'+p.id+'</td><td><strong>'+esc(p.label)+'</strong></td><td style="color:'+(sv[sev]||'#666')+';font-weight:bold;">'+sev+'</td><td style="text-align:center;">'+p.hits+'</td><td>'+ph+'</td><td style="font-size:11px;color:#666;">'+esc(p.prompt)+'</td></tr>';}
                    h+='</tbody></table>';$('#dba-kc-scan-pats').html(h);
                    storePatterns(pid, pats);
                });
            });
        })(jQuery);
        </script>
        <style>
        .dba-kc-bot .button-small{font-size:12px;padding:2px 10px;}
        .dba-kc-bot input[type="text"]{height:28px;}
        .dba-kc-result pre::-webkit-scrollbar{width:6px;}
        .dba-kc-result pre::-webkit-scrollbar-thumb{background:#50c878;border-radius:3px;}
        #dba-kc-scan-pats code{background:#f0f0f0;padding:1px 5px;border-radius:3px;font-size:11px;}
        #dba-kc-plist a{color:#2271b1;}
        </style>
        </div>
        <?php
    }
}

add_action( 'admin_menu', array( 'DBA_Kaizen_Center', 'register_menu' ), 14 );
add_action( 'init', array( 'DBA_Kaizen_Center', 'register_ajax' ) );
