<?php

/**
 * Readability Analysis
 *
 * Provides Flesch-Kincaid Reading Ease score calculation.
 *
 * @package ProRank\SEO\Modules\Content
 * @since   1.0.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Modules\Content;

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


/**
 * Readability class
 */


class Readability {
    
    /**
     * Calculate Flesch-Kincaid Reading Ease score
     *
     * Formula: 206.835 - 1.015 * (total words / total sentences) - 84.6 * (total syllables / total words)
     *
     * @param string $text Text to analyze.
     * @return float|null Score between 0-100, or null if calculation impossible.
     */
    public static function calculate_flesch_kincaid(string $text): ?float {
        // Clean and prepare text
        $text = trim($text);
        
        // Handle empty or very short text
        if (empty($text) || strlen($text) < 10) {
            return null;
        }
        
        // Count components
        $word_count = self::count_words($text);
        $sentence_count = self::count_sentences($text);
        $syllable_count = self::count_syllables($text);
        
        // Validate counts to prevent division by zero
        if ($word_count === 0 || $sentence_count === 0) {
            return null;
        }
        
        // Calculate average sentence length
        $avg_sentence_length = $word_count / $sentence_count;
        
        // Calculate average syllables per word
        $avg_syllables_per_word = $syllable_count / $word_count;
        
        // Apply Flesch-Kincaid formula
        $score = 206.835 - (1.015 * $avg_sentence_length) - (84.6 * $avg_syllables_per_word);
        
        // Clamp score between 0 and 100
        $score = max(0, min(100, $score));
        
        return round($score, 1);
    }
    
    /**
     * Count words in text
     *
     * @param string $text Text to analyze.
     * @return int Word count.
     */
    private static function count_words(string $text): int {
        // Remove extra whitespace
        $text = preg_replace('/\s+/', ' ', $text);
        
        // Split by whitespace and filter empty strings
        $words = array_filter(explode(' ', $text), function($word) {
            // Consider a word if it contains at least one letter
            return preg_match('/[a-zA-Z]/', $word);
        });
        
        return count($words);
    }
    
    /**
     * Count sentences in text
     *
     * @param string $text Text to analyze.
     * @return int Sentence count.
     */
    private static function count_sentences(string $text): int {
        // Replace common abbreviations to avoid false sentence endings
        $abbreviations = [
            'Mr.', 'Mrs.', 'Ms.', 'Dr.', 'Prof.', 'Sr.', 'Jr.',
            'Co.', 'Corp.', 'Inc.', 'Ltd.', 'LLC.',
            'Jan.', 'Feb.', 'Mar.', 'Apr.', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Sept.', 'Oct.', 'Nov.', 'Dec.',
            'Mon.', 'Tue.', 'Wed.', 'Thu.', 'Fri.', 'Sat.', 'Sun.',
            'etc.', 'vs.', 'e.g.', 'i.e.', 'cf.', 'al.',
        ];
        
        foreach ($abbreviations as $abbr) {
            $text = str_ireplace($abbr, str_replace('.', '', $abbr), $text);
        }
        
        // Split by sentence terminators
        $sentences = preg_split('/[.!?]+/', $text, -1, PREG_SPLIT_NO_EMPTY);
        
        // Filter out very short fragments
        $sentences = array_filter($sentences, function($sentence) {
            return strlen(trim($sentence)) > 2;
        });
        
        return count($sentences);
    }
    
    /**
     * Count syllables in text
     *
     * Uses a heuristic approach for English text.
     *
     * @param string $text Text to analyze.
     * @return int Syllable count.
     */
    private static function count_syllables(string $text): int {
        // Convert to lowercase for consistent matching
        $text = strtolower($text);
        
        // Extract words
        preg_match_all('/\b[a-z]+\b/', $text, $words);
        $words = $words[0];
        
        $total_syllables = 0;
        
        foreach ($words as $word) {
            $syllables = self::count_syllables_in_word($word);
            $total_syllables += $syllables;
        }
        
        return $total_syllables;
    }
    
    /**
     * Count syllables in a single word
     *
     * @param string $word Word to analyze.
     * @return int Syllable count.
     */
    private static function count_syllables_in_word(string $word): int {
        $word = strtolower(trim($word));
        
        // Handle very short words
        if (strlen($word) <= 2) {
            return 1;
        }
        
        // Count vowel groups as syllables
        $vowel_count = 0;
        $previous_was_vowel = false;
        $word_array = str_split($word);
        
        foreach ($word_array as $char) {
            $is_vowel = in_array($char, ['a', 'e', 'i', 'o', 'u', 'y'], true);
            
            if ($is_vowel && !$previous_was_vowel) {
                $vowel_count++;
            }
            
            $previous_was_vowel = $is_vowel;
        }
        
        // Adjust for silent 'e'
        if (substr($word, -1) === 'e' && $vowel_count > 1) {
            $vowel_count--;
        }
        
        // Adjust for specific patterns
        // -le endings (like "apple", "table")
        if (preg_match('/[^aeiou]le$/', $word) && $vowel_count > 1) {
            $vowel_count++;
        }
        
        // -ed endings (not adding syllable unless preceded by 't' or 'd')
        if (preg_match('/[^td]ed$/', $word) && $vowel_count > 1) {
            $vowel_count--;
        }
        
        // Ensure at least one syllable
        return max(1, $vowel_count);
    }
    
    /**
     * Get readability level description
     *
     * @param float $score Flesch-Kincaid score.
     * @return string Readability level description.
     */
    public static function get_readability_level(float $score): string {
        if ($score >= 90) {
            return __('Very Easy', 'prorank-seo');
        } elseif ($score >= 80) {
            return __('Easy', 'prorank-seo');
        } elseif ($score >= 70) {
            return __('Fairly Easy', 'prorank-seo');
        } elseif ($score >= 60) {
            return __('Standard', 'prorank-seo');
        } elseif ($score >= 50) {
            return __('Fairly Difficult', 'prorank-seo');
        } elseif ($score >= 30) {
            return __('Difficult', 'prorank-seo');
        } else {
            return __('Very Difficult', 'prorank-seo');
        }
    }
    
    /**
     * Get estimated reading grade level
     *
     * @param float $score Flesch-Kincaid score.
     * @return string Estimated grade level.
     */
    public static function get_grade_level(float $score): string {
        if ($score >= 90) {
            return __('5th grade', 'prorank-seo');
        } elseif ($score >= 80) {
            return __('6th grade', 'prorank-seo');
        } elseif ($score >= 70) {
            return __('7th grade', 'prorank-seo');
        } elseif ($score >= 60) {
            return __('8th & 9th grade', 'prorank-seo');
        } elseif ($score >= 50) {
            return __('10th to 12th grade', 'prorank-seo');
        } elseif ($score >= 30) {
            return __('College', 'prorank-seo');
        } else {
            return __('College graduate', 'prorank-seo');
        }
    }
}