<?php
/**
 * DBA GeoBot Kaizen — Learning Engine
 *
 * Learns from editor corrections on GeoBot articles to improve future
 * generations. Tracks structural patterns to force variation and prevent
 * the "cookie-cutter" problem where every city article reads identically.
 *
 * Features:
 *   - Stores original AI output for diff comparison
 *   - Detects title, content, H2, excerpt, and link corrections
 *   - Tracks sentence openers / paragraph patterns across recent posts
 *   - Injects learning context into the system prompt
 *   - Identifies overused transition words and blacklists them
 *   - Forces structural variety (opening angles, paragraph lengths, transitions)
 *
 * ═══════════════════════════════════════════════════════════════════════
 *  ZERO CONFLICT: Completely separate from Startup Hub Kaizen.
 *  Delegates to core DBA_Kaizen when available, works standalone if not.
 * ═══════════════════════════════════════════════════════════════════════
 *
 * @since 2.17.0
 */

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

class DBA_GeoBot_Kaizen {

    /* ── Meta keys (GeoBot-specific namespace) ── */
    const META_ORIGINAL_CONTENT  = '_dba_geobot_original_content';
    const META_ORIGINAL_TITLE    = '_dba_geobot_original_title';
    const META_ORIGINAL_EXCERPT  = '_dba_geobot_original_excerpt';
    const META_STRUCTURE_HASH    = '_dba_geobot_structure_hash';
    const META_OPENER_SIGNATURE  = '_dba_geobot_opener_sig';

    const TRANSIENT_KEY  = 'dba_geobot_kaizen_context';
    const LOOKBACK_DAYS  = 5;
    const MAX_CORRECTIONS_IN_PROMPT = 12;

    /**
     * Get learning context string to append to the GeoBot system prompt.
     *
     * Combines:
     *   1. Core Kaizen corrections (if available)
     *   2. GeoBot-specific editor corrections (title, content, H2 changes)
     *   3. Overused transition warnings
     *
     * @param string $country_key  Country key for scoping.
     * @return string              Prompt injection string (may be empty).
     */
    public static function get_learning_context( string $country_key = '' ) {

        // Try core DBA_Kaizen first (it aggregates across all bots)
        $core_context = '';
        if ( class_exists( 'DBA_Kaizen' ) && method_exists( 'DBA_Kaizen', 'get_learning_context' ) ) {
            $core_context = DBA_Kaizen::get_learning_context();
        }

        // GeoBot-specific context (country-scoped)
        $cache_key = self::TRANSIENT_KEY . '_' . $country_key;
        $cached    = get_transient( $cache_key );

        if ( $cached === false ) {
            $cached = self::build_context( $country_key );
            set_transient( $cache_key, $cached, 6 * HOUR_IN_SECONDS );
        }

        // Merge both contexts
        $combined = trim( $core_context . "\n" . $cached );
        return $combined;
    }

    /**
     * Store the original AI-generated content for later diff comparison.
     *
     * Also computes and stores a "structure hash" — a fingerprint of the
     * article's skeleton (H2 order, paragraph count per section, opener words)
     * used to detect cookie-cutter repetition.
     *
     * @param int    $post_id
     * @param string $title
     * @param string $content
     * @param string $excerpt
     */
    public static function store_original( $post_id, $title, $content, $excerpt ) {

        // GeoBot-specific meta
        update_post_meta( $post_id, self::META_ORIGINAL_TITLE,   $title );
        update_post_meta( $post_id, self::META_ORIGINAL_CONTENT, $content );
        update_post_meta( $post_id, self::META_ORIGINAL_EXCERPT, $excerpt );

        // Structure fingerprint for anti-repetition checks
        $hash = self::compute_structure_hash( $content );
        update_post_meta( $post_id, self::META_STRUCTURE_HASH, $hash );

        // Opener signature (first 3 words of each section)
        $opener = self::compute_opener_signature( $content );
        update_post_meta( $post_id, self::META_OPENER_SIGNATURE, $opener );

        // Also store in core Kaizen meta if available
        if ( class_exists( 'DBA_Kaizen' ) && method_exists( 'DBA_Kaizen', 'store_original' ) ) {
            DBA_Kaizen::store_original( $post_id, $title, $content, $excerpt );
        } else {
            // Fallback: store in the standard core meta keys too
            update_post_meta( $post_id, '_dba_original_title',   $title );
            update_post_meta( $post_id, '_dba_original_content', $content );
        }

        // Invalidate cache so next generation picks up corrections
        self::invalidate_cache();
    }

