<?php
/**
 * EU AI Act Labeller & Watermark Service
 *
 * Implements official European Commission transparency labelling icons
 * and custom graphics for AI-generated and AI-modified content under the EU AI Act (Article 50).
 *
 * @package PackshotPhotoAI
 */

namespace PackshotPhotoAI\Core;

use PackshotPhotoAI\Core\Logger;
use PackshotPhotoAI\Core\MemoryManager;

defined('ABSPATH') || exit;

class EuAiActLabeller {

    /**
     * Map of available icon presets
     */
    public const ICONS = [
        'generated-white-trans' => [
            'type'     => 'generated',
            'style'    => 'white-trans',
            'file'     => 'ai-generated-white-trans.png',
            'label'    => 'AI Generated (White Semi-Transparent)',
            'category' => 'AI Generated',
        ],
        'generated-white' => [
            'type'     => 'generated',
            'style'    => 'white',
            'file'     => 'ai-generated-white.png',
            'label'    => 'AI Generated (White Solid)',
            'category' => 'AI Generated',
        ],
        'generated-black-trans' => [
            'type'     => 'generated',
            'style'    => 'black-trans',
            'file'     => 'ai-generated-black-trans.png',
            'label'    => 'AI Generated (Black Semi-Transparent)',
            'category' => 'AI Generated',
        ],
        'generated-black' => [
            'type'     => 'generated',
            'style'    => 'black',
            'file'     => 'ai-generated-black.png',
            'label'    => 'AI Generated (Black Solid)',
            'category' => 'AI Generated',
        ],
        'modified-white-trans' => [
            'type'     => 'modified',
            'style'    => 'white-trans',
            'file'     => 'ai-modified-white-trans.png',
            'label'    => 'AI Modified (White Semi-Transparent)',
            'category' => 'AI Modified',
        ],
        'modified-white' => [
            'type'     => 'modified',
            'style'    => 'white',
            'file'     => 'ai-modified-white.png',
            'label'    => 'AI Modified (White Solid)',
            'category' => 'AI Modified',
        ],
        'modified-black-trans' => [
            'type'     => 'modified',
            'style'    => 'black-trans',
            'file'     => 'ai-modified-black-trans.png',
            'label'    => 'AI Modified (Black Semi-Transparent)',
            'category' => 'AI Modified',
        ],
        'modified-black' => [
            'type'     => 'modified',
            'style'    => 'black',
            'file'     => 'ai-modified-black.png',
            'label'    => 'AI Modified (Black Solid)',
            'category' => 'AI Modified',
        ],
        'basic-white-trans' => [
            'type'     => 'basic',
            'style'    => 'white-trans',
            'file'     => 'ai-white-trans.png',
            'label'    => 'AI Badge (White Semi-Transparent)',
            'category' => 'AI Badge',
        ],
        'basic-white' => [
            'type'     => 'basic',
            'style'    => 'white',
            'file'     => 'ai-white.png',
            'label'    => 'AI Badge (White Solid)',
            'category' => 'AI Badge',
        ],
        'basic-black-trans' => [
            'type'     => 'basic',
            'style'    => 'black-trans',
            'file'     => 'ai-black-trans.png',
            'label'    => 'AI Badge (Black Semi-Transparent)',
            'category' => 'AI Badge',
        ],
        'basic-black' => [
            'type'     => 'basic',
            'style'    => 'black',
            'file'     => 'ai-black.png',
            'label'    => 'AI Badge (Black Solid)',
            'category' => 'AI Badge',
        ],
    ];

