<?php
/**
 * DBA Startup Hub Validator
 *
 * Validates AI-generated startup ecosystem content before publication.
 * Checks JSON structure, word count, headings, CTA presence, company links,
 * placeholder text, and AI slop phrases.
 *
 * @since 2.16.0
 */

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

class DBA_Startup_Hub_Validator {

    private static $instance = null;
    private $warnings = array();

    public static function get_instance() {
        if ( null === self::$instance ) self::$instance = new self();
        return self::$instance;
    }

    /**
     * Validate a raw JSON string from OpenAI.
     *
     * @param  string $raw_json The JSON string to validate.
     * @param  string $city     Expected city name (for city-relevance checks).
     * @return array  Array of error strings. Empty = all checks passed.
     */
    public function validate( $raw_json, $city = '' ) {
        $errors         = array();
        $this->warnings = array();

        // ── JSON parse ──
        $data = json_decode( $raw_json, true );
        if ( json_last_error() !== JSON_ERROR_NONE ) {
            $errors[] = 'Invalid JSON: ' . json_last_error_msg();
            return $errors;
        }

        // ── Required fields ──
        $required = array( 'title', 'slug', 'content_html', 'excerpt' );
        foreach ( $required as $field ) {
            if ( empty( $data[ $field ] ) ) {
                $errors[] = "Missing required field: {$field}";
            }
        }
        if ( ! empty( $errors ) ) return $errors;

        $title   = trim( $data['title'] );
        $content = trim( $data['content_html'] );
        $excerpt = trim( $data['excerpt'] );

        // ── Title checks ──
        if ( mb_strlen( $title ) < 20 ) {
            $errors[] = 'Title too short (' . mb_strlen( $title ) . ' chars, min 20).';
        }
        if ( mb_strlen( $title ) > 200 ) {
            $this->warnings[] = 'Title very long (' . mb_strlen( $title ) . ' chars). Consider shortening for SEO.';
        }

        // ── Content word count ──
        $config   = DBA_Startup_Hub_Registry::get_config();
        $settings = get_option( $config['settings_option'], array() );
        $min_words = max( 500, intval( $settings['word_count_min'] ?? 1000 ) );
        $max_words = max( $min_words, intval( $settings['word_count_max'] ?? 1800 ) );

        $word_count = str_word_count( strip_tags( $content ) );
        $soft_min   = (int) round( $min_words * 0.88 ); // 12% tolerance

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

        // ── Structure: H2 headings ──
        $h2_count = substr_count( $content, '<h2' );
        if ( $h2_count < 2 ) {
            $errors[] = "Content must have at least 2 H2 headings (found: {$h2_count}).";
        }

        // ── Structure: Paragraphs ──
        $p_count = substr_count( $content, '<p' );
        if ( $p_count < 3 ) {
            $errors[] = "Content must have at least 3 paragraphs (found: {$p_count}).";
        }

        // ── CTA presence ──
        if ( stripos( $content, 'dba-startup-hub-cta' ) === false && stripos( $content, 'damalion' ) === false ) {
            $this->warnings[] = 'No Damalion CTA div found in content. CTA will be injected after generation.';
        }

        // ── Featured image object ──
        if ( empty( $data['featured_image'] ) || empty( $data['featured_image']['prompt'] ) ) {
            $this->warnings[] = 'Missing featured_image object or DALL-E prompt. Fallback prompt will be used.';
        }

        // ── Yoast SEO object ──
        if ( empty( $data['yoast_seo'] ) || empty( $data['yoast_seo']['focus_keyphrase'] ) ) {
            $this->warnings[] = 'Missing yoast_seo object or focus_keyphrase. SEO fields may be incomplete.';
        }

        // ── Placeholder text ──
        $placeholders = array( '[INSERT', '[TODO', '[PLACEHOLDER', '{{', '}}', 'Lorem ipsum', '[CITY]', '[CATEGORY]' );
        foreach ( $placeholders as $ph ) {
            if ( stripos( $content, $ph ) !== false ) {
                $errors[] = "Placeholder text found: \"{$ph}\"";
            }
        }

        // ── AI slop phrases ──
        $slop_phrases = array(
            'in conclusion',
            'it is important to note',
            'as we have seen',
            'let\'s dive in',
            'without further ado',
            'in today\'s ever-changing',
            'navigating the complex',
            'in the ever-evolving',
            'it\'s worth noting',
            'at the end of the day',
        );
        $content_lower = mb_strtolower( $content );
        foreach ( $slop_phrases as $phrase ) {
            if ( strpos( $content_lower, $phrase ) !== false ) {
                $this->warnings[] = "AI slop phrase detected: \"{$phrase}\".";
            }
        }

        // ── City relevance check ──
        if ( ! empty( $city ) && stripos( $content, $city ) === false ) {
            $errors[] = "City \"{$city}\" not mentioned anywhere in the content.";
        }

        // ── Startups mentioned ──
        if ( empty( $data['startups_mentioned'] ) || ! is_array( $data['startups_mentioned'] ) ) {
            $this->warnings[] = 'No startups_mentioned array provided. Research data may not have been incorporated.';
        }

        return $errors;
    }

    /**
     * Get warnings from the last validate() call.
     *
     * @return array
     */
    public function get_warnings() {
        return $this->warnings;
    }
}