    /**
     * Invalidate all country caches so fresh context is built next run.
     */
    public static function invalidate_cache() {
        if ( class_exists( 'DBA_GeoBot_Registry' ) ) {
            foreach ( DBA_GeoBot_Registry::get_countries() as $key => $cfg ) {
                delete_transient( self::TRANSIENT_KEY . '_' . $key );
            }
        }
        delete_transient( self::TRANSIENT_KEY . '_' );
    }

    /* =================================================================
     *  STRUCTURE FINGERPRINTING — Anti-Cookie-Cutter Engine
     *
     *  Detects when multiple articles share the same skeleton:
     *    - Same H2 order with same paragraph counts
     *    - Same opening words in each section
     *    - Same transition patterns
     * ================================================================= */

    /**
     * Compute a structure hash from article HTML.
     *
     * Format: "h2:X|pp:A-B-C-D|avg:Y"
     * where A-B-C-D are paragraph counts per section.
     *
     * @param string $html
     * @return string
     */
    public static function compute_structure_hash( string $html ) {

        // Split by H2 sections
        $sections = preg_split( '/<h2[^>]*>/i', $html );
        $h2_count = max( 0, count( $sections ) - 1 );

        // Count paragraphs per section
        $para_counts = array();
        foreach ( $sections as $section ) {
            $p_count = preg_match_all( '/<p[^>]*>/i', $section );
            $para_counts[] = $p_count;
        }

        // Average paragraph length (words)
        $all_word_counts = array();
        preg_match_all( '/<p[^>]*>(.*?)<\/p>/si', $html, $matches );
        foreach ( $matches[1] as $p_html ) {
            $words = str_word_count( wp_strip_all_tags( $p_html ) );
            if ( $words > 0 ) $all_word_counts[] = $words;
        }
        $avg_len = count( $all_word_counts ) > 0
            ? round( array_sum( $all_word_counts ) / count( $all_word_counts ) )
            : 0;

        return 'h2:' . $h2_count
            . '|pp:' . implode( '-', $para_counts )
            . '|avg:' . $avg_len;
    }

    /**
     * Compute opener signature — first 3 words of each section's first paragraph.
     *
     * @param string $html
     * @return string  Pipe-separated openers: "opener1|opener2|opener3"
     */
    public static function compute_opener_signature( string $html ) {

        $sections = preg_split( '/<h2[^>]*>/i', $html );
        $openers  = array();

        foreach ( $sections as $section ) {
            if ( preg_match( '/<p[^>]*>(.*?)<\/p>/si', $section, $m ) ) {
                $text   = wp_strip_all_tags( $m[1] );
                $words  = preg_split( '/\s+/', trim( $text ), 4 );
                $opener = mb_strtolower( implode( ' ', array_slice( $words, 0, 3 ) ), 'UTF-8' );
                if ( mb_strlen( $opener, 'UTF-8' ) > 5 ) {
                    $openers[] = $opener;
                }
            }
        }

        return implode( '|', $openers );
    }

    /**
     * Check if a new article's structure is too similar to recent posts.
     *
     * Returns warnings injected into retry prompts or the next generation.
     *
     * @param string $html         New article HTML.
     * @param string $country_key  Country key.
     * @return array               Array of structural warnings (empty = fine).
     */
    public static function check_structural_variety( string $html, string $country_key ) {

        $warnings = array();
        $config   = DBA_GeoBot_Registry::get_country( $country_key );
        if ( ! $config ) return $warnings;

        $new_hash   = self::compute_structure_hash( $html );
        $new_opener = self::compute_opener_signature( $html );

        // Get recent GeoBot posts for this country
        $meta_key = $config['meta_prefix'] . 'auto_created';
        $recent   = get_posts( array(
            'post_type'      => 'post',
            'post_status'    => array( 'publish', 'draft', 'pending' ),
            'posts_per_page' => 15,
            'meta_query'     => array(
                array( 'key' => $meta_key, 'compare' => 'EXISTS' ),
            ),
            'fields'  => 'ids',
            'orderby' => 'date',
            'order'   => 'DESC',
        ) );

        if ( empty( $recent ) ) return $warnings;

        $hash_matches   = 0;
        $opener_matches = 0;

        foreach ( $recent as $pid ) {
            $existing_hash   = get_post_meta( $pid, self::META_STRUCTURE_HASH, true );
            $existing_opener = get_post_meta( $pid, self::META_OPENER_SIGNATURE, true );

            // Exact structure hash match
            if ( $existing_hash && $existing_hash === $new_hash ) {
                $hash_matches++;
            }

            // Opener overlap (>60% of section openers are the same)
            if ( $existing_opener && $new_opener ) {
                $existing_parts = explode( '|', $existing_opener );
                $new_parts      = explode( '|', $new_opener );
                $overlap        = count( array_intersect( $new_parts, $existing_parts ) );
                $total          = max( count( $new_parts ), 1 );
                if ( $overlap / $total > 0.6 ) {
                    $opener_matches++;
                }
            }
        }

        $checked = count( $recent );

        if ( $hash_matches >= 3 ) {
            $warnings[] = "STRUCTURAL CLONE: {$hash_matches}/{$checked} recent articles have IDENTICAL paragraph structure. VARY your structure: change paragraph counts per section, merge short sections, split long ones, add sub-points in some sections but not others.";
        }

        if ( $opener_matches >= 3 ) {
            $warnings[] = "OPENER REPETITION: {$opener_matches}/{$checked} recent articles start sections with the SAME opening words. START each section differently: a statistic, a question, a contrast, a historical reference, a geographic detail, a company mention.";
        }

        return $warnings;
    }

