<?php

/**
 * Above-the-fold Content Detector
 *
 * Detects elements that appear above the fold based on viewport dimensions
 *
 * @package ProRank\SEO\Modules\Performance
 * @since   1.0.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Modules\Performance;

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


/**
 * AboveFoldDetector class
 */


class AboveFoldDetector {
    
    /**
     * Default viewport dimensions
     *
     * @var array
     */
    private const DEFAULT_VIEWPORT = [
        'width' => 1366,
        'height' => 768,
    ];
    
    /**
     * Elements typically above the fold
     *
     * @var array
     */
    private const PRIORITY_ELEMENTS = [
        'header',
        'nav',
        'hero',
        'banner',
        'masthead',
        'site-header',
        'main-navigation',
        'primary-navigation',
        'top-bar',
        'announcement-bar',
    ];
    
    /**
     * Detect above-the-fold elements
     *
     * @param string $html     HTML content
     * @param array  $viewport Viewport dimensions
     * @return array Above-fold elements
     */
    public function detect(string $html, array $viewport = []): array {
        $viewport = array_merge(self::DEFAULT_VIEWPORT, $viewport);

        try {
            $dom = new \DOMDocument();
            @$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

            $elements = [];

            // Strategy 1: Analyze DOM structure and common patterns
            $elements = array_merge($elements, $this->detect_by_structure($dom));

            // Strategy 2: Analyze element positions and hierarchy
            $elements = array_merge($elements, $this->detect_by_hierarchy($dom, $viewport));

            // Strategy 3: Analyze priority selectors
            $elements = array_merge($elements, $this->detect_priority_elements($dom));

            // Strategy 4: Analyze inline styles for positioning
            $elements = array_merge($elements, $this->detect_by_inline_styles($dom, $viewport));

            // Remove duplicates and return
            return $this->deduplicate_elements($elements);
        } catch (\Throwable $e) {
            prorank_log('ProRank SEO: Failed to detect above-fold elements - ' . $e->getMessage());
            return [];
        }
    }
    
    /**
     * Detect elements by DOM structure
     *
     * @param \DOMDocument $dom DOM document
     * @return array
     */
    private function detect_by_structure(\DOMDocument $dom): array {
        $elements = [];
        $xpath = new \DOMXPath($dom);
        
        // Get elements that are typically at the top
        $top_level_tags = ['header', 'nav', 'section', 'div', 'main', 'article'];
        
        foreach ($top_level_tags as $tag) {
            $nodes = $xpath->query("//{$tag}[position() <= 3]");
            
            foreach ($nodes as $node) {
                $element = $this->extract_element_info($node);
                
                // Check if element has indicators of being above fold
                if ($this->has_above_fold_indicators($element)) {
                    $elements[] = $element;
                }
            }
        }
        
        return $elements;
    }
    
    /**
     * Detect elements by hierarchy
     *
     * @param \DOMDocument $dom      DOM document
     * @param array        $viewport Viewport dimensions
     * @return array
     */
    private function detect_by_hierarchy(\DOMDocument $dom, array $viewport): array {
        $elements = [];
        $body = $dom->getElementsByTagName('body')->item(0);
        
        if (!$body) {
            return $elements;
        }
        
        // Traverse first few levels of DOM
        $this->traverse_dom_levels($body, $elements, 0, 3);
        
        return $elements;
    }
    
    /**
     * Detect priority elements
     *
     * @param \DOMDocument $dom DOM document
     * @return array
     */
    private function detect_priority_elements(\DOMDocument $dom): array {
        $elements = [];
        $xpath = new \DOMXPath($dom);
        
        foreach (self::PRIORITY_ELEMENTS as $identifier) {
            // Check by ID
            $nodes = $xpath->query("//*[@id='{$identifier}']");
            foreach ($nodes as $node) {
                $elements[] = $this->extract_element_info($node);
            }
            
            // Check by class
            $nodes = $xpath->query("//*[contains(@class, '{$identifier}')]");
            foreach ($nodes as $node) {
                $elements[] = $this->extract_element_info($node);
            }
            
            // Check by tag name
            if (preg_match('/^[a-z]+$/', $identifier)) {
                $nodes = $dom->getElementsByTagName($identifier);
                foreach ($nodes as $node) {
                    $elements[] = $this->extract_element_info($node);
                }
            }
        }
        
        // Also get elements with specific attributes
        $nodes = $xpath->query("//*[@data-above-fold='true' or @data-critical='true']");
        foreach ($nodes as $node) {
            $elements[] = $this->extract_element_info($node);
        }
        
        return $elements;
    }
    
    /**
     * Detect elements by inline styles
     *
     * @param \DOMDocument $dom      DOM document
     * @param array        $viewport Viewport dimensions
     * @return array
     */
    private function detect_by_inline_styles(\DOMDocument $dom, array $viewport): array {
        $elements = [];
        $xpath = new \DOMXPath($dom);
        
        // Find elements with positioning styles
        $nodes = $xpath->query("//*[@style]");
        
        foreach ($nodes as $node) {
            $style = $node->getAttribute('style');
            
            // Check for fixed/absolute positioning at top
            if (preg_match('/position:\s*(fixed|absolute)/i', $style)) {
                if (preg_match('/top:\s*(\d+)(px|%|em|rem)?/i', $style, $matches)) {
                    $top_value = intval($matches[1]);
                    $unit = $matches[2] ?? 'px';
                    
                    // Convert to pixels for comparison
                    if ($unit === '%' && $top_value < 50) {
                        $elements[] = $this->extract_element_info($node);
                    } elseif ($unit === 'px' && $top_value < $viewport['height'] / 2) {
                        $elements[] = $this->extract_element_info($node);
                    }
                }
            }
            
            // Check for elements with high z-index (likely overlays/modals)
            if (preg_match('/z-index:\s*(\d+)/i', $style, $matches)) {
                $z_index = intval($matches[1]);
                if ($z_index > 100) {
                    $elements[] = $this->extract_element_info($node);
                }
            }
        }
        
        return $elements;
    }
    
