<?php
/**
 * DBA_EEurope_Validator
 *
 * Anti-hallucination + quality gate for all generated EEurope content.
 * Merges LuxStrat validation (slop detection, style checks, placeholder rejection)
 * with LATAM density controls (keyword stuffing prevention).
 *
 * ANTI-HALLUCINATION STRATEGY:
 * 1. Slop phrase detection — catches AI filler ("it's important to note", "in conclusion", etc.)
 * 2. Placeholder rejection — catches unfilled templates ("[INSERT", "{{", etc.)
 * 3. Fake institution detection — catches hallucinated organization names
 * 4. URL fabrication detection — catches made-up .gov or .eu links not in our verified registry
 * 5. Style validation — passive voice, sentence length, transition words
 * 6. Density control — city name max 3x, country name max 4x (Google anti-spam)
 * 7. Corporate data verification — cross-checks legal forms/CIT rates against verified spreadsheet
 *
 * @since 3.0.0
 */

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

class DBA_EEurope_Validator {

    /**
     * AI slop phrases to detect — English.
     * These betray AI-generated content and hurt SEO quality.
     */
    const SLOP_PHRASES = array(
        'in conclusion',
        'it is important to note',
        'it\'s worth noting',
        'it should be noted',
        'let\'s dive in',
        'let\'s explore',
        'dive into',
        'navigate the complexities',
        'in today\'s rapidly evolving',
        'in an ever-changing landscape',
        'it goes without saying',
        'without further ado',
        'as we have seen',
        'in this article we',
        'in the world of',
        'at the end of the day',
        'needless to say',
        'it is worth mentioning',
        'the bottom line is',
        'all in all',
        'last but not least',
        'first and foremost',
        'one of the most important',
        'it is essential to',
        'it is crucial to',
        'plays a pivotal role',
        'a game-changer',
        'robust framework',
        'holistic approach',
        'synergy',
        'paradigm shift',
        'cutting-edge',
        'state-of-the-art',
        'best-in-class',
        'world-class',
        'leverage the power',
        'unlock the potential',
        'in the realm of',
        'a plethora of',
        'myriad of',
    );

    /**
     * Placeholders that indicate incomplete content.
     */
    const PLACEHOLDERS = array(
        '[INSERT',
        '[TODO',
        '[PLACEHOLDER',
        '[TBD',
        '[FILL',
        '{{',
        '}}',
        'Lorem ipsum',
        '[YOUR',
        '[COMPANY',
        'XX%',
        'X,XXX',
        '[YEAR]',
        '[CITY]',
        '[COUNTRY]',
    );

    /**
     * Known fake institutions the AI likes to hallucinate.
     * If these appear, the content is unreliable.
     */
    const FAKE_INSTITUTIONS = array(
        'Central European Investment Authority',
        'Eastern European Business Council',
        'CEE Investment Board',
        'European Corporate Registry',
        'International Business Formation Institute',
        'Eastern European Trade Commission',
        'Central European Economic Forum',
    );

