<?php

/**
 * Enhanced Content Parser
 *
 * Parses content into sentences and extracts linkable phrases with context
 *
 * @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;
}


/**
 * ContentParser class
 */


class ContentParser {
    
    /**
     * Sentence delimiters
     *
     * @var array
     */
    private const SENTENCE_DELIMITERS = ['.', '!', '?'];
    
    /**
     * Skip tags when parsing
     *
     * @var array
     */
    private const SKIP_TAGS = ['a', 'script', 'style', 'code', 'pre'];
    
    /**
     * Minimum sentence length
     *
     * @var int
     */
    private const MIN_SENTENCE_LENGTH = 10;
    
    /**
     * Maximum sentence length
     *
     * @var int
     */
    private const MAX_SENTENCE_LENGTH = 500;
    
    /**
     * Parse content into sentences
     *
     * @param string $content HTML content to parse
     * @return array Array of sentences with metadata
     */
    public function parse_sentences(string $content): array {
        // Strip problematic tags first
        $content = $this->strip_skip_tags($content);
        
        // Convert to plain text while preserving structure
        $text = $this->html_to_text($content);
        
        // Split into sentences
        $sentences = $this->split_into_sentences($text);
        
        // Process each sentence
        $processed_sentences = [];
        foreach ($sentences as $index => $sentence) {
            $sentence = trim($sentence);
            
            // Skip if too short or too long
            if (strlen($sentence) < self::MIN_SENTENCE_LENGTH || 
                strlen($sentence) > self::MAX_SENTENCE_LENGTH) {
                continue;
            }
            
            $processed_sentences[] = [
                'text' => $sentence,
                'position' => $index,
                'word_count' => str_word_count($sentence),
                'has_links' => $this->contains_links($sentence),
                'hash' => md5($sentence),
            ];
        }
        
        return $processed_sentences;
    }
    