    /**
     * AJAX handler to fetch list of available icons
     */
    public function ajax_get_icons(): void {
        wp_send_json_success([
            'icons'        => self::ICONS,
            'icons_url'    => PACKSHOTPHOTO_AI_URL . 'assets/icons/eu-ai/',
            'current_mode' => get_option('packshotphoto_eu_ai_mode', 'preset'),
            'preset'       => get_option('packshotphoto_eu_ai_preset', 'generated-white-trans'),
            'custom_image' => get_option('packshotphoto_eu_ai_custom_image', ''),
            'position'     => get_option('packshotphoto_eu_ai_position', 'bottom-right'),
            'scale'        => (int) get_option('packshotphoto_eu_ai_scale', 15),
            'opacity'      => (int) get_option('packshotphoto_eu_ai_opacity', 100),
        ]);
    }

    /**
     * AJAX handler to apply EU AI label to attachment
     */
    public function ajax_apply_label(): void {
        $post_id = isset($_POST['id']) ? absint($_POST['id']) : 0;
        if (!$post_id) {
            wp_send_json_error(['message' => __('Invalid file ID.', 'packshotphoto-ai')], 400);
        }

        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        $valid_nonce = wp_verify_nonce($nonce, 'packshotphoto_edit_' . $post_id)
                    || wp_verify_nonce($nonce, 'packshotphoto_global_nonce')
                    || wp_verify_nonce($nonce, 'packshotphoto_admin_nonce');

        if (!$valid_nonce && !current_user_can('manage_options')) {
            Logger::error('EuAiActLabeller: Nonce verification failed for #' . $post_id, ['nonce' => $nonce]);
            wp_send_json_error(['message' => __('Security verification failed.', 'packshotphoto-ai')], 403);
        }

        if (!current_user_can('upload_files') && !current_user_can('edit_post', $post_id) && !current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied.', 'packshotphoto-ai')], 403);
        }

        $custom_args = [];
        if (!empty($_POST['mode'])) {
            $custom_args['mode'] = sanitize_key($_POST['mode']);
        }
        if (!empty($_POST['preset'])) {
            $custom_args['preset'] = sanitize_key($_POST['preset']);
        }
        if (!empty($_POST['custom_image'])) {
            $custom_args['custom_image'] = esc_url_raw($_POST['custom_image']);
        }
        if (!empty($_POST['position'])) {
            $custom_args['position'] = sanitize_key($_POST['position']);
        }
        if (isset($_POST['scale'])) {
            $custom_args['scale'] = absint($_POST['scale']);
        }
        if (isset($_POST['opacity'])) {
            $custom_args['opacity'] = absint($_POST['opacity']);
        }

        $result = $this->apply_label($post_id, $custom_args);
        if ($result['success']) {
            wp_send_json_success($result);
        } else {
            Logger::error('EuAiActLabeller: apply_label failed', $result);
            wp_send_json_error($result, 500);
        }
    }

