<?php

/**
 * Base Stemmer Class
 *
 * Abstract base class for language-specific word stemmers
 *
 * @package ProRank\SEO\Modules\Content\InternalLinking\Parser\MultiLanguage
 * @since   1.0.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Modules\Content\InternalLinking\Parser\MultiLanguage;

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


/**
 * Stemmer abstract class
 */


abstract class Stemmer {
    
    /**
     * Language code
     *
     * @var string
     */
    protected string $language;
    
    /**
     * Stop words for this language
     *
     * @var array
     */
    protected array $stop_words = [];
    
    /**
     * Cached stems
     *
     * @var array
     */
    protected array $cache = [];
    
    /**
     * Constructor
     *
     * @param string $language Language code
     */
    public function __construct(string $language) {
        $this->language = $language;
        $this->load_stop_words();
    }
    
    /**
     * Stem a word
     *
     * @param string $word Word to stem
     * @return string Stemmed word
     */
    abstract public function stem(string $word): string;
    
    /**
     * Stem multiple words
     *
     * @param array $words Array of words
     * @return array Array of stemmed words
     */
    public function stem_words(array $words): array {
        $stemmed = [];
        
        foreach ($words as $word) {
            // Check cache first
            if (isset($this->cache[$word])) {
                $stemmed[] = $this->cache[$word];
                continue;
            }
            
            // Skip stop words
            if ($this->is_stop_word($word)) {
                $stemmed[] = $word;
                continue;
            }
            
            // Stem the word
            $stem = $this->stem($word);
            $this->cache[$word] = $stem;
            $stemmed[] = $stem;
        }
        
        return $stemmed;
    }
    
    /**
     * Stem a phrase
     *
     * @param string $phrase Phrase to stem
     * @return string Stemmed phrase
     */
    public function stem_phrase(string $phrase): string {
        $words = preg_split('/\s+/', $phrase);
        $stemmed_words = $this->stem_words($words);
        return implode(' ', $stemmed_words);
    }
    
    /**
     * Check if word is a stop word
     *
     * @param string $word Word to check
     * @return bool
     */
    public function is_stop_word(string $word): bool {
        return in_array(mb_strtolower($word), $this->stop_words, true);
    }
    
    /**
     * Get stop words
     *
     * @return array
     */
    public function get_stop_words(): array {
        return $this->stop_words;
    }
    
    /**
     * Load stop words for the language
     *
     * @return void
     */
    protected function load_stop_words(): void {
        $file = dirname(__DIR__) . '/StopWords/' . $this->language . '_stop_words.txt';
        
        if (file_exists($file)) {
            $words = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
            $this->stop_words = array_map('mb_strtolower', $words);
        } else {
            // Default English stop words if file not found
            $this->stop_words = [
                'a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'for',
                'from', 'has', 'he', 'in', 'is', 'it', 'its', 'of', 'on',
                'that', 'the', 'to', 'was', 'will', 'with', 'the', 'this',
                'but', 'they', 'have', 'had', 'what', 'when', 'where', 'who',
                'which', 'why', 'how', 'all', 'both', 'each', 'more', 'most',
                'other', 'some', 'such', 'only', 'own', 'same', 'so', 'than',
                'too', 'very', 'can', 'could', 'may', 'might', 'must', 'shall',
                'should', 'will', 'would', 'am', 'is', 'are', 'was', 'were',
                'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does',
                'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or',
            ];
        }
    }
    
    /**
     * Normalize word before stemming
     *
     * @param string $word Word to normalize
     * @return string Normalized word
     */
    protected function normalize(string $word): string {
        // Convert to lowercase
        $word = mb_strtolower($word);
        
        // Remove apostrophes
        $word = str_replace("'", '', $word);
        
        // Remove non-alphanumeric characters from ends
        $word = preg_replace('/^[^a-z0-9]+|[^a-z0-9]+$/i', '', $word);
        
        return $word;
    }
    
    /**
     * Check if word contains vowel
     *
     * @param string $word Word to check
     * @return bool
     */
    protected function contains_vowel(string $word): bool {
        return preg_match('/[aeiou]/i', $word) === 1;
    }
    
    /**
     * Count syllables in word
     *
     * @param string $word Word to analyze
     * @return int Number of syllables
     */
    protected function count_syllables(string $word): int {
        $word = mb_strtolower($word);
        $count = 0;
        $previous_was_vowel = false;
        
        $vowels = ['a', 'e', 'i', 'o', 'u', 'y'];
        $chars = mb_str_split($word);
        
        foreach ($chars as $char) {
            $is_vowel = in_array($char, $vowels);
            if ($is_vowel && !$previous_was_vowel) {
                $count++;
            }
            $previous_was_vowel = $is_vowel;
        }
        
        // Adjust for silent e
        if (mb_substr($word, -1) === 'e') {
            $count--;
        }
        
        // Ensure at least one syllable
        return max(1, $count);
    }
    
    /**
     * Get measure of word (consonant-vowel pattern)
     *
     * @param string $word Word to measure
     * @return int Measure value
     */
    protected function get_measure(string $word): int {
        $word = mb_strtolower($word);
        $measure = 0;
        $vowels = ['a', 'e', 'i', 'o', 'u'];
        
        $chars = mb_str_split($word);
        $in_consonant_seq = false;
        
        foreach ($chars as $i => $char) {
            $is_vowel = in_array($char, $vowels);
            
            // Y is vowel if not at start and preceded by consonant
            if ($char === 'y' && $i > 0 && !in_array($chars[$i - 1], $vowels)) {
                $is_vowel = true;
            }
            
            if (!$is_vowel && !$in_consonant_seq) {
                $in_consonant_seq = true;
            } elseif ($is_vowel && $in_consonant_seq) {
                $measure++;
                $in_consonant_seq = false;
            }
        }
        
        return $measure;
    }
}