    /**
     * Validate generated content against quality and anti-hallucination criteria.
     *
     * @param  string $country_key Country being generated.
     * @param  array  $data        Generated content { title, content, meta_description, focus_keyphrase }.
     * @param  array  $config      Country configuration from registry.
     * @return array               { valid, errors, warnings }
     */
    public static function validate( $country_key, $data, $config ) {
        $errors   = array();
        $warnings = array();

        $title   = isset( $data['title'] )   ? trim( $data['title'] )   : '';
        $content = isset( $data['content'] ) ? trim( $data['content'] ) : '';
        $meta    = isset( $data['meta_description'] ) ? trim( $data['meta_description'] ) : '';

        // ── Title checks ──
        if ( empty( $title ) ) {
            $errors[] = 'Title is empty.';
        } elseif ( mb_strlen( $title ) < 20 ) {
            $errors[] = 'Title too short (min 20 chars). Got: ' . mb_strlen( $title );
        } elseif ( mb_strlen( $title ) > 120 ) {
            $warnings[] = 'Title is long (' . mb_strlen( $title ) . ' chars). Target: under 60.';
        }

        // ── Content checks ──
        if ( empty( $content ) ) {
            $errors[] = 'Content is empty.';
        } else {
            // Word count
            $word_count = str_word_count( wp_strip_all_tags( $content ) );
            $min_words  = ! empty( $config['word_count_min'] ) ? (int) $config['word_count_min'] : 1000;
            $max_words  = ! empty( $config['word_count_max'] ) ? (int) $config['word_count_max'] : 1800;
            $soft_min   = (int) round( $min_words * 0.85 );

            if ( $word_count < $soft_min ) {
                $errors[] = "Word count too low: {$word_count} (minimum: {$min_words}, hard floor: {$soft_min}).";
            } elseif ( $word_count < $min_words ) {
                $warnings[] = "Word count slightly below target: {$word_count} (target: {$min_words}).";
            }
            if ( $word_count > $max_words + 300 ) {
                $warnings[] = "Word count high: {$word_count} (target max: {$max_words}).";
            }

            // Structure
            if ( substr_count( $content, '<h2' ) < 2 ) {
                $errors[] = 'Content must have at least 2 H2 headings.';
            }
            if ( substr_count( $content, '<p' ) < 3 ) {
                $errors[] = 'Content must have at least 3 paragraphs.';
            }

            $content_lower = mb_strtolower( $content );

            // ── ANTI-HALLUCINATION: Slop phrases ──
            foreach ( self::SLOP_PHRASES as $phrase ) {
                if ( mb_strpos( $content_lower, $phrase ) !== false ) {
                    $warnings[] = 'AI slop phrase detected: "' . $phrase . '"';
                }
            }

            // ── ANTI-HALLUCINATION: Placeholders ──
            foreach ( self::PLACEHOLDERS as $ph ) {
                if ( stripos( $content, $ph ) !== false ) {
                    $errors[] = 'Placeholder text found: "' . $ph . '"';
                }
            }

            // ── ANTI-HALLUCINATION: Fake institutions ──
            foreach ( self::FAKE_INSTITUTIONS as $fi ) {
                if ( stripos( $content, $fi ) !== false ) {
                    $errors[] = 'Suspected hallucinated institution: "' . $fi . '"';
                }
            }

            // ── ANTI-HALLUCINATION: Fabricated URLs ──
            if ( preg_match_all( '/https?:\/\/[^\s"<>]+/i', $content, $url_matches ) ) {
                $allowed_domains = array( 'damalion.com', 'www.damalion.com' );
                foreach ( $url_matches[0] as $found_url ) {
                    $host = wp_parse_url( $found_url, PHP_URL_HOST );
                    if ( $host && ! in_array( $host, $allowed_domains, true ) ) {
                        // Only Damalion URLs are allowed — all external URLs are potential hallucinations
                        $errors[] = 'Unauthorized external URL detected (possible hallucination): ' . $found_url;
                    }
                }
            }

            // ── ANTI-HALLUCINATION: Source attribution (GPT must not cite sources) ──
            $source_patterns = array(
                'according to',
                'reported by',
                'as per the',
                'data from the',
                'published by',
                'study by',
                'survey by',
                'research by',
                'analysis by',
                'report by',
            );
            $source_count = 0;
            foreach ( $source_patterns as $sp ) {
                if ( mb_stripos( $content, $sp ) !== false ) $source_count++;
            }
            if ( $source_count >= 2 ) {
                $warnings[] = 'Multiple source attributions detected (' . $source_count . '). Articles should state facts directly without citing sources.';
            }

            // ── ANTI-HALLUCINATION: Heading label prefixes ──
            if ( preg_match( '/<h[23][^>]*>\s*(Conclusion|Introduction|Summary|Overview|Final Thoughts|In Conclusion)\s*:/i', $content ) ) {
                $warnings[] = 'Heading has label prefix (e.g. "Conclusion:"). Strip these.';
            }

            // ── CORPORATE DATA VERIFICATION ──
            if ( class_exists( 'DBA_EEurope_Cities' ) ) {
                $corp = DBA_EEurope_Cities::get_corporate_data();
                if ( isset( $corp[ $country_key ] ) ) {
                    $cd = $corp[ $country_key ];
                    // Check if the legal form mentioned matches the verified one
                    $legal_form = $cd['legal_form'];
                    // Only warn if article mentions a DIFFERENT legal form for this country
                    $known_forms = array( 'Sp. z o.o.', 's.r.o.', 'd.o.o.', 'Kft.', 'SRL', 'OOD', 'EOOD', 'OÜ', 'SIA', 'UAB' );
                    foreach ( $known_forms as $form ) {
                        if ( $form === $legal_form ) continue; // correct form
                        if ( stripos( $content, $form ) !== false && stripos( $content, $legal_form ) === false ) {
                            $warnings[] = 'Article mentions "' . $form . '" but the verified legal form for ' . $config['label'] . ' is "' . $legal_form . '".';
                        }
                    }
                }
            }
        }

        // ── Meta description check ──
        if ( empty( $meta ) ) {
            $warnings[] = 'Meta description is empty.';
        } elseif ( mb_strlen( $meta ) > 160 ) {
            $warnings[] = 'Meta description too long (' . mb_strlen( $meta ) . ' chars). Target: max 135.';
        }

        return array(
            'valid'    => empty( $errors ),
            'errors'   => $errors,
            'warnings' => $warnings,
        );
    }

