<?php

/**
 * Phrase Extractor
 *
 * Extracts meaningful phrases from content for linking opportunities
 *
 * @package ProRank\SEO\Modules\Content\InternalLinking\Parser
 * @since   1.0.0
 */

declare(strict_types=1);

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

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


/**
 * PhraseExtractor class
 */


class PhraseExtractor {
    
    /**
     * Content parser instance
     *
     * @var ContentParser
     */
    private ContentParser $parser;
    
    /**
     * Minimum phrase score
     *
     * @var float
     */
    private const MIN_PHRASE_SCORE = 0.3;
    
    /**
     * Constructor
     */
    public function __construct() {
        $this->parser = new ContentParser();
    }
    
    /**
     * Extract phrases from post content
     *
     * @param int    $post_id Post ID
     * @param string $content Post content
     * @return array Array of extracted phrases
     */
    public function extract_from_post(int $post_id, string $content): array {
        $phrases = [];
        
        // Parse content into sentences
        $sentences = $this->parser->parse_sentences($content);
        
        foreach ($sentences as $sentence) {
            // Skip sentences that already have links
            if ($sentence['has_links']) {
                continue;
            }
            
            // Find potential anchor phrases
            $anchor_phrases = $this->parser->find_anchor_phrases($sentence['text']);
            
            foreach ($anchor_phrases as $phrase) {
                // Calculate phrase score
                $score = $this->calculate_phrase_score($phrase, $sentence);
                
                if ($score < self::MIN_PHRASE_SCORE) {
                    continue;
                }
                
                // Extract context
                $context = $this->parser->extract_context($content, $phrase['text']);
                
                $phrases[] = [
                    'post_id' => $post_id,
                    'phrase' => $phrase['text'],
                    'clean_phrase' => $phrase['clean_text'],
                    'sentence' => $sentence['text'],
                    'sentence_position' => $sentence['position'],
                    'word_count' => $phrase['word_count'],
                    'score' => $score,
                    'context' => $context,
                    'hash' => md5($post_id . ':' . $phrase['text']),
                ];
            }
        }
        
        // Sort by score
        usort($phrases, function($a, $b) {
            return $b['score'] <=> $a['score'];
        });
        
        return $phrases;
    }
    
    /**
     * Extract key phrases using TF-IDF
     *
     * @param string $content     Content to analyze
     * @param array  $corpus      Document corpus for IDF calculation
     * @return array Key phrases with scores
     */
    public function extract_key_phrases(string $content, array $corpus = []): array {
        $phrases = [];
        $sentences = $this->parser->parse_sentences($content);
        
        // Calculate document frequency if corpus provided
        $document_frequencies = [];
        if (!empty($corpus)) {
            $document_frequencies = $this->calculate_document_frequencies($corpus);
        }
        
        // Extract all potential phrases
        $all_phrases = [];
        foreach ($sentences as $sentence) {
            $anchor_phrases = $this->parser->find_anchor_phrases($sentence['text']);
            foreach ($anchor_phrases as $phrase) {
                $all_phrases[] = $phrase['clean_text'];
            }
        }
        
        // Calculate term frequencies
        $term_frequencies = array_count_values($all_phrases);
        $max_frequency = max($term_frequencies);
        
        // Calculate TF-IDF scores
        foreach ($term_frequencies as $phrase => $frequency) {
            // Term frequency (normalized)
            $tf = $frequency / $max_frequency;
            
            // Inverse document frequency
            $idf = 1.0; // Default if no corpus
            if (isset($document_frequencies[$phrase])) {
                $idf = log(count($corpus) / $document_frequencies[$phrase]);
            }
            
            $score = $tf * $idf;
            
            if ($score > 0.1) { // Threshold
                $phrases[] = [
                    'phrase' => $phrase,
                    'frequency' => $frequency,
                    'tf_idf_score' => $score,
                ];
            }
        }
        
        // Sort by score
        usort($phrases, function($a, $b) {
            return $b['tf_idf_score'] <=> $a['tf_idf_score'];
        });
        
        return array_slice($phrases, 0, 20); // Top 20 phrases
    }
    
    /**
     * Find phrases matching patterns
     *
     * @param string $content  Content to search
     * @param array  $patterns Array of patterns to match
     * @return array Matched phrases
     */
    public function find_pattern_matches(string $content, array $patterns): array {
        $matches = [];
        $sentences = $this->parser->parse_sentences($content);
        
        foreach ($patterns as $pattern) {
            $pattern_type = $pattern['type'] ?? 'exact';
            $search_term = $pattern['keyword'] ?? '';
            
            foreach ($sentences as $sentence) {
                $found_phrases = [];
                
                switch ($pattern_type) {
                    case 'exact':
                        $found_phrases = $this->find_exact_matches($sentence['text'], $search_term);
                        break;
                        
                    case 'regex':
                        $found_phrases = $this->find_regex_matches($sentence['text'], $search_term);
                        break;
                        
                    case 'phrase':
                        $found_phrases = $this->find_phrase_matches($sentence['text'], $search_term);
                        break;
                        
                    case 'synonym':
                        $found_phrases = $this->find_synonym_matches($sentence['text'], $search_term);
                        break;
                }
                
                foreach ($found_phrases as $phrase) {
                    $context = $this->parser->extract_context($content, $phrase);
                    $matches[] = [
                        'pattern' => $pattern,
                        'matched_text' => $phrase,
                        'sentence' => $sentence['text'],
                        'context' => $context,
                    ];
                }
            }
        }
        
        return $matches;
    }
    