    /**
     * Extract paragraphs from content
     *
     * @param string $content HTML content
     * @return array Array of paragraphs
     */
    public function extract_paragraphs(string $content): array {
        $paragraphs = [];
        
        // Use DOMDocument for proper HTML parsing
        $dom = new \DOMDocument();
        @$dom->loadHTML(mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        
        // Find all paragraph tags
        $p_tags = $dom->getElementsByTagName('p');
        
        foreach ($p_tags as $index => $p) {
            $text = $p->textContent;
            if (empty(trim($text))) {
                continue;
            }
            
            $paragraphs[] = [
                'text' => trim($text),
                'html' => $dom->saveHTML($p),
                'position' => $index,
                'sentences' => $this->parse_sentences($text),
            ];
        }
        
        // Also extract text from other block elements
        $block_tags = ['div', 'section', 'article'];
        foreach ($block_tags as $tag) {
            $elements = $dom->getElementsByTagName($tag);
            foreach ($elements as $element) {
                // Skip if contains other block elements
                if ($this->contains_block_elements($element)) {
                    continue;
                }
                
                $text = $element->textContent;
                if (strlen(trim($text)) > 50) { // Minimum content threshold
                    $paragraphs[] = [
                        'text' => trim($text),
                        'html' => $dom->saveHTML($element),
                        'position' => count($paragraphs),
                        'sentences' => $this->parse_sentences($text),
                    ];
                }
            }
        }
        
        return $paragraphs;
    }
    
    /**
     * Extract context around a phrase
     *
     * @param string $content    Full content
     * @param string $phrase     Phrase to find context for
     * @param int    $words      Number of context words
     * @return array Context data
     */
    public function extract_context(string $content, string $phrase, int $words = 10): array {
        $text = wp_strip_all_tags($content);
        $pos = stripos($text, $phrase);
        
        if ($pos === false) {
            return [
                'before' => '',
                'phrase' => $phrase,
                'after' => '',
                'sentence' => '',
            ];
        }
        
        // Extract words before
        $before_text = substr($text, 0, $pos);
        $before_words = preg_split('/\s+/', $before_text);
        $before_context = implode(' ', array_slice($before_words, -$words));
        
        // Extract words after
        $after_start = $pos + strlen($phrase);
        $after_text = substr($text, $after_start);
        $after_words = preg_split('/\s+/', $after_text);
        $after_context = implode(' ', array_slice($after_words, 0, $words));
        
        // Find containing sentence
        $sentence = $this->find_containing_sentence($text, $pos, strlen($phrase));
        
        return [
            'before' => trim($before_context),
            'phrase' => $phrase,
            'after' => trim($after_context),
            'sentence' => $sentence,
            'position' => $pos,
        ];
    }
    
    /**
     * Find potential anchor phrases in text
     *
     * @param string $text         Text to analyze
     * @param int    $min_length   Minimum phrase length
     * @param int    $max_length   Maximum phrase length
     * @return array Array of potential phrases
     */
    public function find_anchor_phrases(string $text, int $min_length = 2, int $max_length = 5): array {
        $phrases = [];
        $words = preg_split('/\s+/', $text);
        $word_count = count($words);
        
        // Generate n-grams
        for ($length = $min_length; $length <= $max_length; $length++) {
            for ($i = 0; $i <= $word_count - $length; $i++) {
                $phrase = implode(' ', array_slice($words, $i, $length));
                
                // Skip if contains stop words at beginning/end
                if ($this->starts_or_ends_with_stop_word($phrase)) {
                    continue;
                }
                
                // Skip if too short when cleaned
                $clean_phrase = preg_replace('/[^\w\s]/u', '', $phrase);
                if (strlen($clean_phrase) < 3) {
                    continue;
                }
                
                $phrases[] = [
                    'text' => $phrase,
                    'clean_text' => $clean_phrase,
                    'word_count' => $length,
                    'position' => $i,
                ];
            }
        }
        
        return $phrases;
    }
    
    /**
     * Strip skip tags from content
     *
     * @param string $content HTML content
     * @return string Cleaned content
     */
    private function strip_skip_tags(string $content): string {
        foreach (self::SKIP_TAGS as $tag) {
            $content = preg_replace('/<' . $tag . '[^>]*>.*?<\/' . $tag . '>/is', '', $content);
        }
        return $content;
    }
    
    /**
     * Convert HTML to text while preserving structure
     *
     * @param string $html HTML content
     * @return string Plain text
     */
    private function html_to_text(string $html): string {
        // Replace block elements with double newlines
        $block_elements = ['p', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'blockquote'];
        foreach ($block_elements as $tag) {
            $html = preg_replace('/<' . $tag . '[^>]*>/i', "\n\n", $html);
            $html = str_replace('</' . $tag . '>', '', $html);
        }
        
        // Replace br tags with newlines
        $html = preg_replace('/<br[^>]*>/i', "\n", $html);
        
        // Strip remaining tags
        $text = wp_strip_all_tags($html);
        
        // Clean up whitespace
        $text = preg_replace('/\n\s*\n/', "\n\n", $text);
        $text = preg_replace('/[ \t]+/', ' ', $text);
        
        return trim($text);
    }
    
    /**
     * Split text into sentences
     *
     * @param string $text Plain text
     * @return array Array of sentences
     */
    private function split_into_sentences(string $text): array {
        // Handle common abbreviations
        $abbreviations = ['Dr.', 'Mr.', 'Mrs.', 'Ms.', 'Prof.', 'Sr.', 'Jr.'];
        foreach ($abbreviations as $abbr) {
            $text = str_replace($abbr, str_replace('.', '<!DOT!>', $abbr), $text);
        }
        
        // Handle decimal numbers
        $text = preg_replace('/(\d)\.(\d)/', '$1<!DOT!>$2', $text);
        
        // Split on sentence delimiters
        $sentences = preg_split('/[.!?]+/', $text);
        
        // Restore dots
        $sentences = array_map(function($sentence) {
            return str_replace('<!DOT!>', '.', $sentence);
        }, $sentences);
        
        return array_filter($sentences, 'trim');
    }
    
    /**
     * Check if text contains links
     *
     * @param string $text Text to check
     * @return bool
     */
    private function contains_links(string $text): bool {
        return preg_match('/<a[^>]+href[^>]*>/i', $text) === 1;
    }
    
    /**
     * Check if element contains block elements
     *
     * @param \DOMElement $element DOM element
     * @return bool
     */
    private function contains_block_elements(\DOMElement $element): bool {
        $block_tags = ['p', 'div', 'section', 'article', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
        
        foreach ($block_tags as $tag) {
            if ($element->getElementsByTagName($tag)->length > 0) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Find sentence containing a position
     *
     * @param string $text     Full text
     * @param int    $position Position in text
     * @param int    $length   Length of phrase
     * @return string Containing sentence
     */
    private function find_containing_sentence(string $text, int $position, int $length): string {
        // Find sentence start
        $start = $position;
        while ($start > 0) {
            if (in_array($text[$start - 1], self::SENTENCE_DELIMITERS)) {
                break;
            }
            $start--;
        }
        
        // Find sentence end
        $end = $position + $length;
        $text_length = strlen($text);
        while ($end < $text_length) {
            if (in_array($text[$end], self::SENTENCE_DELIMITERS)) {
                $end++;
                break;
            }
            $end++;
        }
        
        return trim(substr($text, $start, $end - $start));
    }
    
    /**
     * Check if phrase starts or ends with stop word
     *
     * @param string $phrase Phrase to check
     * @return bool
     */
    private function starts_or_ends_with_stop_word(string $phrase): bool {
        $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', 'or', 'but',
        ];
        
        $words = preg_split('/\s+/', strtolower($phrase));
        if (empty($words)) {
            return true;
        }
        
        return in_array($words[0], $stop_words) || 
               in_array($words[count($words) - 1], $stop_words);
    }
}