    /**
     * Apply EU AI watermark label to image file
     *
     * @param int   $post_id Attachment ID
     * @param array $args    Optional overrides for mode, preset, position, scale, opacity, etc.
     * @return array
     */
    public function apply_label(int $post_id, array $args = []): array {
        MemoryManager::ensure_memory_limit(true);

        $file_path = get_attached_file($post_id);
        if (!$file_path || !file_exists($file_path)) {
            Logger::error('EuAiActLabeller: get_attached_file returned missing path for #' . $post_id, ['path' => $file_path]);
            return ['success' => false, 'message' => __('Image file does not exist on server.', 'packshotphoto-ai')];
        }

        // Determine settings: parameters override saved options
        $mode         = $args['mode']         ?? get_option('packshotphoto_eu_ai_mode', 'preset');
        $preset       = $args['preset']       ?? get_option('packshotphoto_eu_ai_preset', 'generated-white-trans');
        $custom_image = $args['custom_image'] ?? get_option('packshotphoto_eu_ai_custom_image', '');
        $position     = $args['position']     ?? get_option('packshotphoto_eu_ai_position', 'bottom-right');
        $scale        = isset($args['scale'])   ? absint($args['scale'])   : (int) get_option('packshotphoto_eu_ai_scale', 15);
        $opacity      = isset($args['opacity']) ? absint($args['opacity']) : (int) get_option('packshotphoto_eu_ai_opacity', 100);

        $padding = [
            'top'    => isset($args['padding_top'])    ? absint($args['padding_top'])    : (int) get_option('packshotphoto_eu_ai_padding_top', 15),
            'right'  => isset($args['padding_right'])  ? absint($args['padding_right'])  : (int) get_option('packshotphoto_eu_ai_padding_right', 15),
            'bottom' => isset($args['padding_bottom']) ? absint($args['padding_bottom']) : (int) get_option('packshotphoto_eu_ai_padding_bottom', 15),
            'left'   => isset($args['padding_left'])   ? absint($args['padding_left'])   : (int) get_option('packshotphoto_eu_ai_padding_left', 15),
        ];

        $scale   = max(5, min(60, $scale));
        $opacity = max(10, min(100, $opacity));

        // Locate watermark file
        $watermark_file = '';
        if ('custom' === $mode && !empty($custom_image)) {
            // Can be absolute path, attachment ID, or URL
            if (is_numeric($custom_image)) {
                $custom_path = get_attached_file((int) $custom_image);
                if ($custom_path && file_exists($custom_path)) {
                    $watermark_file = $custom_path;
                }
            } elseif (file_exists($custom_image)) {
                $watermark_file = $custom_image;
            } else {
                // Try converting URL to local path
                $upload_dir = wp_upload_dir();
                if (strpos($custom_image, $upload_dir['baseurl']) === 0) {
                    $rel_path = str_replace($upload_dir['baseurl'], '', $custom_image);
                    $local_path = $upload_dir['basedir'] . $rel_path;
                    if (file_exists($local_path)) {
                        $watermark_file = $local_path;
                    }
                }
            }
        }

        // Fallback to preset if custom not found or preset mode
        if (empty($watermark_file)) {
            if (!isset(self::ICONS[$preset])) {
                $preset = 'generated-white-trans';
            }
            $icon_info = self::ICONS[$preset];
            $base_dir  = defined('PACKSHOTPHOTO_AI_DIR') ? PACKSHOTPHOTO_AI_DIR : plugin_dir_path(dirname(__DIR__));
            $watermark_file = rtrim($base_dir, '/\\') . '/assets/icons/eu-ai/' . $icon_info['file'];
        }

        if (!file_exists($watermark_file)) {
            Logger::error('EuAiActLabeller: watermark file missing', ['path' => $watermark_file]);
            return ['success' => false, 'message' => __('EU AI icon file missing: ', 'packshotphoto-ai') . basename($watermark_file)];
        }

        Logger::info('EuAiActLabeller: Starting stamp for #' . $post_id, [
            'file_path'      => $file_path,
            'watermark_file' => $watermark_file,
            'file_writable'  => is_writable($file_path) ? 'yes' : 'NO',
            'file_size_before' => filesize($file_path),
            'mode' => $mode,
            'preset' => $preset,
            'position' => $position,
            'scale' => $scale,
            'opacity' => $opacity,
        ]);

        // 1. Create safety backup before stamping (so Restore button works)
        $this->ensure_file_backup($post_id, $file_path);

        // 2. Apply watermark using Imagick or GD
        $stamped = false;
        $engine_used = 'none';

        if (extension_loaded('imagick') && class_exists('\Imagick')) {
            try {
                $stamped = $this->stamp_image_imagick($file_path, $watermark_file, $position, $scale, $opacity, $padding);
                $engine_used = 'imagick';
                Logger::info('EuAiActLabeller: Imagick stamp result: ' . ($stamped ? 'OK' : 'FAIL'));
            } catch (\Throwable $e) {
                Logger::warning('EuAiActLabeller: Imagick failed, falling back to GD', ['err' => $e->getMessage()]);
                $stamped = false;
            }
        } else {
            Logger::info('EuAiActLabeller: Imagick not available, using GD');
        }

        if (!$stamped) {
            $stamped = $this->stamp_image_gd($file_path, $watermark_file, $position, $scale, $opacity, $padding);
            $engine_used = 'gd';
            Logger::info('EuAiActLabeller: GD stamp result: ' . ($stamped ? 'OK' : 'FAIL'));
        }

        if (!$stamped) {
            Logger::error('EuAiActLabeller: BOTH engines failed to stamp #' . $post_id);
            return ['success' => false, 'message' => __('Error stamping EU AI watermark on image.', 'packshotphoto-ai')];
        }

        // Force PHP to re-read file stats
        clearstatcache(true, $file_path);

        Logger::info('EuAiActLabeller: Stamp COMPLETE for #' . $post_id, [
            'engine'          => $engine_used,
            'file_size_after' => filesize($file_path),
        ]);

        // 3. Re-generate all intermediate thumbnail sizes from the stamped master file
        if (!function_exists('wp_generate_attachment_metadata')) {
            require_once ABSPATH . 'wp-admin/includes/image.php';
        }
        $new_meta = wp_generate_attachment_metadata($post_id, $file_path);
        if (!empty($new_meta) && is_array($new_meta) && !empty($new_meta['sizes'])) {
            wp_update_attachment_metadata($post_id, $new_meta);
            Logger::info('EuAiActLabeller: Successfully regenerated all thumbnails for #' . $post_id);
        } else {
            // Fallback: manually stamp existing intermediate sizes
            $this->stamp_intermediate_sizes($post_id, $watermark_file, $position, $scale, $opacity, $padding);
        }

        // 4. Save metadata
        update_post_meta($post_id, '_pmm_eu_ai_label', [
            'mode'      => $mode,
            'preset'    => $preset,
            'position'  => $position,
            'scale'     => $scale,
            'opacity'   => $opacity,
            'timestamp' => time(),
        ]);

        // Touch post modified date to automatically bust WordPress media cache everywhere
        wp_update_post([
            'ID'                => $post_id,
            'post_modified'     => current_time('mysql'),
            'post_modified_gmt' => current_time('mysql', 1),
        ]);

        $file_url  = wp_get_attachment_url($post_id);
        $thumb_url = wp_get_attachment_image_url($post_id, 'thumbnail') ?: $file_url;
        $timestamp = time();

        $cache_bust_url   = $file_url . (strpos($file_url, '?') !== false ? '&' : '?') . 't=' . $timestamp;
        $cache_bust_thumb = $thumb_url . (strpos($thumb_url, '?') !== false ? '&' : '?') . 't=' . $timestamp;

        return [
            'success'    => true,
            'message'    => __('Successfully stamped with EU AI Act label.', 'packshotphoto-ai'),
            'mode'       => $mode,
            'preset'     => $preset,
            'position'   => $position,
            'file_url'   => $cache_bust_url,
            'thumb_url'  => $cache_bust_thumb,
            'debug_info' => [
                'engine'         => $engine_used,
                'file_path'      => $file_path,
                'watermark_path' => $watermark_file,
                'file_writable'  => is_writable($file_path),
                'file_size'      => filesize($file_path),
                'imagick_loaded' => extension_loaded('imagick'),
                'gd_loaded'      => extension_loaded('gd'),
            ],
        ];
    }