    /**
     * Calculate phrase score
     *
     * @param array $phrase   Phrase data
     * @param array $sentence Sentence data
     * @return float Score between 0 and 1
     */
    private function calculate_phrase_score(array $phrase, array $sentence): float {
        $score = 0.0;
        
        // Word count score (prefer 2-4 words)
        if ($phrase['word_count'] >= 2 && $phrase['word_count'] <= 4) {
            $score += 0.3;
        } elseif ($phrase['word_count'] === 5) {
            $score += 0.2;
        } else {
            $score += 0.1;
        }
        
        // Position score (prefer beginning of sentence)
        $position_ratio = $phrase['position'] / max($sentence['word_count'], 1);
        if ($position_ratio < 0.3) {
            $score += 0.2;
        } elseif ($position_ratio < 0.7) {
            $score += 0.1;
        }
        
        // Capitalization score (prefer proper nouns)
        if (preg_match('/^[A-Z]/', $phrase['text'])) {
            $score += 0.2;
        }
        
        // Length score (prefer medium length phrases)
        $phrase_length = strlen($phrase['clean_text']);
        if ($phrase_length >= 10 && $phrase_length <= 30) {
            $score += 0.2;
        } elseif ($phrase_length > 30 && $phrase_length <= 50) {
            $score += 0.1;
        }
        
        // Contains numbers or special terms
        if (preg_match('/\d+/', $phrase['text']) || 
            preg_match('/[A-Z]{2,}/', $phrase['text'])) {
            $score += 0.1;
        }
        
        return min($score, 1.0);
    }
    
    /**
     * Calculate document frequencies for corpus
     *
     * @param array $corpus Array of documents
     * @return array Document frequencies
     */
    private function calculate_document_frequencies(array $corpus): array {
        $document_frequencies = [];
        
        foreach ($corpus as $document) {
            $phrases = [];
            $sentences = $this->parser->parse_sentences($document);
            
            foreach ($sentences as $sentence) {
                $anchor_phrases = $this->parser->find_anchor_phrases($sentence['text']);
                foreach ($anchor_phrases as $phrase) {
                    $phrases[$phrase['clean_text']] = true;
                }
            }
            
            foreach (array_keys($phrases) as $phrase) {
                $document_frequencies[$phrase] = ($document_frequencies[$phrase] ?? 0) + 1;
            }
        }
        
        return $document_frequencies;
    }
    
    /**
     * Find exact matches
     *
     * @param string $text    Text to search
     * @param string $keyword Keyword to find
     * @return array Found matches
     */
    private function find_exact_matches(string $text, string $keyword): array {
        $matches = [];
        $pattern = '/\b' . preg_quote($keyword, '/') . '\b/i';
        
        if (preg_match_all($pattern, $text, $found, PREG_OFFSET_CAPTURE)) {
            foreach ($found[0] as $match) {
                $matches[] = $match[0];
            }
        }
        
        return $matches;
    }
    
    /**
     * Find regex matches
     *
     * @param string $text    Text to search
     * @param string $pattern Regex pattern
     * @return array Found matches
     */
    private function find_regex_matches(string $text, string $pattern): array {
        $matches = [];
        
        // Validate regex pattern
        if (@preg_match($pattern, '') === false) {
            return $matches;
        }
        
        if (preg_match_all($pattern, $text, $found)) {
            foreach ($found[0] as $match) {
                $matches[] = $match;
            }
        }
        
        return $matches;
    }
    
    /**
     * Find phrase matches (with word variations)
     *
     * @param string $text   Text to search
     * @param string $phrase Phrase to find
     * @return array Found matches
     */
    private function find_phrase_matches(string $text, string $phrase): array {
        $matches = [];
        $words = preg_split('/\s+/', $phrase);
        
        // Create flexible pattern allowing for word variations
        $pattern_parts = [];
        foreach ($words as $word) {
            $pattern_parts[] = $word . '\w*'; // Allow word endings
        }
        
        $pattern = '/\b' . implode('\s+', $pattern_parts) . '\b/i';
        
        if (preg_match_all($pattern, $text, $found)) {
            foreach ($found[0] as $match) {
                $matches[] = $match;
            }
        }
        
        return $matches;
    }
    
    /**
     * Find synonym matches
     *
     * @param string $text    Text to search
     * @param string $keyword Keyword with synonyms
     * @return array Found matches
     */
    private function find_synonym_matches(string $text, string $keyword): array {
        // This would integrate with a synonym database or AI
        // For now, just return exact matches
        return $this->find_exact_matches($text, $keyword);
    }
}