    /**
     * Validate writing style: passive voice, sentence length, transition words.
     *
     * @param  array  $data  Generated content with 'content' key.
     * @return array         Error messages (empty if all checks pass).
     */
    public static function validate_style( $data ) {
        $errors = array();

        if ( empty( $data['content'] ) ) return $errors;

        $text = wp_strip_all_tags( $data['content'] );

        $sentences = preg_split( '/(?<=[.!?])\s+/', $text, -1, PREG_SPLIT_NO_EMPTY );
        $sentences = array_filter( $sentences, function( $s ) {
            return mb_strlen( trim( $s ) ) > 10;
        } );
        $sentences      = array_values( $sentences );
        $sentence_count = count( $sentences );

        if ( $sentence_count < 5 ) return $errors;

        // ── 1. Passive voice (threshold: 10%) ──
        $passive_regex = '/\b(is|are|was|were|been|being|be)\s+(\w+ly\s+)?((?:[a-z]+ed|built|held|made|set|run|done|given|known|seen|shown|found|kept|meant|paid|sold|told|written|chosen|driven|spoken|taken|broken|frozen|gotten|proven|worn|torn|sworn|born|borne|drawn|grown|thrown|begun|drunk|shrunk|sunk|struck|stuck)\b)/i';

        $passive_count = 0;
        foreach ( $sentences as $s ) {
            if ( preg_match( $passive_regex, $s ) ) $passive_count++;
        }
        $passive_pct = round( ( $passive_count / $sentence_count ) * 100, 1 );

        if ( $passive_pct > 10 ) {
            $errors[] = 'Passive voice is at ' . $passive_pct . '% (' . $passive_count . '/' . $sentence_count . '). Target: under 10%.';
        }

        // ── 2. Sentence length (threshold: 25% over 20 words) ──
        $long_count = 0;
        foreach ( $sentences as $s ) {
            if ( str_word_count( trim( $s ) ) > 20 ) $long_count++;
        }
        $long_pct = round( ( $long_count / $sentence_count ) * 100, 1 );

        if ( $long_pct > 25 ) {
            $errors[] = 'Long sentences: ' . $long_pct . '% exceed 20 words. Target: under 25%.';
        }

        // ── 3. Transition words (threshold: 30%) ──
        $transitions = array(
            'however', 'consequently', 'furthermore', 'therefore', 'moreover',
            'in contrast', 'for example', 'in particular', 'nevertheless', 'thus',
            'notably', 'in practice', 'although', 'in this regard', 'in this context',
            'finally', 'additionally', 'indeed', 'meanwhile', 'accordingly',
            'besides', 'in other words', 'as a result', 'specifically', 'likewise',
            'on the other hand', 'similarly', 'in summary', 'first', 'second',
            'third', 'also', 'equally', 'subsequently', 'hence', 'conversely',
            'that said', 'as such', 'to illustrate', 'for instance',
        );

        $pattern = '/\b(' . implode( '|', array_map( function( $t ) {
            return preg_quote( $t, '/' );
        }, $transitions ) ) . ')\b/i';

        $transition_count = 0;
        foreach ( $sentences as $s ) {
            if ( preg_match( $pattern, $s ) ) $transition_count++;
        }
        $transition_pct = round( ( $transition_count / $sentence_count ) * 100, 1 );

        if ( $transition_pct < 30 ) {
            $errors[] = 'Transition words in only ' . $transition_pct . '% of sentences. Target: at least 30%.';
        }

        error_log( 'DBA EEurope style — Passive: ' . $passive_pct . '% | Long: ' . $long_pct . '% | Transitions: ' . $transition_pct . '%' );

        return $errors;
    }