    /**
     * Get transition words overused in recent posts (for blacklisting).
     *
     * @param string $country_key
     * @return array  List of overused transition phrases with counts.
     */
    public static function get_overused_transitions( string $country_key ) {

        $config = DBA_GeoBot_Registry::get_country( $country_key );
        if ( ! $config ) return array();

        $meta_key = $config['meta_prefix'] . 'auto_created';
        $recent   = get_posts( array(
            'post_type'      => 'post',
            'post_status'    => array( 'publish', 'draft' ),
            'posts_per_page' => 10,
            'meta_query'     => array(
                array( 'key' => $meta_key, 'compare' => 'EXISTS' ),
            ),
            'fields' => 'ids',
        ) );

        if ( empty( $recent ) ) return array();

        // Common AI transition patterns (multilingual)
        $transition_patterns = array(
            // English
            'moreover', 'furthermore', 'in addition', 'additionally', 'notably',
            'in particular', 'specifically', 'in recent years', 'it is worth noting',
            'plays a key role', 'is home to', 'stands out', 'has emerged',
            // French
            'de plus', 'en outre', 'par ailleurs', 'notamment', 'il convient de noter',
            'en particulier', 'ces dernières années', 'joue un rôle clé',
            'se distingue', 'a émergé', 'il est à noter',
            // Italian
            'inoltre', 'in particolare', 'nello specifico', 'è importante notare',
            'negli ultimi anni', 'va sottolineato', 'per quanto riguarda',
            'svolge un ruolo chiave', 'si distingue', 'è emersa',
            // Spanish
            'además', 'en particular', 'cabe destacar', 'en los últimos años',
            'por otra parte', 'asimismo', 'es importante señalar',
            'desempeña un papel clave', 'se destaca', 'ha surgido',
            // Portuguese
            'além disso', 'em particular', 'nos últimos anos', 'vale ressaltar',
            'por outro lado', 'é importante notar', 'desempenha um papel fundamental',
            'destaca-se', 'tem emergido',
        );

        $counts = array_fill_keys( $transition_patterns, 0 );

        foreach ( $recent as $pid ) {
            $content = mb_strtolower( wp_strip_all_tags( get_post_field( 'post_content', $pid ) ), 'UTF-8' );
            foreach ( $transition_patterns as $phrase ) {
                $count = mb_substr_count( $content, $phrase );
                $counts[ $phrase ] += $count;
            }
        }

        // Flag phrases used ≥5 times across last 10 posts
        $overused = array();
        foreach ( $counts as $phrase => $count ) {
            if ( $count >= 5 ) {
                $overused[] = $phrase . " (used {$count}× in last 10 posts)";
            }
        }

        return $overused;
    }

    /* =================================================================
     *  CONTEXT BUILDER — Generates prompt injection from corrections
     * ================================================================= */

