<?php
/**
 * TamRank WebP Generator
 *
 * Converteert afbeeldingen naar WebP met Imagick (voorkeur) of GD (fallback)
 * Transparantie in PNG's wordt behouden.
 */

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

class TamRank_WebP_Generator {
    /**
     * Maak WebP bestanden voor een attachment (origineel + alle registered sizes)
     *
     * @param int   $attachment_id
     * @param array $settings ['quality'=>int,'convert_png'=>bool,'sizes'=>array]
     * @return array Resultaten per size
     */
    public function generate_for_attachment($attachment_id, $settings = array()) {
        $results = array();

        $file = get_attached_file($attachment_id);
        if (!$file || !file_exists($file)) {
            return $results;
        }

        $meta = wp_get_attachment_metadata($attachment_id);
        $uploads = wp_get_upload_dir();
        $base_dir = trailingslashit($uploads['basedir']);

        // Haal toegestane sizes op (standaard: full, large, medium_large, medium)
        $allowed_sizes = !empty($settings['sizes']) && is_array($settings['sizes'])
            ? $settings['sizes']
            : array('full', 'large', 'medium_large', 'medium', '1536x1536', '2048x2048');

        // Voeg origineel toe als het is geselecteerd
        $targets = array();
        if (in_array('full', $allowed_sizes)) {
            $targets['full'] = $file;
        }

        // Voeg alleen geselecteerde sizes toe
        if (!empty($meta['sizes'])) {
            $dirname = pathinfo($file, PATHINFO_DIRNAME);
            foreach ($meta['sizes'] as $size => $data) {
                if (in_array($size, $allowed_sizes) && !empty($data['file'])) {
                    $targets[$size] = $dirname . '/' . $data['file'];
                }
            }
        }

        // Animated GIFs would convert to a single static frame; skip them so the
        // original animation is preserved (no twin -> serving leaves the <img>).
        $is_animated_gif = (strtolower(pathinfo($file, PATHINFO_EXTENSION)) === 'gif')
            && $this->is_animated_gif($file);

        foreach ($targets as $size => $path) {
            if (!file_exists($path)) {
                continue;
            }
            $info = pathinfo($path);
            $webp_path = $info['dirname'] . '/' . $info['filename'] . '.webp';

            // Skip svg of andere niet-afbeeldingen
            $ext = strtolower($info['extension'] ?? '');
            if (in_array($ext, array('svg', 'ico'))) {
                continue;
            }
            if ($ext === 'png' && empty($settings['convert_png'])) {
                continue; // png niet converteren tenzij toegestaan
            }
            if ($ext === 'gif' && $is_animated_gif) {
                continue; // keep animated GIFs animated (a WebP twin would freeze them)
            }

            $generated = $this->convert_to_webp($path, $webp_path, isset($settings['quality']) ? (int) $settings['quality'] : 80);
            if ($generated) {
                $orig_size = filesize($path);
                $webp_size = file_exists($webp_path) ? filesize($webp_path) : 0;
                $saved = $webp_size > 0 ? max(0, $orig_size - $webp_size) : 0;
                $results[$size] = array(
                    'path' => $webp_path,
                    'saved_bytes' => $saved
                );
            }
        }

        return $results;
    }

    /**
     * Detect a multi-frame (animated) GIF without a full decode.
     */
    private function is_animated_gif($path) {
        if (!is_readable($path)) {
            return false;
        }
        $contents = $this->read_local_file($path);
        if ($contents === '') {
            // Could not read the bytes (e.g. a non-'direct' filesystem method).
            // Bias toward "animated" so we SKIP conversion and never freeze a real
            // animation into a static WebP twin. A false positive only costs one
            // skipped optimization; a false negative silently breaks the GIF.
            return true;
        }
        // >1 Graphic Control Extension block = multi-frame (animated) GIF.
        return preg_match_all('#\x00\x21\xF9\x04.{4}\x00[\x2C\x00]#s', $contents) > 1;
    }

    /**
     * Read a local file through WP_Filesystem. wp.org requires this over direct
     * fopen()/fread() (WordPress.WP.AlternativeFunctions). Returns '' when the
     * direct filesystem method is unavailable, in which case callers treat the
     * image as non-animated.
     */
    private function read_local_file($path) {
        global $wp_filesystem;
        if (!function_exists('WP_Filesystem')) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }
        // Only the direct method is safe here; requesting FTP/SSH credentials
        // would emit a form mid-request.
        if ('direct' !== get_filesystem_method()) {
            return '';
        }
        if (empty($wp_filesystem)) {
            WP_Filesystem();
        }
        if (empty($wp_filesystem) || !is_callable(array($wp_filesystem, 'get_contents'))) {
            return '';
        }
        $contents = $wp_filesystem->get_contents($path);
        return is_string($contents) ? $contents : '';
    }

    /**
     * Conversie naar WebP met Imagick of GD
     */
    private function convert_to_webp($source, $destination, $quality = 80) {
        // Probeer Imagick
        if (extension_loaded('imagick') && class_exists('Imagick')) {
            try {
                $imagick = new Imagick($source);
                $imagick->setImageFormat('webp');
                $imagick->setImageCompressionQuality($quality);
                $imagick->writeImage($destination);
                $imagick->clear();
                $imagick->destroy();
                return file_exists($destination);
            } catch (Exception $e) {
                // fallback naar GD
            }
        }

        // Fallback GD
        if (!function_exists('imagecreatefromstring') || !function_exists('imagewebp')) {
            return false;
        }
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- reading a local uploads-dir image into memory for GD conversion, not a remote resource.
        $contents = file_get_contents($source);
        if ($contents === false) {
            return false;
        }
        $image = imagecreatefromstring($contents);
        if (!$image) {
            return false;
        }

        // Behoud transparantie
        imagepalettetotruecolor($image);
        imagealphablending($image, true);
        imagesavealpha($image, true);

        $result = imagewebp($image, $destination, $quality);
        imagedestroy($image);
        return $result && file_exists($destination);
    }

    /**
     * Controleer server support voor WebP
     */
    public static function server_supports_webp() {
        $imagick = extension_loaded('imagick') && class_exists('Imagick');
        $gd = function_exists('imagewebp');
        return $imagick || $gd;
    }
}
