<?php
/**
 * Name: WebP Conversion
 * Description: Automatically convert uploaded JPEG and PNG images to WebP format for better performance and smaller file sizes while keeping original format as backup
 * Category: Media
 * Icon: -
 * UseCases: [
 *   {
 *     "title": "Page Speed Optimization",
 *     "description": "Automatically convert all uploaded JPG/PNG images to WebP format to reduce file sizes by 30-50% and improve Core Web Vitals scores."
 *   },
 *   {
 *     "title": "Mobile Performance",
 *     "description": "Serve lighter WebP images to mobile visitors to reduce data usage and improve load times on slower connections."
 *   },
 *   {
 *     "title": "Hosting Cost Reduction",
 *     "description": "Cut bandwidth usage and storage costs on high-traffic image-heavy sites by using smaller WebP files."
 *   }
 * ]
 * Added: 2025-09-25
 * Tags: webp, images, optimization, performance, conversion
 * WhereToFind: {
 *   "location": "Media Library",
 *   "url": "upload.php",
 *   "instructions": "Upload JPEG/PNG - WebP version created automatically",
 *   "newTab": false
 * }
 */

// Prevent direct access
if (!defined('ABSPATH')) exit;

class SwitchboardWebPConversion {
    
    private $settings;
    
    public function __construct() {
        $this->settings = get_option('switchboard_webp-conversion_settings', []);

        // Check if GD or Imagick supports WebP
        if (!$this->webp_supported()) {
            add_action('admin_notices', [$this, 'show_support_notice']);
            return;
        }

        // Priority 10: Runs after Resize Images on Upload module (priority 5)
        add_filter('wp_handle_upload', [$this, 'convert_to_webp'], 10, 2);
    }

    /**
     * Register data locations for cleanup system
     *
     * @return array Data storage locations
     */
    public function register_data_locations() {
        return [
            'options' => [
                'switchboard_webp-conversion_settings',
                'switchboard_webp-conversion_stats',
            ],
            'option_patterns' => [],
            'tables' => [],
            'post_meta' => [],
            'user_meta' => [],
        ];
    }