    /**
     * Extract element information
     *
     * @param \DOMElement $node DOM element
     * @return array
     */
    private function extract_element_info(\DOMElement $node): array {
        $info = [
            'tag' => $node->tagName,
            'id' => $node->getAttribute('id') ?: null,
            'classes' => [],
            'attributes' => [],
            'depth' => $this->get_element_depth($node),
            'text_length' => strlen(trim($node->textContent)),
            'has_images' => $node->getElementsByTagName('img')->length > 0,
            'has_links' => $node->getElementsByTagName('a')->length > 0,
        ];
        
        // Extract classes
        $class_attr = $node->getAttribute('class');
        if ($class_attr) {
            $info['classes'] = array_filter(explode(' ', $class_attr));
        }
        
        // Extract relevant attributes
        $relevant_attrs = ['role', 'aria-label', 'data-section', 'data-component'];
        foreach ($relevant_attrs as $attr) {
            if ($node->hasAttribute($attr)) {
                $info['attributes'][$attr] = $node->getAttribute($attr);
            }
        }
        
        return $info;
    }
    
    /**
     * Get element depth in DOM tree
     *
     * @param \DOMElement $node DOM element
     * @return int
     */
    private function get_element_depth(\DOMElement $node): int {
        $depth = 0;
        $current = $node;
        
        while ($current->parentNode && $current->parentNode->nodeName !== '#document') {
            $depth++;
            $current = $current->parentNode;
        }
        
        return $depth;
    }
    
    /**
     * Check if element has above-fold indicators
     *
     * @param array $element Element info
     * @return bool
     */
    private function has_above_fold_indicators(array $element): bool {
        // Check tag name
        if (in_array($element['tag'], ['header', 'nav'], true)) {
            return true;
        }
        
        // Check ID
        if ($element['id']) {
            foreach (self::PRIORITY_ELEMENTS as $priority) {
                if (stripos($element['id'], $priority) !== false) {
                    return true;
                }
            }
        }
        
        // Check classes
        foreach ($element['classes'] as $class) {
            foreach (self::PRIORITY_ELEMENTS as $priority) {
                if (stripos($class, $priority) !== false) {
                    return true;
                }
            }
            
            // Check for common above-fold class patterns
            if (preg_match('/^(hero|banner|masthead|jumbotron|splash)/i', $class)) {
                return true;
            }
        }
        
        // Check attributes
        if (isset($element['attributes']['role'])) {
            if (in_array($element['attributes']['role'], ['banner', 'navigation'], true)) {
                return true;
            }
        }
        
        // Check depth and content
        if ($element['depth'] < 3 && $element['text_length'] > 50) {
            return true;
        }
        
        return false;
    }
    
    /**
     * Traverse DOM levels
     *
     * @param \DOMNode $node     Current node
     * @param array    $elements Elements array
     * @param int      $level    Current level
     * @param int      $max_level Maximum level
     * @return void
     */
    private function traverse_dom_levels(\DOMNode $node, array &$elements, int $level, int $max_level): void {
        if ($level > $max_level || !$node instanceof \DOMElement) {
            return;
        }
        
        // Add current element if it's significant
        if ($this->is_significant_element($node)) {
            $elements[] = $this->extract_element_info($node);
        }
        
        // Traverse children
        foreach ($node->childNodes as $child) {
            if ($child->nodeType === XML_ELEMENT_NODE) {
                $this->traverse_dom_levels($child, $elements, $level + 1, $max_level);
            }
        }
    }
    
    /**
     * Check if element is significant
     *
     * @param \DOMElement $node DOM element
     * @return bool
     */
    private function is_significant_element(\DOMElement $node): bool {
        // Skip script and style tags
        if (in_array($node->tagName, ['script', 'style', 'noscript'], true)) {
            return false;
        }
        
        // Must have ID, class, or meaningful content
        if ($node->hasAttribute('id') || $node->hasAttribute('class')) {
            return true;
        }
        
        // Check for meaningful text content
        $text = trim($node->textContent);
        if (strlen($text) > 20) {
            return true;
        }
        
        // Check for images or other media
        if ($node->getElementsByTagName('img')->length > 0 ||
            $node->getElementsByTagName('video')->length > 0) {
            return true;
        }
        
        return false;
    }
    
    /**
     * Deduplicate elements
     *
     * @param array $elements Elements array
     * @return array
     */
    private function deduplicate_elements(array $elements): array {
        $unique = [];
        $seen = [];
        
        foreach ($elements as $element) {
            // Create unique key
            $key = $element['tag'];
            
            if ($element['id']) {
                $key .= '#' . $element['id'];
            }
            
            if (!empty($element['classes'])) {
                $key .= '.' . implode('.', array_slice($element['classes'], 0, 2));
            }
            
            if (!isset($seen[$key])) {
                $seen[$key] = true;
                $unique[] = $element;
            }
        }
        
        return $unique;
    }
    
    /**
     * Estimate if element is visible in viewport
     *
     * @param array $element  Element info
     * @param array $viewport Viewport dimensions
     * @return bool
     */
    public function is_element_in_viewport(array $element, array $viewport): bool {
        // Simple heuristic based on element properties
        
        // Headers and navigation are always considered visible
        if (in_array($element['tag'], ['header', 'nav'], true)) {
            return true;
        }
        
        // Elements with low depth are likely visible
        if ($element['depth'] < 4) {
            return true;
        }
        
        // Check for above-fold indicators
        return $this->has_above_fold_indicators($element);
    }
}