<?php

/**
 * CSS Parser for Critical CSS Generation
 *
 * Parses CSS content and extracts rules, selectors, and other components
 *
 * @package ProRank\SEO\Modules\Performance
 * @since   1.0.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Modules\Performance;

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


/**
 * CssParser class
 */


class CssParser {
    
    /**
     * Parse CSS content
     *
     * @param string $css CSS content
     * @return array Parsed CSS data
     */
    public function parse(string $css): array {
        $parsed = [
            'rules' => [],
            'selectors' => [],
            'media_queries' => [],
            'animations' => [],
            'fonts' => [],
        ];
        
        // Remove comments
        $css = $this->remove_comments($css);
        
        // Parse @font-face rules
        $css = $this->parse_font_faces($css, $parsed);
        
        // Parse @keyframes
        $css = $this->parse_keyframes($css, $parsed);
        
        // Parse @media rules
        $css = $this->parse_media_queries($css, $parsed);
        
        // Parse regular rules
        $this->parse_regular_rules($css, $parsed);
        
        return $parsed;
    }
    
    /**
     * Remove CSS comments
     *
     * @param string $css CSS content
     * @return string
     */
    private function remove_comments(string $css): string {
        return preg_replace('/\/\*.*?\*\//s', '', $css);
    }
    
    /**
     * Parse @font-face rules
     *
     * @param string $css    CSS content
     * @param array  $parsed Parsed data array
     * @return string CSS with font-face rules removed
     */
    private function parse_font_faces(string $css, array &$parsed): string {
        $pattern = '/@font-face\s*\{([^}]+)\}/i';
        
        if (preg_match_all($pattern, $css, $matches, PREG_SET_ORDER)) {
            foreach ($matches as $match) {
                $declarations = $this->parse_declarations($match[1]);
                
                $parsed['fonts'][] = [
                    'type' => 'font-face',
                    'declarations' => $declarations,
                    'raw' => $match[0],
                ];
            }
        }
        
        return preg_replace($pattern, '', $css);
    }
    
    /**
     * Parse @keyframes rules
     *
     * @param string $css    CSS content
     * @param array  $parsed Parsed data array
     * @return string CSS with keyframes removed
     */
    private function parse_keyframes(string $css, array &$parsed): string {
        $pattern = '/@(?:-webkit-|-moz-|-o-)?keyframes\s+([^\s{]+)\s*\{((?:[^{}]|\{[^}]*\})*)\}/i';
        
        if (preg_match_all($pattern, $css, $matches, PREG_SET_ORDER)) {
            foreach ($matches as $match) {
                $name = $match[1];
                $keyframes_content = $match[2];
                
                $keyframes = [];
                if (preg_match_all('/([^{]+)\{([^}]+)\}/', $keyframes_content, $keyframe_matches, PREG_SET_ORDER)) {
                    foreach ($keyframe_matches as $keyframe_match) {
                        $keyframes[] = [
                            'selector' => trim($keyframe_match[1]),
                            'declarations' => $this->parse_declarations($keyframe_match[2]),
                        ];
                    }
                }
                
                $parsed['animations'][] = [
                    'type' => 'keyframes',
                    'name' => $name,
                    'keyframes' => $keyframes,
                    'usage_count' => 0, // Will be updated later
                    'raw' => $match[0],
                ];
            }
        }
        
        return preg_replace($pattern, '', $css);
    }
    
    /**
     * Parse @media rules
     *
     * @param string $css    CSS content
     * @param array  $parsed Parsed data array
     * @return string CSS with media queries removed
     */
    private function parse_media_queries(string $css, array &$parsed): string {
        $pattern = '/@media\s+([^{]+)\{((?:[^{}]|\{[^}]*\})*)\}/i';
        
        if (preg_match_all($pattern, $css, $matches, PREG_SET_ORDER)) {
            foreach ($matches as $match) {
                $media = trim($match[1]);
                $media_content = $match[2];
                
                $rules = [];
                $this->parse_regular_rules($media_content, ['rules' => &$rules]);
                
                $parsed['media_queries'][] = [
                    'type' => 'media',
                    'media' => $media,
                    'rules' => $rules,
                    'raw' => $match[0],
                ];
            }
        }
        
        return preg_replace($pattern, '', $css);
    }
    
    /**
     * Parse regular CSS rules
     *
     * @param string $css    CSS content
     * @param array  $parsed Parsed data array
     * @return void
     */
    private function parse_regular_rules(string $css, array &$parsed): void {
        $pattern = '/([^{]+)\{([^}]+)\}/';
        
        if (preg_match_all($pattern, $css, $matches, PREG_SET_ORDER)) {
            foreach ($matches as $match) {
                $selector = trim($match[1]);
                $declarations_str = $match[2];
                
                // Skip empty selectors
                if (empty($selector)) {
                    continue;
                }
                
                $declarations = $this->parse_declarations($declarations_str);
                
                // Skip rules with no declarations
                if (empty($declarations)) {
                    continue;
                }
                
                $rule = [
                    'selector' => $selector,
                    'declarations' => $declarations,
                    'specificity' => $this->calculate_specificity($selector),
                    'raw' => $match[0],
                ];
                
                $parsed['rules'][] = $rule;
                
                // Add to selectors list
                if (!isset($parsed['selectors'][$selector])) {
                    $parsed['selectors'][$selector] = [
                        'selector' => $selector,
                        'specificity' => $rule['specificity'],
                        'declarations' => [],
                        'usage_count' => 0,
                    ];
                }
                
                $parsed['selectors'][$selector]['declarations'] = array_merge(
                    $parsed['selectors'][$selector]['declarations'],
                    $declarations
                );
            }
        }
        
        // Convert selectors to indexed array
        $parsed['selectors'] = array_values($parsed['selectors']);
    }
    
