<?php
/**
 * Code Renderer Class
 * Safely renders code content with proper escaping and syntax highlighting
 */

// Prevent direct access
if (!defined('WPINC')) {
    die('Direct access not permitted.');
}

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

class MCB_Code_Renderer {
    
    /**
     * Render code content safely
     */
    public function render_code($content, $filename) {
        // Validate content before processing
        $validation = $this->validate_content($content);
        if (is_wp_error($validation)) {
            return '<div class="gcv-error">' . esc_html($validation->get_error_message()) . '</div>';
        }
        
        // First, ensure the content is treated as plain text
        // Multiple layers of safety to prevent any code execution
        
        // 1. Convert to UTF-8 if needed
        if (!mb_check_encoding($content, 'UTF-8')) {
            $content = mb_convert_encoding($content, 'UTF-8', mb_detect_encoding($content));
        }
        
        // 2. Remove any null bytes
        $content = str_replace("\0", '', $content);
        
        // 3. HTML encode everything - this is crucial for safety
        $safe_content = htmlspecialchars($content, ENT_QUOTES | ENT_HTML5 | ENT_SUBSTITUTE, 'UTF-8', false);
        
        // 4. Additional escaping for JavaScript context
        $safe_content = $this->escape_for_javascript($safe_content);
        
        // 5. Get language for syntax highlighting
        $language = $this->detect_language($filename);
        
        // 6. Prepare the code block with line numbers
        $lines = explode("\n", $safe_content);
        $formatted_code = $this->format_with_line_numbers($lines);
        
        // 7. Wrap in proper HTML structure
        $output = '<div class="gcv-code-container" data-language="' . esc_attr($language) . '">';
        $output .= '<div class="gcv-code-header">';
        $output .= '<span class="gcv-filename">' . esc_html(basename($filename)) . '</span>';
        $output .= '<button class="gcv-copy-btn" data-content="' . esc_attr($safe_content) . '">Copy</button>';
        $output .= '</div>';
        $output .= '<div class="gcv-code-wrapper">';
        $output .= '<pre class="line-numbers"><code class="language-' . esc_attr($language) . '">';
        $output .= $formatted_code;
        $output .= '</code></pre>';
        $output .= '</div>';
        $output .= '</div>';
        
        return $output;
    }
    
    /**
     * Format code with line numbers
     */
    private function format_with_line_numbers($lines) {
        $output = '';
        $line_count = count($lines);
        $digit_count = strlen((string)$line_count);
        
        foreach ($lines as $index => $line) {
            $line_num = $index + 1;
            $padded_num = str_pad($line_num, $digit_count, ' ', STR_PAD_LEFT);
            $output .= '<span class="line-number" data-line="' . $line_num . '">' . $padded_num . '</span>';
            $output .= '<span class="line-content">' . $line . '</span>' . "\n";
        }
        
        return rtrim($output);
    }
    
    /**
     * Additional escaping for JavaScript context
     */
    private function escape_for_javascript($content) {
        // Escape any remaining potentially dangerous patterns
        $patterns = array(
            '/<script/i' => '&lt;script',
            '/<\/script/i' => '&lt;/script',
            '/javascript:/i' => 'javascript&colon;',
            '/on\w+\s*=/i' => 'on_event=',
            '/<iframe/i' => '&lt;iframe',
            '/<object/i' => '&lt;object',
            '/<embed/i' => '&lt;embed',
            '/<applet/i' => '&lt;applet'
        );
        
        foreach ($patterns as $pattern => $replacement) {
            $content = preg_replace($pattern, $replacement, $content);
        }
        
        return $content;
    }
    
    /**
     * Detect programming language from filename
     */
    private function detect_language($filename) {
        $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        $name_lower = strtolower($filename);
        
        // Map extensions to Prism.js language identifiers
        $language_map = array(
            'php' => 'php',
            'js' => 'javascript',
            'jsx' => 'jsx',
            'ts' => 'typescript',
            'tsx' => 'tsx',
            'py' => 'python',
            'rb' => 'ruby',
            'java' => 'java',
            'c' => 'c',
            'cpp' => 'cpp',
            'cc' => 'cpp',
            'cxx' => 'cpp',
            'h' => 'c',
            'hpp' => 'cpp',
            'cs' => 'csharp',
            'swift' => 'swift',
            'kt' => 'kotlin',
            'go' => 'go',
            'rs' => 'rust',
            'scala' => 'scala',
            'r' => 'r',
            'sql' => 'sql',
            'sh' => 'bash',
            'bash' => 'bash',
            'yml' => 'yaml',
            'yaml' => 'yaml',
            'json' => 'json',
            'xml' => 'xml',
            'html' => 'html',
            'htm' => 'html',
            'css' => 'css',
            'scss' => 'scss',
            'sass' => 'sass',
            'less' => 'less',
            'md' => 'markdown',
            'markdown' => 'markdown',
            'txt' => 'plain',
            'ini' => 'ini',
            'conf' => 'ini',
            'cfg' => 'ini'
        );
        
        // Special file names
        if ($name_lower === 'dockerfile') {
            return 'docker';
        }
        if ($name_lower === 'makefile' || $name_lower === 'gnumakefile') {
            return 'makefile';
        }
        if ($name_lower === '.gitignore') {
            return 'git';
        }
        if ($name_lower === '.htaccess') {
            return 'apacheconf';
        }
        if ($name_lower === '.env') {
            return 'bash';
        }
        
        return isset($language_map[$extension]) ? $language_map[$extension] : 'plain';
    }
    
    /**
     * Sanitize and validate file content before rendering
     */
    public function validate_content($content) {
        // Check for binary content
        if ($this->is_binary($content)) {
            return new WP_Error('binary_file', 'Binary files cannot be displayed');
        }
        
        // Check file size (limit to 1MB for performance)
        if (strlen($content) > 1048576) {
            return new WP_Error('file_too_large', 'File is too large to display (max 1MB)');
        }
        
        return true;
    }
    
    /**
     * Check if content appears to be binary
     */
    private function is_binary($content) {
        // Check for null bytes or excessive non-printable characters
        $null_count = substr_count($content, "\0");
        if ($null_count > 0) {
            return true;
        }
        
        // Sample first 8192 bytes
        $sample = substr($content, 0, 8192);
        $non_printable = 0;
        
        for ($i = 0; $i < strlen($sample); $i++) {
            $char = ord($sample[$i]);
            // Allow common whitespace and printable ASCII
            if ($char < 32 && $char !== 9 && $char !== 10 && $char !== 13) {
                $non_printable++;
            }
        }
        
        // If more than 30% non-printable, consider it binary
        return ($non_printable / strlen($sample)) > 0.3;
    }
}