    /**
     * Render settings panel for WebP Conversion
     *
     * @param array $settings Current module settings
     * @return void Outputs HTML directly
     */
    public function render_settings($settings = []) {
        $convert_jpeg = isset($settings['convert_jpeg']) ? $settings['convert_jpeg'] : 'yes';
        $convert_png = isset($settings['convert_png']) ? $settings['convert_png'] : 'yes';
        $quality = isset($settings['quality']) ? $settings['quality'] : '80';
        $keep_original = isset($settings['keep_original']) ? $settings['keep_original'] : 'no';
        $exclusion_suffix = isset($settings['exclusion_suffix']) ? $settings['exclusion_suffix'] : '-sbe';
        
        // Check WebP support with detailed diagnostics
        $webp_supported = false;
        $support_method = '';
        $gd_available = function_exists('imagewebp');
        $gd_version = $gd_available && function_exists('gd_info') ? gd_info() : null;
        $imagick_available = extension_loaded('imagick') && class_exists('Imagick');
        $imagick_webp = false;

        if ($gd_available) {
            $webp_supported = true;
            $support_method = 'GD Library';
            if ($gd_version) {
                $support_method .= ' ' . (isset($gd_version['GD Version']) ? $gd_version['GD Version'] : '');
            }
        } elseif ($imagick_available) {
            try {
                $imagick = new Imagick();
                $formats = $imagick->queryFormats('WEBP');
                if (in_array('WEBP', $formats)) {
                    $webp_supported = true;
                    $imagick_webp = true;
                    $imagick_version = Imagick::getVersion();
                    $support_method = 'ImageMagick';
                    if (isset($imagick_version['versionString'])) {
                        $support_method .= ' (' . $imagick_version['versionString'] . ')';
                    }
                }
            } catch (Exception $e) {
                // Imagick installed but misconfigured
            }
        }

        // Check if AVIF Conversion module is also active
        $active_modules = get_option('switchboard_active_modules', []);
        $avif_active = is_array($active_modules) && in_array('avif-conversion', $active_modules);

        $html = '
            <input type="hidden" name="module" value="webp-conversion">

            <div class="sb-sp-info-box ' . ($webp_supported ? 'sb-sp-info-box-info' : 'sb-sp-info-box-tip') . '" style="margin-bottom: 24px;">
                <h4 class="sb-sp-heading-sm">WebP Image Conversion</h4>
                <p class="sb-sp-description" style="margin-bottom: 12px;">Automatically convert uploaded JPEG and PNG images to WebP format for 25-34% smaller file sizes and faster page loads.</p>';

        if ($webp_supported) {
            $html .= '<p class="sb-sp-description" style="margin: 0;"><strong>WebP Support Detected:</strong> ' . esc_html($support_method) . '</p>';
        } else {
            $html .= '<div class="sb-sp-card" style="margin-top: 12px;">
                <div class="sb-sp-card-body">
                    <p style="margin: 0 0 12px 0; font-weight: 600; color: #dc2626;"><strong>WebP Not Supported</strong></p>
                    <p class="sb-sp-description" style="margin-bottom: 12px;">Your server does not have the required libraries to convert images to WebP format.</p>
                    <p style="margin: 0 0 8px 0; font-weight: 600; font-size: 13px;"><strong>Server Status:</strong></p>
                    <ul class="sb-sp-list" style="margin-bottom: 12px;">
                        <li class="sb-sp-list-item">GD Library with WebP: ' . ($gd_available ? '<span style="color: #dc2626;"> Not available</span>' : '<span style="color: #dc2626;"> Not installed</span>') . '</li>
                        <li class="sb-sp-list-item">ImageMagick with WebP: ' . ($imagick_available ? ($imagick_webp ? '<span style="color: #166534;"> Available</span>' : '<span style="color: #dc2626;"> WebP not enabled</span>') : '<span style="color: #dc2626;"> Not installed</span>') . '</li>
                    </ul>
                    <p class="sb-sp-description" style="margin: 0;"><strong>What to do:</strong> Contact your hosting provider and ask them to enable "GD Library with WebP support" or "ImageMagick with WebP support".</p>
                </div>
            </div>';
        }

        $html .= '
            </div>';

        if ($avif_active) {
            $html .= '
            <div class="sb-sp-info-box sb-sp-info-box-tip" style="margin-bottom: 24px;">
                <h4 class="sb-sp-heading-sm">AVIF Conversion is Also Active</h4>
                <p class="sb-sp-description" style="margin-bottom: 8px;">WebP runs first (priority 10) and converts your images before AVIF gets a chance. The AVIF module will have no effect while WebP is active.</p>
                <p class="sb-sp-description" style="margin: 0;"><strong>Action:</strong> Use one or the other — not both. If you prefer AVIF (better compression), disable this module.</p>
            </div>';
        }

        // Show conversion stats if any conversions have happened
        $stats = get_option('switchboard_webp-conversion_stats', []);
        $stats_count = isset($stats['count']) ? intval($stats['count']) : 0;
        $stats_saved = isset($stats['bytes_saved']) ? intval($stats['bytes_saved']) : 0;

        if ($stats_count > 0) {
            $html .= '
            <div class="sb-sp-info-box sb-sp-info-box-info" style="margin-bottom: 24px; display: flex; align-items: center; gap: 24px;">
                <div style="display: flex; align-items: center; gap: 8px;">
                    <span style="font-size: 20px; font-weight: 700; color: var(--gray-900);">' . number_format($stats_count) . '</span>
                    <span class="sb-sp-description" style="margin: 0;">images converted</span>
                </div>
                <div style="width: 1px; height: 20px; background: var(--primary-200);"></div>
                <div style="display: flex; align-items: center; gap: 8px;">
                    <span style="font-size: 20px; font-weight: 700; color: var(--gray-900);">' . esc_html($this->format_bytes($stats_saved)) . '</span>
                    <span class="sb-sp-description" style="margin: 0;">saved</span>
                </div>
            </div>';
        }

        $html .= '
            <div class="sb-sp-group" style="margin-bottom: 24px;">
                <h3 class="sb-sp-heading">Conversion Settings</h3>

                <div class="sb-module-option-item sb-module-toggle-item" style="margin-bottom: 16px;">
                    <div class="sb-module-option-content">
                        <div class="sb-module-toggle-header">
                            <strong class="sb-module-option-title">Convert JPEG Files</strong>
                            <label class="sb-module-toggle">
                                <input type="checkbox" name="setting_convert_jpeg" value="yes"' . ($convert_jpeg === 'yes' ? ' checked' : '') . '>
                                <span class="sb-module-toggle-slider"></span>
                            </label>
                        </div>
                        <p class="sb-module-option-text">Convert JPEG files to WebP format</p>
                    </div>
                </div>

                <div class="sb-module-option-item sb-module-toggle-item" style="margin-bottom: 16px;">
                    <div class="sb-module-option-content">
                        <div class="sb-module-toggle-header">
                            <strong class="sb-module-option-title">Convert PNG Files</strong>
                            <label class="sb-module-toggle">
                                <input type="checkbox" name="setting_convert_png" value="yes"' . ($convert_png === 'yes' ? ' checked' : '') . '>
                                <span class="sb-module-toggle-slider"></span>
                            </label>
                        </div>
                        <p class="sb-module-option-text">Convert PNG files to WebP (transparency preserved)</p>
                    </div>
                </div>
            </div>
            
            <div class="sb-sp-group sb-sp-field-horizontal" style="margin-bottom: 24px;">
                <div class="sb-sp-field-horizontal-left">
                    <label for="setting_quality" class="sb-sp-label">
                        WebP Quality
                    </label>
                    <p class="sb-sp-description">Recommended: 80. Higher values = better quality but larger files.</p>
                </div>
                <div style="flex: 0 0 50%;">
                    <input type="range" name="setting_quality" id="setting_quality" value="' . esc_attr($quality) . '" min="60" max="99" step="1" style="width: 100%;">
                    <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 8px;">
                        <span style="font-size: 12px; color: #6b7280;">60 (Smaller)</span>
                        <span id="quality-value" style="font-weight: 600; font-size: 14px; color: #111827;">' . esc_html($quality) . '</span>
                        <span style="font-size: 12px; color: #6b7280;">99 (Best Quality)</span>
                    </div>
                </div>
            </div>
            
            <div class="sb-sp-group sb-sp-field-horizontal" style="margin-bottom: 24px;">
                <div class="sb-sp-field-horizontal-left">
                    <label for="setting_keep_original" class="sb-sp-label">
                        Original Files
                    </label>
                    <p class="sb-sp-description">Choose whether to delete or keep original JPEG/PNG files after WebP conversion</p>
                </div>
                <select name="setting_keep_original" id="setting_keep_original" class="sb-sp-select">
                    <option value="no"' . ($keep_original === 'no' ? ' selected' : '') . '>Delete original (save space)</option>
                    <option value="yes"' . ($keep_original === 'yes' ? ' selected' : '') . '>Keep original as backup</option>
                </select>
            </div>

            <div class="sb-sp-group sb-sp-field-horizontal" style="margin-bottom: 24px;">
                <div class="sb-sp-field-horizontal-left">
                    <label for="setting_exclusion_suffix" class="sb-sp-label">
                        Exclusion Suffix
                    </label>
                    <p class="sb-sp-description">Images with this suffix in the filename will skip conversion (e.g., <code>photo-sbe.jpg</code>)</p>
                </div>
                <input type="text" name="setting_exclusion_suffix" id="setting_exclusion_suffix" value="' . esc_attr($exclusion_suffix) . '" placeholder="-sbe" class="sb-sp-input" style="width: 300px;">
            </div>

            <div class="sb-sp-info-box sb-sp-info-box-tip">
                <h4 class="sb-sp-heading-sm">Good to Know</h4>
                <ul class="sb-sp-list">
                    <li class="sb-sp-list-item">Conversion happens automatically during upload</li>
                    <li class="sb-sp-list-item">Only affects <strong>new uploads</strong> — existing images are not converted</li>
                    <li class="sb-sp-list-item">To convert existing images, use the "Regenerate Thumbnails" module</li>
                    <li class="sb-sp-list-item">If the converted file is larger than the original, the original is kept as-is</li>
                </ul>
            </div>
            
            <script>
            jQuery(document).ready(function($) {
                $("#setting_quality").on("input", function() {
                    $("#quality-value").text($(this).val());
                });
            });
            </script>
        ';
        
        echo $html;
    }
    