    /**
     * Parse CSS declarations
     *
     * @param string $declarations_str Declarations string
     * @return array
     */
    private function parse_declarations(string $declarations_str): array {
        $declarations = [];
        
        $parts = explode(';', $declarations_str);
        
        foreach ($parts as $part) {
            $part = trim($part);
            
            if (empty($part)) {
                continue;
            }
            
            $colon_pos = strpos($part, ':');
            if ($colon_pos === false) {
                continue;
            }
            
            $property = trim(substr($part, 0, $colon_pos));
            $value = trim(substr($part, $colon_pos + 1));
            
            if (!empty($property) && !empty($value)) {
                $declarations[] = [
                    'property' => $property,
                    'value' => $value,
                    'important' => strpos($value, '!important') !== false,
                ];
            }
        }
        
        return $declarations;
    }
    
    /**
     * Calculate CSS selector specificity
     *
     * @param string $selector CSS selector
     * @return int Specificity score
     */
    private function calculate_specificity(string $selector): int {
        $specificity = 0;
        
        // Count IDs (worth 100 points each)
        $id_count = preg_match_all('/#[^\s+>~.[:]+/', $selector);
        $specificity += $id_count * 100;
        
        // Count classes, attributes, and pseudo-classes (worth 10 points each)
        $class_count = preg_match_all('/\.[^\s+>~.[:]+/', $selector);
        $attr_count = preg_match_all('/\[[^\]]+\]/', $selector);
        $pseudo_count = preg_match_all('/:[^\s+>~.[:]+/', $selector);
        $specificity += ($class_count + $attr_count + $pseudo_count) * 10;
        
        // Count elements and pseudo-elements (worth 1 point each)
        $element_count = preg_match_all('/(?:^|[\s+>~])([a-z][a-z0-9]*)/i', $selector);
        $pseudo_element_count = preg_match_all('/::?[a-z-]+/i', $selector);
        $specificity += $element_count + $pseudo_element_count;
        
        return $specificity;
    }
    
    /**
     * Extract used animations from CSS
     *
     * @param array $rules CSS rules
     * @return array Animation names
     */
    public function extract_used_animations(array $rules): array {
        $animations = [];
        
        foreach ($rules as $rule) {
            foreach ($rule['declarations'] as $declaration) {
                if (in_array($declaration['property'], ['animation', 'animation-name', '-webkit-animation', '-webkit-animation-name'], true)) {
                    // Extract animation name from value
                    $value = $declaration['value'];
                    
                    // Handle shorthand animation property
                    if ($declaration['property'] === 'animation' || $declaration['property'] === '-webkit-animation') {
                        // Animation name is usually the first value
                        $parts = preg_split('/\s+/', $value);
                        if (!empty($parts[0]) && !is_numeric($parts[0][0])) {
                            $animations[] = $parts[0];
                        }
                    } else {
                        // Direct animation-name property
                        $animations[] = trim($value);
                    }
                }
            }
        }
        
        return array_unique($animations);
    }
    
    /**
     * Extract font families from CSS
     *
     * @param array $rules CSS rules
     * @return array Font families
     */
    public function extract_font_families(array $rules): array {
        $fonts = [];
        
        foreach ($rules as $rule) {
            foreach ($rule['declarations'] as $declaration) {
                if ($declaration['property'] === 'font-family') {
                    // Parse font family value
                    $value = $declaration['value'];
                    
                    // Remove quotes and split by comma
                    $families = array_map(function($font) {
                        return trim(str_replace(['"', "'"], '', $font));
                    }, explode(',', $value));
                    
                    $fonts = array_merge($fonts, $families);
                }
            }
        }
        
        return array_unique($fonts);
    }
    
    /**
     * Minify CSS
     *
     * @param string $css CSS content
     * @return string Minified CSS
     */
    public function minify(string $css): string {
        // Remove comments
        $css = $this->remove_comments($css);
        
        // Remove unnecessary whitespace
        $css = preg_replace('/\s+/', ' ', $css);
        
        // Remove whitespace around special characters
        $css = preg_replace('/\s*([{}:;,>+~])\s*/', '$1', $css);
        
        // Remove trailing semicolon before closing brace
        $css = str_replace(';}', '}', $css);
        
        // Remove empty rules
        $css = preg_replace('/[^{}]+\{\s*\}/', '', $css);
        
        // Remove leading/trailing whitespace
        $css = trim($css);
        
        return $css;
    }
}