    /**
     * Post-process content: strip heading labels, enforce density limits.
     *
     * @param  string &$content HTML content (modified by reference).
     * @param  string $city_name
     * @param  string $country_label
     * @param  int    $max_city    Max city name occurrences (default 3).
     * @param  int    $max_country Max country name occurrences (default 4).
     * @return array  { city_reduced, country_reduced }
     */
    public static function enforce_density( &$content, $city_name, $country_label, $max_city = 3, $max_country = 4 ) {
        $reduced = array( 'city_reduced' => 0, 'country_reduced' => 0 );

        // Strip heading labels
        $content = preg_replace( '/(<h[23][^>]*>)\s*(Conclusion|Introduction|Summary|Overview|Final Thoughts|In Conclusion)\s*:\s*/i', '$1', $content );

        $plain = strtolower( wp_strip_all_tags( $content ) );

        // ── City density ──
        $city_lower = mb_strtolower( $city_name );
        $city_count = substr_count( $plain, $city_lower );
        if ( $city_count > $max_city && mb_strlen( $city_name ) > 2 ) {
            $replacements = array( 'the city', 'this urban center', 'the municipality', 'the capital' );
            $parts = preg_split( '/(<[^>]+>)/s', $content, -1, PREG_SPLIT_DELIM_CAPTURE );
            $kept = 0;
            if ( $parts !== false ) {
                for ( $pi = 0; $pi < count( $parts ); $pi++ ) {
                    if ( $pi % 2 === 0 ) {
                        $parts[$pi] = preg_replace_callback(
                            '/\b(' . preg_quote( $city_name, '/' ) . ')\b/iu',
                            function( $m ) use ( &$kept, $max_city, $replacements, &$reduced ) {
                                $kept++;
                                if ( $kept <= $max_city ) return $m[0];
                                $reduced['city_reduced']++;
                                return $replacements[ $reduced['city_reduced'] % count( $replacements ) ];
                            },
                            $parts[$pi]
                        );
                    }
                }
                $content = implode( '', $parts );
            }
        }

        // ── Country density ──
        $country_lower = mb_strtolower( $country_label );
        $country_count = substr_count( $plain, $country_lower );
        if ( $country_count > $max_country ) {
            $c_replacements = array( 'the country', 'this economy', 'the nation', 'the domestic market' );
            $parts = preg_split( '/(<[^>]+>)/s', $content, -1, PREG_SPLIT_DELIM_CAPTURE );
            $kept = 0;
            if ( $parts !== false ) {
                for ( $pi = 0; $pi < count( $parts ); $pi++ ) {
                    if ( $pi % 2 === 0 ) {
                        $parts[$pi] = preg_replace_callback(
                            '/\b(' . preg_quote( $country_label, '/' ) . ')\b/iu',
                            function( $m ) use ( &$kept, $max_country, $c_replacements, &$reduced ) {
                                $kept++;
                                if ( $kept <= $max_country ) return $m[0];
                                $reduced['country_reduced']++;
                                return $c_replacements[ $reduced['country_reduced'] % count( $c_replacements ) ];
                            },
                            $parts[$pi]
                        );
                    }
                }
                $content = implode( '', $parts );
            }
        }

        return $reduced;
    }
}