    /**
     * Stamp intermediate thumbnail sizes of the attachment
     */
    private function stamp_intermediate_sizes(int $post_id, string $watermark_path, string $pos, int $scale, int $opacity, array $padding = []): void {
        $meta = wp_get_attachment_metadata($post_id);
        if (empty($meta['sizes']) || !is_array($meta['sizes'])) {
            return;
        }

        $file_path = get_attached_file($post_id);
        $dir_path  = dirname($file_path);
        $orig_w    = !empty($meta['width']) ? (int) $meta['width'] : 1920;

        $has_imagick = extension_loaded('imagick') && class_exists('\Imagick');

        foreach ($meta['sizes'] as $size_key => $size_data) {
            if (empty($size_data['file'])) {
                continue;
            }
            $thumb_path = $dir_path . '/' . $size_data['file'];
            if (!file_exists($thumb_path)) {
                continue;
            }

            $thumb_w = !empty($size_data['width']) ? (int) $size_data['width'] : 0;
            // Proportionally scale padding for thumbnails
            $ratio = max(0.05, min(1.0, $thumb_w / max(1, $orig_w)));
            $scaled_padding = [
                'top'    => max(1, (int) round(($padding['top'] ?? 15) * $ratio)),
                'right'  => max(1, (int) round(($padding['right'] ?? 15) * $ratio)),
                'bottom' => max(1, (int) round(($padding['bottom'] ?? 15) * $ratio)),
                'left'   => max(1, (int) round(($padding['left'] ?? 15) * $ratio)),
            ];

            if ($has_imagick) {
                try {
                    $this->stamp_image_imagick($thumb_path, $watermark_path, $pos, $scale, $opacity, $scaled_padding);
                } catch (\Throwable $e) {
                    $this->stamp_image_gd($thumb_path, $watermark_path, $pos, $scale, $opacity, $scaled_padding);
                }
            } else {
                $this->stamp_image_gd($thumb_path, $watermark_path, $pos, $scale, $opacity, $scaled_padding);
            }
        }
    }