    /**
     * Build learning context from recent GeoBot corrections.
     *
     * @param string $country_key
     * @return string
     */
    private static function build_context( string $country_key ) {

        if ( empty( $country_key ) || ! class_exists( 'DBA_GeoBot_Registry' ) ) return '';

        $config = DBA_GeoBot_Registry::get_country( $country_key );
        if ( ! $config ) return '';

        $meta_key = $config['meta_prefix'] . 'auto_created';

        $recent = get_posts( array(
            'post_type'      => 'post',
            'post_status'    => array( 'publish', 'draft' ),
            'meta_key'       => $meta_key,
            'meta_compare'   => 'EXISTS',
            'posts_per_page' => 20,
            'fields'         => 'ids',
            'date_query'     => array( array( 'after' => self::LOOKBACK_DAYS . ' days ago' ) ),
            'orderby'        => 'date',
            'order'          => 'DESC',
        ) );

        if ( empty( $recent ) ) return '';

        $corrections = array();

        foreach ( $recent as $pid ) {
            $original_title   = get_post_meta( $pid, self::META_ORIGINAL_TITLE, true );
            $original_content = get_post_meta( $pid, self::META_ORIGINAL_CONTENT, true );
            $original_excerpt = get_post_meta( $pid, self::META_ORIGINAL_EXCERPT, true );

            if ( empty( $original_content ) ) continue;

            $current_title   = get_the_title( $pid );
            $current_content = get_post_field( 'post_content', $pid );
            $current_excerpt = get_post_field( 'post_excerpt', $pid );
            $commune         = get_post_meta( $pid, $config['meta_prefix'] . 'commune', true ) ?: "#{$pid}";

            // ── Title corrections ──
            if ( ! empty( $original_title ) && $original_title !== $current_title ) {
                $corrections[] = "TITLE CORRECTION [{$commune}]: \"{$original_title}\" → \"{$current_title}\"";
            }

            // ── Excerpt corrections ──
            if ( ! empty( $original_excerpt ) && $original_excerpt !== $current_excerpt ) {
                $corrections[] = "EXCERPT CORRECTION [{$commune}]: Editor rewrote the excerpt";
            }

            // ── Content word count changes ──
            $original_words = str_word_count( strip_tags( $original_content ) );
            $current_words  = str_word_count( strip_tags( $current_content ) );
            if ( $original_words > 0 ) {
                $diff_pct = abs( $current_words - $original_words ) / $original_words * 100;
                if ( $diff_pct > 5 ) {
                    $direction = $current_words > $original_words ? 'EXPANDED' : 'TRIMMED';
                    $corrections[] = "CONTENT {$direction} [{$commune}]: {$original_words} → {$current_words} words (" . round( $diff_pct ) . "% change)";
                }
            }

            // ── H2 heading changes ──
            preg_match_all( '/<h2[^>]*>(.*?)<\/h2>/si', $original_content, $orig_h2s );
            preg_match_all( '/<h2[^>]*>(.*?)<\/h2>/si', $current_content, $curr_h2s );
            $orig_h2_list = array_map( 'strip_tags', $orig_h2s[1] ?? array() );
            $curr_h2_list = array_map( 'strip_tags', $curr_h2s[1] ?? array() );

            $added_h2s   = array_diff( $curr_h2_list, $orig_h2_list );
            $removed_h2s = array_diff( $orig_h2_list, $curr_h2_list );

            if ( ! empty( $added_h2s ) ) {
                $corrections[] = "HEADINGS ADDED [{$commune}]: " . implode( ', ', array_slice( $added_h2s, 0, 3 ) );
            }
            if ( ! empty( $removed_h2s ) ) {
                $corrections[] = "HEADINGS REMOVED [{$commune}]: " . implode( ', ', array_slice( $removed_h2s, 0, 3 ) );
            }

            // ── Link corrections ──
            preg_match_all( '/<a\s[^>]*href=["\']([^"\']+)["\'][^>]*>/i', $original_content, $orig_links );
            preg_match_all( '/<a\s[^>]*href=["\']([^"\']+)["\'][^>]*>/i', $current_content, $curr_links );
            $added_links   = array_diff( $curr_links[1] ?? array(), $orig_links[1] ?? array() );
            $removed_links = array_diff( $orig_links[1] ?? array(), $curr_links[1] ?? array() );

            if ( count( $added_links ) > 0 ) {
                $corrections[] = "LINKS ADDED [{$commune}]: " . count( $added_links ) . " new links added by editor";
            }
            if ( count( $removed_links ) > 0 ) {
                $corrections[] = "LINKS REMOVED [{$commune}]: " . count( $removed_links ) . " links removed (possibly bad URLs)";
            }
        }

        if ( empty( $corrections ) ) return '';

        // ── Also gather overused transitions ──
        $overused = self::get_overused_transitions( $country_key );

        $context  = "\n\n══ KAIZEN — EDITOR CORRECTIONS FOR GEOBOT (" . strtoupper( $country_key ) . ") ══\n";
        $context .= "The following corrections were made by a human editor on recent GeoBot articles.\n";
        $context .= "APPLY these patterns to improve your output and AVOID repeating the same mistakes:\n\n";
        $context .= implode( "\n", array_slice( $corrections, 0, self::MAX_CORRECTIONS_IN_PROMPT ) );

        if ( count( $corrections ) > self::MAX_CORRECTIONS_IN_PROMPT ) {
            $context .= "\n… and " . ( count( $corrections ) - self::MAX_CORRECTIONS_IN_PROMPT ) . " more corrections";
        }

        if ( ! empty( $overused ) ) {
            $context .= "\n\nOVERUSED TRANSITIONS — AVOID these (find fresh alternatives):\n";
            foreach ( array_slice( $overused, 0, 8 ) as $phrase ) {
                $context .= "✗ {$phrase}\n";
            }
        }

        $context .= "\n══════════════════════════════════════════════════════════\n";

        return $context;
    }
}