    private function webp_supported() {
        // Check GD support
        if (function_exists('imagewebp')) {
            return true;
        }
        
        // Check Imagick support
        if (extension_loaded('imagick') && class_exists('Imagick')) {
            $imagick = new Imagick();
            $formats = $imagick->queryFormats('WEBP');
            if (in_array('WEBP', $formats)) {
                return true;
            }
        }
        
        return false;
    }
    
    public function show_support_notice() {
        // Check what's available for better diagnostics
        $gd_available = function_exists('imagecreatefromjpeg');
        $gd_webp = function_exists('imagewebp');
        $imagick_available = extension_loaded('imagick') && class_exists('Imagick');
        $imagick_webp = false;

        if ($imagick_available) {
            try {
                $imagick = new Imagick();
                $formats = $imagick->queryFormats('WEBP');
                $imagick_webp = in_array('WEBP', $formats);
            } catch (Exception $e) {
                // Imagick available but error checking formats
            }
        }

        echo '<div class="notice notice-warning is-dismissible">';
        echo '<p><strong>' . esc_html__('WebP Conversion Module:', 'wpswitchboard') . '</strong> ' . esc_html__('Your server does not support WebP image conversion.', 'wpswitchboard') . '</p>';
        echo '<p><strong>' . esc_html__('Server Status:', 'wpswitchboard') . '</strong></p>';
        echo '<ul style="list-style: disc; margin-left: 20px;">';

        if ($gd_available) {
            echo '<li>GD Library: Installed, ' . ($gd_webp ? '<span style="color: #0a7e3e;">WebP supported</span>' : '<span style="color: #dc2626;">WebP NOT supported</span>') . '</li>';
        } else {
            echo '<li>GD Library: <span style="color: #dc2626;">Not installed</span></li>';
        }

        if ($imagick_available) {
            echo '<li>ImageMagick: Installed, ' . ($imagick_webp ? '<span style="color: #0a7e3e;">WebP supported</span>' : '<span style="color: #dc2626;">WebP NOT supported</span>') . '</li>';
        } else {
            echo '<li>ImageMagick: <span style="color: #dc2626;">Not installed</span></li>';
        }

        echo '</ul>';
        echo '<p>' . esc_html__('Contact your hosting provider and ask them to enable "GD Library with WebP support" or "ImageMagick with WebP support".', 'wpswitchboard') . '</p>';
        echo '</div>';
    }
    