    /**
     * Ensure original file backup in packshotphoto-trash
     */
    private function ensure_file_backup(int $post_id, string $file_path): void {
        $has_backup = get_post_meta($post_id, '_pmm_has_file_backup', true);
        if ('1' === $has_backup) {
            return;
        }

        $upload_dir = wp_upload_dir();
        $trash_dir  = $upload_dir['basedir'] . '/packshotphoto-trash';
        if (!file_exists($trash_dir)) {
            wp_mkdir_p($trash_dir);
        }

        $path_info    = pathinfo($file_path);
        $timestamp    = time();
        $backup_name  = $path_info['filename'] . '-original-' . $timestamp . '.' . ($path_info['extension'] ?? 'jpg');
        $backup_path  = $trash_dir . '/' . $backup_name;
        $backup_rel   = 'packshotphoto-trash/' . $backup_name;

        if (@copy($file_path, $backup_path)) {
            update_post_meta($post_id, '_pmm_original_backup', $backup_rel);
            update_post_meta($post_id, '_pmm_has_file_backup', '1');
            Logger::info('EuAiActLabeller: Created original backup for #' . $post_id, ['path' => $backup_rel]);
        }
    }

    /**
     * Stamp watermark image using GD library
     */
    private function stamp_image_gd(string $image_path, string $watermark_path, string $pos, int $scale = 15, int $opacity = 100, array $padding = []): bool {
        $info = @getimagesize($image_path);
        if (!$info) {
            $ext = strtolower(pathinfo($image_path, PATHINFO_EXTENSION));
            if ($ext === 'avif') {
                $mime = 'image/avif';
            } elseif ($ext === 'webp') {
                $mime = 'image/webp';
            } elseif (in_array($ext, ['jpg', 'jpeg'], true)) {
                $mime = 'image/jpeg';
            } elseif ($ext === 'png') {
                $mime = 'image/png';
            } else {
                $mime = @mime_content_type($image_path);
            }
        } else {
            $mime = $info['mime'];
        }

        $img = false;
        switch ($mime) {
            case 'image/jpeg':
                $img = @imagecreatefromjpeg($image_path);
                break;
            case 'image/png':
                $img = @imagecreatefrompng($image_path);
                break;
            case 'image/webp':
                $img = function_exists('imagecreatefromwebp') ? @imagecreatefromwebp($image_path) : false;
                break;
            case 'image/avif':
                $img = function_exists('imagecreatefromavif') ? @imagecreatefromavif($image_path) : false;
                break;
            default:
                break;
        }

        if (!$img) {
            Logger::error('EuAiActLabeller: GD failed to create image resource for ' . $image_path, [
                'mime' => $mime,
                'has_imagecreatefromavif' => function_exists('imagecreatefromavif'),
                'has_imagecreatefromwebp' => function_exists('imagecreatefromwebp'),
            ]);
            return false;
        }

        // Enable alpha blending on the destination image
        imagealphablending($img, true);
        imagesavealpha($img, true);

        // Load watermark (PNG, JPEG, WebP or GIF)
        $wm_info = @getimagesize($watermark_path);
        $wm_mime = $wm_info['mime'] ?? 'image/png';
        switch ($wm_mime) {
            case 'image/jpeg':
                $wm = @imagecreatefromjpeg($watermark_path);
                break;
            case 'image/webp':
                $wm = function_exists('imagecreatefromwebp') ? @imagecreatefromwebp($watermark_path) : false;
                break;
            case 'image/png':
            default:
                $wm = @imagecreatefrompng($watermark_path);
                break;
        }

        if (!$wm) {
            imagedestroy($img);
            return false;
        }

        $img_w = imagesx($img);
        $img_h = imagesy($img);
        $wm_w  = imagesx($wm);
        $wm_h  = imagesy($wm);

        // Perceptually balanced watermark scaling across all image dimensions (300px up to 4000px+)
        // Uses square-root dampened ratio against a standard 1200px web canvas
        $ref_w       = 1200.0;
        $user_pct    = max(5, min(60, $scale)) / 100.0;
        $base_size   = $ref_w * $user_pct; // e.g. 180px for 15%
        $dampened_w  = (int) round($base_size * sqrt(max(200, $img_w) / $ref_w));
        $target_wm_w = max(64, min((int) round($img_w * 0.32), $dampened_w));
        $target_wm_h = (int) round($wm_h * ($target_wm_w / max(1, $wm_w)));

        // Create transparent resampled canvas for watermark
        $wm_resized = imagecreatetruecolor($target_wm_w, $target_wm_h);
        imagealphablending($wm_resized, false);
        imagesavealpha($wm_resized, true);
        $transparent = imagecolorallocatealpha($wm_resized, 0, 0, 0, 127);
        imagefilledrectangle($wm_resized, 0, 0, $target_wm_w, $target_wm_h, $transparent);
        imagecopyresampled($wm_resized, $wm, 0, 0, 0, 0, $target_wm_w, $target_wm_h, $wm_w, $wm_h);

        $pad_top    = max(0, $padding['top'] ?? 15);
        $pad_right  = max(0, $padding['right'] ?? 15);
        $pad_bottom = max(0, $padding['bottom'] ?? 15);
        $pad_left   = max(0, $padding['left'] ?? 15);

        // Calculate position coordinates
        switch ($pos) {
            case 'top-left':
                $dest_x = $pad_left;
                $dest_y = $pad_top;
                break;
            case 'top-right':
                $dest_x = max(0, $img_w - $target_wm_w - $pad_right);
                $dest_y = $pad_top;
                break;
            case 'bottom-left':
                $dest_x = $pad_left;
                $dest_y = max(0, $img_h - $target_wm_h - $pad_bottom);
                break;
            case 'center':
                $dest_x = (int) round(($img_w - $target_wm_w) / 2);
                $dest_y = (int) round(($img_h - $target_wm_h) / 2);
                break;
            case 'bottom-right':
            default:
                $dest_x = max(0, $img_w - $target_wm_w - $pad_right);
                $dest_y = max(0, $img_h - $target_wm_h - $pad_bottom);
                break;
        }

        // Apply watermark with alpha blending
        if ($opacity < 100) {
            $this->imagecopymerge_alpha($img, $wm_resized, $dest_x, $dest_y, 0, 0, $target_wm_w, $target_wm_h, $opacity);
        } else {
            imagecopy($img, $wm_resized, $dest_x, $dest_y, 0, 0, $target_wm_w, $target_wm_h);
        }

        // Save back image
        $saved = false;
        switch ($mime) {
            case 'image/jpeg':
                $saved = @imagejpeg($img, $image_path, 92);
                break;
            case 'image/png':
                $saved = @imagepng($img, $image_path, 8);
                break;
            case 'image/webp':
                $saved = function_exists('imagewebp') ? @imagewebp($img, $image_path, 90) : false;
                break;
            case 'image/avif':
                $saved = function_exists('imageavif') ? @imageavif($img, $image_path, 85, 9) : false;
                break;
        }

        imagedestroy($img);
        imagedestroy($wm);
        imagedestroy($wm_resized);

        return $saved;
    }