    public function convert_to_webp($upload) {
        // Check if upload was successful
        if (isset($upload['error']) && $upload['error']) {
            return $upload;
        }

        $file_path = $upload['file'];
        $file_type = $upload['type'];

        // Don't try to convert WebP files (avoid recursive conversion)
        if ($file_type === 'image/webp' || preg_match('/\.webp$/i', $file_path)) {
            return $upload;
        }

        // Get settings
        $convert_jpeg = isset($this->settings['convert_jpeg']) ? $this->settings['convert_jpeg'] : 'yes';
        $convert_png = isset($this->settings['convert_png']) ? $this->settings['convert_png'] : 'yes';
        $quality = isset($this->settings['quality']) ? min(intval($this->settings['quality']), 99) : 80;
        $keep_original = isset($this->settings['keep_original']) ? $this->settings['keep_original'] : 'no';
        $exclusion_suffix = isset($this->settings['exclusion_suffix']) ? $this->settings['exclusion_suffix'] : '-sbe';

        // Check if file should be excluded based on suffix
        $filename = basename($file_path);
        if (!empty($exclusion_suffix) && strpos($filename, $exclusion_suffix) !== false) {
            return $upload; // Skip conversion
        }

        // Check if we should convert this file type
        $should_convert = false;
        if ($file_type === 'image/jpeg' && $convert_jpeg === 'yes') {
            $should_convert = true;
        } elseif ($file_type === 'image/png' && $convert_png === 'yes') {
            $should_convert = true;
        }

        if (!$should_convert) {
            return $upload;
        }
        
        // Temporarily increase resource limits for image conversion
        $this->set_resource_limits();

        // Capture original file size for comparison after conversion
        $original_size = filesize($file_path);

        // Convert to WebP
        $webp_path = preg_replace('/\.(jpe?g|png)$/i', '.webp', $file_path);
        
        $converted = false;
        
        // Try GD first
        if (function_exists('imagecreatefromjpeg') && function_exists('imagewebp')) {
            $converted = $this->convert_with_gd($file_path, $webp_path, $quality);
        }
        
        // Try Imagick if GD failed
        if (!$converted && extension_loaded('imagick') && class_exists('Imagick')) {
            $converted = $this->convert_with_imagick($file_path, $webp_path, $quality);
        }
        
        if ($converted) {
            // Skip conversion if the converted file is larger than the original
            $converted_size = filesize($webp_path);
            if ($converted_size >= $original_size) {
                @unlink($webp_path);
                return $upload;
            }

            // Delete original if configured
            if ($keep_original === 'no') {
                @unlink($file_path);
            }

            // Track conversion stats
            $this->update_stats($original_size, $converted_size);

            // Update upload info
            $upload['file'] = $webp_path;
            $upload['url'] = preg_replace('/\.(jpe?g|png)$/i', '.webp', $upload['url']);
            $upload['type'] = 'image/webp';
        }

        return $upload;
    }

    private function convert_with_gd($source, $destination, $quality) {
        try {
            if (!file_exists($source) || !is_readable($source)) {
                return false;
            }

            $image_info = @getimagesize($source);
            if ($image_info === false) {
                return false;
            }

            // Skip images that exceed maximum supported resolution
            if ($image_info[0] > 8192 || $image_info[1] > 8192) {
                return false;
            }

            // Determine actual image type from the file (more reliable than mime type)
            $actual_type = $image_info[2]; // IMAGETYPE_XXX constant
            $actual_mime = $image_info['mime'];

            // Skip animated GIFs (could be disguised with wrong extension)
            if ($actual_type === IMAGETYPE_GIF && $this->is_animated_gif($source)) {
                return false;
            }

            $image = false;

            // Use actual file type instead of provided mime type to handle mismatches
            if ($actual_type === IMAGETYPE_JPEG || $actual_mime === 'image/jpeg') {
                $image = @imagecreatefromjpeg($source);

                // Correct EXIF orientation for JPEG images (handles phone photos)
                if ($image) {
                    $image = $this->correct_image_orientation($source, $image);
                }
            } elseif ($actual_type === IMAGETYPE_PNG || $actual_mime === 'image/png') {
                $image = @imagecreatefrompng($source);

                // Preserve transparency for PNG
                if ($image) {
                    // Convert palette/indexed images to truecolor first
                    // This is critical for palette PNGs (8-bit colormap)
                    if (!@imageistruecolor($image)) {
                        @imagepalettetotruecolor($image);
                    }
                    // Use false to preserve raw alpha channel data instead of compositing
                    @imagealphablending($image, false);
                    @imagesavealpha($image, true);
                }
            }

            if (!$image) {
                return false;
            }

            $result = @imagewebp($image, $destination, $quality);

            if ($result && file_exists($destination) && filesize($destination) > 0) {
                // Fix WebP byte-alignment: some decoders have issues with odd-sized files
                if (filesize($destination) % 2 === 1) {
                    file_put_contents($destination, "\0", FILE_APPEND);
                }
                imagedestroy($image);
                return true;
            }

            imagedestroy($image);
            @unlink($destination);
            return false;
        } catch (Exception $e) {
            return false;
        }
    }