    /**
     * Stamp watermark image using Imagick for optimal vector/raster alpha fidelity
     */
    private function stamp_image_imagick(string $image_path, string $watermark_path, string $pos, int $scale = 15, int $opacity = 100, array $padding = []): bool {
        $image = new \Imagick($image_path);
        $wm    = new \Imagick($watermark_path);

        $img_w = $image->getImageWidth();
        $img_h = $image->getImageHeight();
        $wm_w  = $wm->getImageWidth();
        $wm_h  = $wm->getImageHeight();

        // Perceptually balanced watermark scaling across all image dimensions (300px up to 4000px+)
        // Uses square-root dampened ratio against a standard 1200px web canvas
        $ref_w       = 1200.0;
        $user_pct    = max(5, min(60, $scale)) / 100.0;
        $base_size   = $ref_w * $user_pct; // e.g. 180px for 15%
        $dampened_w  = (int) round($base_size * sqrt(max(200, $img_w) / $ref_w));
        $target_wm_w = max(64, min((int) round($img_w * 0.32), $dampened_w));
        $target_wm_h = (int) round($wm_h * ($target_wm_w / max(1, $wm_w)));

        $wm->scaleImage($target_wm_w, $target_wm_h);

        if ($opacity < 100) {
            $wm->evaluateImage(\Imagick::EVALUATE_MULTIPLY, $opacity / 100, \Imagick::CHANNEL_ALPHA);
        }

        $pad_top    = max(0, $padding['top'] ?? 15);
        $pad_right  = max(0, $padding['right'] ?? 15);
        $pad_bottom = max(0, $padding['bottom'] ?? 15);
        $pad_left   = max(0, $padding['left'] ?? 15);

        switch ($pos) {
            case 'top-left':
                $dest_x = $pad_left;
                $dest_y = $pad_top;
                break;
            case 'top-right':
                $dest_x = max(0, $img_w - $target_wm_w - $pad_right);
                $dest_y = $pad_top;
                break;
            case 'bottom-left':
                $dest_x = $pad_left;
                $dest_y = max(0, $img_h - $target_wm_h - $pad_bottom);
                break;
            case 'center':
                $dest_x = (int) round(($img_w - $target_wm_w) / 2);
                $dest_y = (int) round(($img_h - $target_wm_h) / 2);
                break;
            case 'bottom-right':
            default:
                $dest_x = max(0, $img_w - $target_wm_w - $pad_right);
                $dest_y = max(0, $img_h - $target_wm_h - $pad_bottom);
                break;
        }

        $image->compositeImage($wm, \Imagick::COMPOSITE_OVER, $dest_x, $dest_y);
        $image->writeImage($image_path);

        $image->clear();
        $image->destroy();
        $wm->clear();
        $wm->destroy();

        return true;
    }

    /**
     * Alpha-preserving imagecopymerge helper for GD
     */
    private function imagecopymerge_alpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct): void {
        $pct /= 100.0;
        $cut = imagecreatetruecolor($src_w, $src_h);
        imagecopy($cut, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h);
        imagecopy($cut, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h);
        imagecopymerge($dst_im, $cut, $dst_x, $dst_y, 0, 0, $src_w, $src_h, (int) round($pct * 100));
        imagedestroy($cut);
    }
}