    private function convert_with_imagick($source, $destination, $quality) {
        try {
            if (!file_exists($source) || !is_readable($source)) {
                return false;
            }

            $image = new Imagick($source);

            // Skip images that exceed maximum supported resolution
            $geo = $image->getImageGeometry();
            if ($geo['width'] > 8192 || $geo['height'] > 8192) {
                $image->clear();
                $image->destroy();
                return false;
            }

            // Skip animated images (e.g., animated GIFs disguised with wrong extension)
            if ($image->getNumberImages() > 1) {
                $image->clear();
                $image->destroy();
                return false;
            }

            // Correct EXIF orientation (handles phone photos with rotation flags)
            $image->autoOrient();

            // Strip metadata while preserving ICC color profiles to avoid color shifts
            $profiles = $image->getImageProfiles('icc', true);
            $image->stripImage();
            if (!empty($profiles) && isset($profiles['icc'])) {
                $image->profileImage('icc', $profiles['icc']);
            }

            $image->setImageFormat('webp');
            $image->setImageCompressionQuality($quality);
            $result = $image->writeImage($destination);
            $image->clear();
            $image->destroy();

            if (!$result || !file_exists($destination) || filesize($destination) === 0) {
                @unlink($destination);
                return false;
            }

            return true;
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * Temporarily increase PHP resource limits for image conversion
     */
    private function set_resource_limits() {
        $current_limit = ini_get('memory_limit');
        $current_bytes = wp_convert_hr_to_bytes($current_limit);
        $target_bytes = 512 * 1024 * 1024; // 512MB
        if ($current_bytes < $target_bytes) {
            @ini_set('memory_limit', '512M');
        }

        if (strpos(ini_get('disable_functions') ?: '', 'set_time_limit') === false) {
            @set_time_limit(120);
        }
    }

    /**
     * Detect animated GIF by counting frame headers in binary data
     */
    private function is_animated_gif($file_path) {
        $fh = @fopen($file_path, 'rb');
        if (!$fh) {
            return false;
        }

        $count = 0;
        while (!feof($fh) && $count < 2) {
            $chunk = fread($fh, 1024 * 100);
            $count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches);
        }
        fclose($fh);

        return $count > 1;
    }

    /**
     * Correct image orientation based on EXIF data (for JPEG images)
     */
    private function correct_image_orientation($source, $image) {
        if (!function_exists('exif_read_data')) {
            return $image;
        }

        $exif = @exif_read_data($source);
        if (empty($exif['Orientation'])) {
            return $image;
        }

        switch ($exif['Orientation']) {
            case 2:
                imageflip($image, IMG_FLIP_HORIZONTAL);
                break;
            case 3:
                $rotated = imagerotate($image, 180, 0);
                imagedestroy($image);
                $image = $rotated;
                break;
            case 4:
                imageflip($image, IMG_FLIP_VERTICAL);
                break;
            case 5:
                $rotated = imagerotate($image, -90, 0);
                imagedestroy($image);
                $image = $rotated;
                imageflip($image, IMG_FLIP_HORIZONTAL);
                break;
            case 6:
                $rotated = imagerotate($image, -90, 0);
                imagedestroy($image);
                $image = $rotated;
                break;
            case 7:
                $rotated = imagerotate($image, 90, 0);
                imagedestroy($image);
                $image = $rotated;
                imageflip($image, IMG_FLIP_HORIZONTAL);
                break;
            case 8:
                $rotated = imagerotate($image, 90, 0);
                imagedestroy($image);
                $image = $rotated;
                break;
        }

        return $image;
    }

    /**
     * Update cumulative conversion stats
     */
    private function update_stats($original_size, $converted_size) {
        $stats = get_option('switchboard_webp-conversion_stats', []);
        $stats['count'] = (isset($stats['count']) ? intval($stats['count']) : 0) + 1;
        $stats['bytes_saved'] = (isset($stats['bytes_saved']) ? intval($stats['bytes_saved']) : 0) + ($original_size - $converted_size);
        update_option('switchboard_webp-conversion_stats', $stats, false);
    }

    /**
     * Format bytes into human-readable string
     */
    private function format_bytes($bytes) {
        if ($bytes >= 1073741824) {
            return number_format($bytes / 1073741824, 2) . ' GB';
        } elseif ($bytes >= 1048576) {
            return number_format($bytes / 1048576, 1) . ' MB';
        } elseif ($bytes >= 1024) {
            return number_format($bytes / 1024, 1) . ' KB';
        }
        return $bytes . ' B';
    }
}

// Initialize and register with data system
$switchboard_instance = new SwitchboardWebPConversion();
Switchboard_Data_Registry::get_instance()->auto_register($switchboard_instance, __FILE__);
