<?php
namespace DS_Plugin;

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

class Rest {
    const NS = 'design-studio/v1';

    private static function decode_template_json_meta($template_id, $meta_key, $fallback = []) {
        $raw = (string) get_post_meta($template_id, $meta_key, true);
        if ($raw === '') return $fallback;
        $decoded = json_decode($raw, true);
        if (json_last_error() !== JSON_ERROR_NONE || !is_array($decoded)) return $fallback;
        return $decoded;
    }

    private static function normalize_zone_id($value) {
        $id = trim((string) $value);
        return $id;
    }

    private static function zone_file_key($zone_id) {
        $zone_id = self::normalize_zone_id($zone_id);
        $safe = sanitize_file_name($zone_id);
        if ($safe === '') $safe = 'zone';
        return $safe . '-' . substr(md5($zone_id), 0, 10);
    }

    private static function normalize_zone_id_legacy($value) {
        return strtolower(preg_replace('/[^a-z0-9_-]/', '', (string) $value));
    }

    private static function read_zone_map_value($map, $zone_id, $default = null) {
        if (!is_array($map)) return $default;
        $zone_id = self::normalize_zone_id($zone_id);
        if ($zone_id === '') return $default;
        if (array_key_exists($zone_id, $map)) return $map[$zone_id];
        foreach ($map as $k => $v) {
            if (self::normalize_zone_id($k) === $zone_id) return $v;
        }
        $legacy = self::normalize_zone_id_legacy($zone_id);
        if ($legacy === '') return $default;
        $hits = [];
        foreach ($map as $k => $v) {
            if (self::normalize_zone_id_legacy($k) === $legacy) {
                $hits[] = $v;
            }
        }
        return count($hits) === 1 ? $hits[0] : $default;
    }

    private static function to_float($value, $default = 0.0) {
        return is_numeric($value) ? (float) $value : (float) $default;
    }

    private static function build_template_zone_snapshot($template_id) {
        $template_id = (int) $template_id;
        if ($template_id <= 0) return ['zones' => [], 'mockups' => []];

        $zones = [];
        $mockups = [];

        $preview = self::decode_template_json_meta($template_id, 'preview_config_json', []);
        $raw_mockups = (isset($preview['mockups']) && is_array($preview['mockups'])) ? $preview['mockups'] : [];
        foreach ($raw_mockups as $m) {
            if (!is_array($m)) continue;
            $image_url = esc_url_raw((string) ($m['image_url'] ?? ''));
            if ($image_url === '') continue;
            $areas_out = [];
            $areas = isset($m['areas']) && is_array($m['areas']) ? $m['areas'] : [];
            foreach ($areas as $a) {
                if (!is_array($a)) continue;
                $zone_id = self::normalize_zone_id($a['zone_id'] ?? $a['id'] ?? '');
                if ($zone_id === '') continue;
                $area_id = self::normalize_zone_id($a['id'] ?? '');
                if ($area_id === '') $area_id = $zone_id;
                $row = [
                    'zone_id' => $zone_id,
                    'id' => $area_id,
                    'label' => sanitize_text_field((string) ($a['label'] ?? $zone_id)),
                    'width_mm' => self::to_float($a['width_mm'] ?? 0, 0),
                    'height_mm' => self::to_float($a['height_mm'] ?? 0, 0),
                    'x_pct' => self::to_float($a['x_pct'] ?? 0, 0),
                    'y_pct' => self::to_float($a['y_pct'] ?? 0, 0),
                    'w_pct' => self::to_float($a['w_pct'] ?? 0, 0),
                    'h_pct' => self::to_float($a['h_pct'] ?? 0, 0),
                    'allow_subcanvas' => !empty($a['allow_subcanvas']) ? 1 : 0,
                    'portrait_width_mm' => self::to_float($a['portrait_width_mm'] ?? 0, 0),
                    'portrait_height_mm' => self::to_float($a['portrait_height_mm'] ?? 0, 0),
                    'landscape_width_mm' => self::to_float($a['landscape_width_mm'] ?? 0, 0),
                    'landscape_height_mm' => self::to_float($a['landscape_height_mm'] ?? 0, 0),
                ];
                $areas_out[] = $row;
                $zones[$zone_id] = [
                    'id' => $zone_id,
                    'label' => $row['label'],
                    'width_mm' => $row['width_mm'],
                    'height_mm' => $row['height_mm'],
                    'allow_subcanvas' => $row['allow_subcanvas'],
                    'portrait_width_mm' => $row['portrait_width_mm'],
                    'portrait_height_mm' => $row['portrait_height_mm'],
                    'landscape_width_mm' => $row['landscape_width_mm'],
                    'landscape_height_mm' => $row['landscape_height_mm'],
                    'side' => sanitize_key((string) ($m['side'] ?? 'custom')),
                ];
            }
            $mockups[] = [
                'id' => sanitize_key((string) ($m['id'] ?? '')),
                'name' => sanitize_text_field((string) ($m['name'] ?? 'Mockup')),
                'side' => sanitize_key((string) ($m['side'] ?? 'custom')),
                'image_url' => $image_url,
                'areas' => $areas_out,
            ];
        }

        $template_zones = self::decode_template_json_meta($template_id, 'zones_json', []);
        if (is_array($template_zones)) {
            foreach ($template_zones as $z) {
                if (!is_array($z)) continue;
                $zone_id = self::normalize_zone_id($z['id'] ?? '');
                if ($zone_id === '') continue;
                $cur = isset($zones[$zone_id]) && is_array($zones[$zone_id]) ? $zones[$zone_id] : [];
                $zones[$zone_id] = array_merge([
                    'id' => $zone_id,
                    'label' => sanitize_text_field((string) ($z['label'] ?? $zone_id)),
                    'width_mm' => self::to_float($z['width_mm'] ?? 0, 0),
                    'height_mm' => self::to_float($z['height_mm'] ?? 0, 0),
                    'allow_subcanvas' => !empty($z['allow_subcanvas']) ? 1 : 0,
                    'portrait_width_mm' => self::to_float($z['portrait_width_mm'] ?? 0, 0),
                    'portrait_height_mm' => self::to_float($z['portrait_height_mm'] ?? 0, 0),
                    'landscape_width_mm' => self::to_float($z['landscape_width_mm'] ?? 0, 0),
                    'landscape_height_mm' => self::to_float($z['landscape_height_mm'] ?? 0, 0),
                    'side' => sanitize_key((string) ($z['side'] ?? 'custom')),
                ], $cur);
            }
        }

        return ['zones' => $zones, 'mockups' => $mockups];
    }

    public static function init() {
        add_action('rest_api_init', [__CLASS__, 'register_routes']);
    }

    public static function register_routes() {
        register_rest_route(self::NS, '/templates', [
            [
                'methods' => 'GET',
                'callback' => [__CLASS__, 'get_templates'],
                'permission_callback' => [__CLASS__, 'require_rest_nonce'],
            ],
        ]);

        register_rest_route(self::NS, '/save', [
            [
                'methods' => 'POST',
                'callback' => [__CLASS__, 'save_design'],
                'permission_callback' => [__CLASS__, 'can_save_design'],
                'args' => [
                    'templateId' => ['required' => true],
                    'pngData' => ['required' => true],
                    'layers' => ['required' => false],
                    'zonesUsed' => ['required' => false],
                    'allZones' => ['required' => false],
                ],
            ],
        ]);

        register_rest_route(self::NS, '/design/(?P<id>\d+)', [
            [
                'methods' => 'GET',
                'callback' => [__CLASS__, 'get_design'],
                'permission_callback' => [__CLASS__, 'require_rest_nonce'],
            ],
        ]);

        register_rest_route(self::NS, '/my-designs', [
            [
                'methods' => 'GET',
                'callback' => [__CLASS__, 'get_my_designs'],
                'permission_callback' => [__CLASS__, 'is_logged_in'],
            ],
        ]);
        register_rest_route(self::NS, '/design-library', [
            [
                'methods' => 'GET',
                'callback' => [__CLASS__, 'get_design_library'],
                'permission_callback' => [__CLASS__, 'require_rest_nonce'],
            ],
        ]);

        register_rest_route(self::NS, '/design/(?P<id>\d+)/visibility', [
            [
                'methods' => 'POST',
                'callback' => [__CLASS__, 'update_design_visibility'],
                'permission_callback' => [__CLASS__, 'is_logged_in'],
                'args' => [
                    'visibility' => ['required' => true],
                ],
            ],
        ]);

        register_rest_route(self::NS, '/ai/remove-bg', [
            [
                'methods' => 'POST',
                'callback' => [__CLASS__, 'remove_background'],
                'permission_callback' => [__CLASS__, 'is_logged_in'],
                'args' => [
                    'imageData' => ['required' => true],
                ],
            ],
        ]);

        register_rest_route(self::NS, '/products/by-template/(?P<id>\d+)', [
            [
                'methods' => 'GET',
                'callback' => [__CLASS__, 'get_products_by_template'],
                'permission_callback' => [__CLASS__, 'require_rest_nonce'],
            ],
        ]);

        register_rest_route(self::NS, '/design/(?P<id>\d+)/zone-layer', [
            [
                'methods' => 'POST',
                'callback' => [__CLASS__, 'save_design_zone_layer'],
                'permission_callback' => [__CLASS__, 'require_rest_nonce'],
            ],
        ]);

        register_rest_route(self::NS, '/protection/check', [
            [
                'methods' => 'GET',
                'callback' => [__CLASS__, 'protection_check'],
                'permission_callback' => [__CLASS__, 'require_rest_nonce'],
            ],
        ]);

        register_rest_route(self::NS, '/protection/consume', [
            [
                'methods' => 'POST',
                'callback' => [__CLASS__, 'protection_consume'],
                'permission_callback' => [__CLASS__, 'require_rest_nonce'],
                'args' => [
                    'action' => ['required' => false],
                ],
            ],
        ]);
    }

    public static function is_logged_in($request) {
        return is_user_logged_in();
    }

    public static function can_save_design($request) {
        return self::require_rest_nonce($request);
    }

    public static function require_rest_nonce($request) {
        $nonce_ok = self::verify_rest_nonce($request);
        if (is_wp_error($nonce_ok)) return $nonce_ok;
        return true;
    }

    private static function protection_settings() {
        return [
            'enabled' => true,
            'trial_total' => 0,
        ];
    }

    private static function protection_enabled() {
        $cfg = self::protection_settings();
        $enabled = (bool) $cfg['enabled'];
        return (bool) apply_filters('ds_protection_enabled', $enabled);
    }

    private static function protection_remote_enforced() {
        $opts = \DS_Plugin\Settings::get();
        $api_url = isset($opts['protection_license_api_url']) ? trim((string) $opts['protection_license_api_url']) : '';
        $api_token = isset($opts['protection_license_api_token']) ? trim((string) $opts['protection_license_api_token']) : '';
        return ($api_url !== '' && $api_token !== '');
    }

    private static function is_pro_user() {
        $is_pro = false;
        return (bool) apply_filters('ds_user_is_pro', $is_pro, get_current_user_id());
    }

    private static function trial_actor_key() {
        $uid = (int) get_current_user_id();
        if ($uid > 0) return 'u:' . $uid;
        $ip = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '0.0.0.0';
        return 'g:' . md5($ip . '|' . wp_salt('nonce'));
    }

    private static function trial_get_used() {
        $key = self::trial_actor_key();
        if (strpos($key, 'u:') === 0) {
            $uid = (int) substr($key, 2);
            return max(0, (int) get_user_meta($uid, '_ds_trial_used', true));
        }
        $v = get_transient('ds_trial_used_' . substr($key, 2));
        return max(0, (int) $v);
    }

    private static function trial_set_used($used) {
        $used = max(0, (int) $used);
        $key = self::trial_actor_key();
        if (strpos($key, 'u:') === 0) {
            $uid = (int) substr($key, 2);
            update_user_meta($uid, '_ds_trial_used', $used);
            return;
        }
        set_transient('ds_trial_used_' . substr($key, 2), $used, 30 * DAY_IN_SECONDS);
    }

    private static function protection_trial_remaining() {
        $cfg = self::protection_settings();
        $total = max(0, (int) $cfg['trial_total']);
        $used = self::trial_get_used();
        return max(0, $total - $used);
    }

    private static function remote_trial_check($action = 'save') {
        $remote = \DS_Plugin\Protection::remote_trial_check($action);
        if (is_wp_error($remote) || !is_array($remote)) {
            return null;
        }
        $enabled = isset($remote['enabled']) ? (bool) intval($remote['enabled']) : true;
        $total = max(0, (int) ($remote['trial_total'] ?? 0));
        $used = max(0, (int) ($remote['trial_used'] ?? 0));
        $remaining = max(0, (int) ($remote['trial_remaining'] ?? max(0, $total - $used)));
        return [
            'enabled' => $enabled,
            'trial_total' => $total,
            'trial_used' => $used,
            'trial_remaining' => $remaining,
        ];
    }

    private static function remote_trial_consume($action = 'save') {
        $remote = \DS_Plugin\Protection::remote_trial_consume($action);
        if (is_wp_error($remote) || !is_array($remote)) {
            return null;
        }
        $allowed = isset($remote['allowed']) ? (bool) intval($remote['allowed']) : true;
        $consumed = isset($remote['consumed']) ? (int) $remote['consumed'] : 0;
        $total = max(0, (int) ($remote['trial_total'] ?? 0));
        $used = max(0, (int) ($remote['trial_used'] ?? 0));
        $remaining = max(0, (int) ($remote['trial_remaining'] ?? max(0, $total - $used)));
        return [
            'allowed' => $allowed,
            'consumed' => $consumed,
            'trial_total' => $total,
            'trial_used' => $used,
            'trial_remaining' => $remaining,
        ];
    }

    public static function protection_check($request) {
        $nonce_ok = self::verify_rest_nonce($request);
        if (is_wp_error($nonce_ok)) return $nonce_ok;
        $cfg = self::protection_settings();
        $enabled = self::protection_enabled();
        $is_pro = self::is_pro_user();
        $remote = null;
        if ($enabled && !$is_pro) {
            $remote = self::remote_trial_check('save');
        }
        if (is_array($remote)) {
            $enabled = (bool) $remote['enabled'];
            $total = (int) $remote['trial_total'];
            $used = (int) $remote['trial_used'];
            $remaining = (int) $remote['trial_remaining'];
        } elseif (self::protection_remote_enforced() && $enabled && !$is_pro) {
            $total = 0;
            $used = 0;
            $remaining = 0;
        } else {
            $total = max(0, (int) $cfg['trial_total']);
            $used = self::trial_get_used();
            $remaining = max(0, $total - $used);
        }
        return rest_ensure_response([
            'enabled' => $enabled ? 1 : 0,
            'is_pro' => $is_pro ? 1 : 0,
            'trial_total' => $total,
            'trial_used' => $used,
            'trial_remaining' => $remaining,
        ]);
    }

    public static function protection_consume($request) {
        $nonce_ok = self::verify_rest_nonce($request);
        if (is_wp_error($nonce_ok)) return $nonce_ok;
        $action = sanitize_key((string) $request->get_param('action'));
        if ($action === '') $action = 'save';
        if (!self::protection_enabled() || self::is_pro_user()) {
            return rest_ensure_response([
                'allowed' => 1,
                'consumed' => 0,
                'trial_remaining' => self::protection_trial_remaining(),
            ]);
        }
        $remote = self::remote_trial_consume($action);
        if (is_array($remote)) {
            if (empty($remote['allowed'])) {
                return new \WP_Error('ds_protection_denied', __('Trial limit reached. Upgrade to Pro.', 'ds'), ['status' => 403]);
            }
            return rest_ensure_response([
                'allowed' => 1,
                'consumed' => (int) $remote['consumed'],
                'trial_remaining' => (int) $remote['trial_remaining'],
            ]);
        }
        if (self::protection_remote_enforced()) {
            return new \WP_Error('ds_protection_remote_unavailable', __('Protection service unavailable. Please try again shortly.', 'ds'), ['status' => 503]);
        }
        $cfg = self::protection_settings();
        $total = max(0, (int) $cfg['trial_total']);
        $used = self::trial_get_used();
        $remaining = max(0, $total - $used);
        if ($remaining <= 0) {
            return new \WP_Error('ds_protection_denied', __('Trial limit reached. Upgrade to Pro.', 'ds'), ['status' => 403]);
        }
        $used += 1;
        self::trial_set_used($used);
        return rest_ensure_response([
            'allowed' => 1,
            'consumed' => 1,
            'trial_remaining' => max(0, $total - $used),
        ]);
    }

    private static function verify_rest_nonce($request) {
        $nonce = $request->get_header('x_wp_nonce');
        if (!$nonce) {
            $nonce = $request->get_param('_wpnonce');
        }
        if (!$nonce || !wp_verify_nonce($nonce, 'wp_rest')) {
            return new \WP_Error('ds_forbidden', __('Invalid or missing nonce', 'ds'), ['status' => 403]);
        }
        return true;
    }

    private static function design_owner_id($design_id) {
        $uid = (int) get_post_meta($design_id, 'user_id', true);
        if ($uid > 0) return $uid;
        $post = get_post($design_id);
        if ($post && isset($post->post_author)) {
            return (int) $post->post_author;
        }
        return 0;
    }

    private static function design_visibility($design_id) {
        $v = (string) get_post_meta($design_id, 'visibility', true);
        return ($v === 'shared') ? 'shared' : 'private';
    }

    private static function user_can_access_design($design_id, $user_id) {
        $owner_id = self::design_owner_id($design_id);
        if ($user_id > 0 && $owner_id > 0 && $user_id === $owner_id) return true;
        return self::design_visibility($design_id) === 'shared';
    }

    private static function user_can_edit_design($design_id, $user_id, $guest_uuid = '') {
        $guest_uuid = trim((string) $guest_uuid);
        if ($user_id <= 0) {
            $owner_id = self::design_owner_id($design_id);
            $stored_uuid = (string) get_post_meta($design_id, 'design_uuid', true);
            return ($owner_id <= 0 && $guest_uuid !== '' && $stored_uuid !== '' && hash_equals($stored_uuid, $guest_uuid));
        }
        if (current_user_can('edit_post', $design_id)) return true;
        $owner_id = self::design_owner_id($design_id);
        return ($owner_id > 0 && $owner_id === $user_id);
    }

    public static function get_templates($request) {
        $args = [
            'post_type' => 'ds_template',
            'post_status' => 'publish',
            'numberposts' => -1,
        ];
        $posts = get_posts($args);
        $items = array_map(function($p) {
            return [
                'id' => $p->ID,
                'title' => get_the_title($p),
                'slug' => $p->post_name,
                'width_mm' => (float) get_post_meta($p->ID, 'width_mm', true),
                'height_mm' => (float) get_post_meta($p->ID, 'height_mm', true),
                'bleed_mm' => (float) get_post_meta($p->ID, 'bleed_mm', true),
                'dpi' => (int) get_post_meta($p->ID, 'dpi', true),
                'preview_type' => ((string) get_post_meta($p->ID, 'preview_type', true)) ?: 'mug',
                'preview_mode' => ((string) get_post_meta($p->ID, 'preview_mode', true)) ?: '',
                'zones' => self::decode_template_json_meta($p->ID, 'zones_json', []),
                'preview_config' => self::decode_template_json_meta($p->ID, 'preview_config_json', []),
            ];
        }, $posts);
        return rest_ensure_response($items);
    }

    public static function save_design($request) {
        // Basic anti-spam for guests and bots
        $nonce_ok = self::verify_rest_nonce($request);
        if (is_wp_error($nonce_ok)) return $nonce_ok;

        if (self::protection_enabled()) {
            $is_pro = self::is_pro_user();
            if (!$is_pro) {
                $cap = \DS_Plugin\Protection::remote_capability_issue('save_design');
                if (is_array($cap)) {
                    if (empty($cap['allowed'])) {
                        return new \WP_Error('ds_protection_denied', __('Action not allowed by license policy.', 'ds'), ['status' => 403]);
                    }
                } else {
                    $remote = self::remote_trial_check('save');
                    if (is_array($remote)) {
                        if (!empty($remote['enabled']) && (int) $remote['trial_remaining'] <= 0) {
                            return new \WP_Error('ds_protection_denied', __('Trial limit reached. Upgrade to Pro.', 'ds'), ['status' => 403]);
                        }
                    } elseif (self::protection_remote_enforced()) {
                        return new \WP_Error('ds_protection_remote_unavailable', __('Protection service unavailable. Please try again shortly.', 'ds'), ['status' => 503]);
                    } elseif (self::protection_trial_remaining() <= 0) {
                        return new \WP_Error('ds_protection_denied', __('Trial limit reached. Upgrade to Pro.', 'ds'), ['status' => 403]);
                    }
                }
            }
        }

        $opts = \DS_Plugin\Settings::get();
        if (!empty($opts['antispam_enabled'])) {
            $interval = max(0, (int) $opts['interval_sec']);
            $hour_cap = max(0, (int) $opts['hour_limit']);
            $ip = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '0.0.0.0';
            // Interval throttle
            if ($interval > 0) {
                $lock_key = 'ds_lock_' . md5($ip);
                if (get_transient($lock_key)) {
                    return new \WP_Error('ds_rate_limited', __('Too many requests, please wait a moment.', 'ds'), ['status' => 429]);
                }
                set_transient($lock_key, 1, $interval);
            }
            // Hourly cap
            if ($hour_cap > 0) {
                $count_key = 'ds_count_' . md5($ip);
                $count = (int) get_transient($count_key);
                if ($count >= $hour_cap) {
                    return new \WP_Error('ds_rate_limited_hour', __('Rate limit exceeded. Try again later.', 'ds'), ['status' => 429]);
                }
                set_transient($count_key, $count + 1, HOUR_IN_SECONDS);
            }
            // Origin check
            $origin = isset($_SERVER['HTTP_ORIGIN']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_ORIGIN'])) : '';
            if ($origin) {
                $site = wp_parse_url(home_url());
                $orig = wp_parse_url($origin);
                $ok = false;
                if (!empty($orig['host']) && !empty($site['host'])) {
                    if (strcasecmp($orig['host'], $site['host']) === 0) {
                        $ok = true;
                    } else {
                        $allowed = array_filter(array_map('trim', explode(',', (string) $opts['allowed_origins'])));
                        foreach ($allowed as $a) {
                            $ah = wp_parse_url($a, PHP_URL_HOST);
                            if ($ah && strcasecmp($ah, $orig['host']) === 0) { $ok = true; break; }
                        }
                    }
                }
                if (!$ok) {
                    return new \WP_Error('ds_bad_origin', __('Origin not allowed', 'ds'), ['status' => 403]);
                }
            }
        }

        $template_id = intval($request->get_param('templateId'));
        $png_data = $request->get_param('pngData');
        $layers_json = $request->get_param('layers');
        $design_name_raw = $request->get_param('designName');
        $zones_used_raw = $request->get_param('zonesUsed');
        $all_zones_raw = $request->get_param('allZones');
        $zone_png_data_raw = $request->get_param('zonePngData');
        $preview_png_data_raw = $request->get_param('previewPngData');

        if (!$template_id || !$png_data) {
            return new \WP_Error('ds_invalid_params', __('Missing parameters', 'ds'), ['status' => 400]);
        }

        if (strpos($png_data, 'data:image/png;base64,') !== 0) {
            return new \WP_Error('ds_invalid_png', __('Invalid PNG data URL', 'ds'), ['status' => 400]);
        }

        $upload_dir = wp_upload_dir();
        $subdir = '/design-studio/' . gmdate('Y/m');
        $dir = $upload_dir['basedir'] . $subdir;
        wp_mkdir_p($dir);

        $uuid = wp_generate_uuid4();
        $design_name = '';
        if (is_string($design_name_raw)) {
            $design_name = sanitize_text_field($design_name_raw);
            $design_name = preg_replace('/\s+/', ' ', trim($design_name));
        }
        if (!is_string($design_name) || $design_name === '') {
            $design_name = 'Design ' . $uuid;
        }
        $png_path = $dir . '/' . $uuid . '.png';
        $png_url = $upload_dir['baseurl'] . $subdir . '/' . $uuid . '.png';

        $raw = base64_decode(str_replace('data:image/png;base64,', '', $png_data));
        if ($raw === false) {
            return new \WP_Error('ds_decode_failed', __('Could not decode PNG', 'ds'), ['status' => 400]);
        }

        $ok = file_put_contents($png_path, $raw);
        if (!$ok) {
            return new \WP_Error('ds_write_failed', __('Could not write file', 'ds'), ['status' => 500]);
        }

        $json_path = null;
        $zones_manifest = [];
        $zones_layers_paths = [];
        $zones_orientations = [];
        $zones_placements = [];

        $zones_used = [];
        if (is_array($zones_used_raw)) {
            foreach ($zones_used_raw as $z) {
                $k = self::normalize_zone_id($z);
                if ($k !== '') $zones_used[$k] = $k;
            }
        } elseif (is_string($zones_used_raw) && $zones_used_raw !== '') {
            $decoded_z = json_decode($zones_used_raw, true);
            if (is_array($decoded_z)) {
                foreach ($decoded_z as $z) {
                    $k = self::normalize_zone_id($z);
                    if ($k !== '') $zones_used[$k] = $k;
                }
            }
        }

        $all_zones = [];
        if (is_array($all_zones_raw)) {
            foreach ($all_zones_raw as $z) {
                $k = self::normalize_zone_id($z);
                if ($k !== '') $all_zones[$k] = $k;
            }
        } elseif (is_string($all_zones_raw) && $all_zones_raw !== '') {
            $decoded_a = json_decode($all_zones_raw, true);
            if (is_array($decoded_a)) {
                foreach ($decoded_a as $z) {
                    $k = self::normalize_zone_id($z);
                    if ($k !== '') $all_zones[$k] = $k;
                }
            }
        }

        $is_zoned_payload = is_array($layers_json) && isset($layers_json['type']) && $layers_json['type'] === 'zoned' && isset($layers_json['zones']) && is_array($layers_json['zones']);
        if ($is_zoned_payload) {
            $all_zone_ids = [];
            $zone_file_keys = [];
            foreach ($layers_json['zones'] as $zid => $_snap) {
                $zone_id = self::normalize_zone_id($zid);
                if ($zone_id === '') continue;
                $all_zone_ids[$zone_id] = $zone_id;
                $zone_file_keys[$zone_id] = self::zone_file_key($zone_id);
            }
            if (empty($zones_used)) {
                foreach ($all_zone_ids as $zone_id) $zones_used[$zone_id] = $zone_id;
            }
            $orient_map = isset($layers_json['orientations']) && is_array($layers_json['orientations']) ? $layers_json['orientations'] : [];
            $zones_placements = isset($layers_json['placements']) && is_array($layers_json['placements']) ? $layers_json['placements'] : [];
            $active_zone_from_payload = isset($layers_json['active_zone']) ? self::normalize_zone_id($layers_json['active_zone']) : '';

            foreach ($all_zone_ids as $zone_id) {
                $is_active = isset($zones_used[$zone_id]);
                $zone_url = '';
                if ($is_active) {
                    $file_key = isset($zone_file_keys[$zone_id]) ? $zone_file_keys[$zone_id] : self::zone_file_key($zone_id);
                    $zone_json_path = $dir . '/' . $uuid . '-' . $file_key . '.json';
                    $zone_json_url = $upload_dir['baseurl'] . $subdir . '/' . $uuid . '-' . $file_key . '.json';
                    $zone_snapshot = self::read_zone_map_value($layers_json['zones'], $zone_id, null);
                    if (!is_array($zone_snapshot)) {
                        $zone_snapshot = ['version' => '5.3.0', 'objects' => []];
                    }
                    if (file_put_contents($zone_json_path, wp_json_encode($zone_snapshot)) !== false) {
                        $zone_url = $zone_json_url;
                        $zones_layers_paths[$zone_id] = $zone_json_url;
                    }
                }
                $zones_manifest[$zone_id] = [
                    'id' => $zone_id,
                    'active' => $is_active,
                    'layers_url' => $zone_url,
                ];
                $zone_orientation = (string) self::read_zone_map_value($orient_map, $zone_id, 'portrait');
                $zone_orientation = in_array($zone_orientation, ['portrait', 'landscape'], true) ? $zone_orientation : 'portrait';
                $zones_orientations[$zone_id] = $zone_orientation;
            }

            // Keep a single active zone for UI restore if provided.
            if ($active_zone_from_payload !== '' && !isset($zones_manifest[$active_zone_from_payload])) {
                $active_zone_from_payload = '';
            }
        } elseif ($layers_json) {
            $json_path = $dir . '/' . $uuid . '.json';
            file_put_contents($json_path, wp_json_encode($layers_json));
        }

        $zones_png_urls = [];
        if (is_array($zone_png_data_raw)) {
            foreach ($zone_png_data_raw as $zone_id => $zone_png_data) {
                $zone_key = self::normalize_zone_id($zone_id);
                if ($zone_key === '' || !is_string($zone_png_data)) continue;
                if (strpos($zone_png_data, 'data:image/png;base64,') !== 0) continue;
                $zone_raw = base64_decode(str_replace('data:image/png;base64,', '', $zone_png_data));
                if ($zone_raw === false) continue;
                $zone_file_key = self::zone_file_key($zone_key);
                $zone_path = $dir . '/' . $uuid . '-' . $zone_file_key . '.png';
                $zone_url = $upload_dir['baseurl'] . $subdir . '/' . $uuid . '-' . $zone_file_key . '.png';
                if (file_put_contents($zone_path, $zone_raw)) {
                    $zones_png_urls[$zone_key] = $zone_url;
                }
            }
        }

        $preview_images = [];
        if (is_array($preview_png_data_raw)) {
            $preview_idx = 0;
            foreach ($preview_png_data_raw as $pk => $row) {
                $preview_key = '';
                $preview_name = '';
                $preview_data = '';

                if (is_array($row)) {
                    $preview_key = sanitize_key((string) ($row['key'] ?? ''));
                    $preview_name = sanitize_text_field((string) ($row['name'] ?? ''));
                    $preview_data = (string) ($row['pngData'] ?? '');
                } elseif (is_string($row)) {
                    $preview_key = sanitize_key((string) $pk);
                    $preview_data = $row;
                }

                if ($preview_key === '') {
                    $preview_key = 'mockup_' . (string) ($preview_idx + 1);
                }
                if (isset($preview_images[$preview_key])) {
                    $base_preview_key = $preview_key;
                    $n = 2;
                    while (isset($preview_images[$preview_key])) {
                        $preview_key = $base_preview_key . '_' . $n;
                        $n++;
                    }
                }
                if ($preview_name === '') {
                    $preview_name = ucfirst(str_replace('_', ' ', $preview_key));
                }
                if ($preview_data === '' || strpos($preview_data, 'data:image/png;base64,') !== 0) {
                    $preview_idx++;
                    continue;
                }

                $preview_raw = base64_decode(str_replace('data:image/png;base64,', '', $preview_data));
                if ($preview_raw === false) {
                    $preview_idx++;
                    continue;
                }

                $preview_file = $uuid . '-preview-' . $preview_key . '.png';
                $preview_path = $dir . '/' . $preview_file;
                $preview_url = $upload_dir['baseurl'] . $subdir . '/' . $preview_file;
                if (file_put_contents($preview_path, $preview_raw)) {
                    $preview_images[$preview_key] = [
                        'name' => $preview_name,
                        'url' => $preview_url,
                        'index' => $preview_idx,
                    ];
                }
                $preview_idx++;
            }
        }

        $template_snapshot = self::build_template_zone_snapshot($template_id);

        // Create a Design post for admin tracking/printing
        $user_id = (int) get_current_user_id();
        $design_post_id = wp_insert_post([
            'post_type' => 'ds_design',
            'post_status' => 'publish',
            'post_title' => $design_name,
            'post_author' => $user_id,
        ]);
        if ($design_post_id && !is_wp_error($design_post_id)) {
            update_post_meta($design_post_id, 'template_id', $template_id);
            update_post_meta($design_post_id, 'design_uuid', $uuid);
            update_post_meta($design_post_id, 'png_url', $png_url);
            delete_post_meta($design_post_id, 'pdf_url');
            if ($json_path) {
                update_post_meta($design_post_id, 'layers_path', str_replace($upload_dir['basedir'], $upload_dir['baseurl'], $json_path));
            } else {
                // For zoned saves we sync layers per-zone; stale single-canvas layers_path
                // must be removed so loader can use zones_layers_paths reconstruction.
                delete_post_meta($design_post_id, 'layers_path');
            }
            if (!empty($zones_png_urls)) update_post_meta($design_post_id, 'zones_png_urls', $zones_png_urls);
            if (!empty($preview_images)) {
                update_post_meta($design_post_id, 'preview_images', $preview_images);
            } else {
                delete_post_meta($design_post_id, 'preview_images');
            }
            if (!empty($template_snapshot['zones']) && is_array($template_snapshot['zones'])) {
                update_post_meta($design_post_id, 'zones_attrs_snapshot', $template_snapshot['zones']);
            } else {
                delete_post_meta($design_post_id, 'zones_attrs_snapshot');
            }
            if (!empty($template_snapshot['mockups']) && is_array($template_snapshot['mockups'])) {
                update_post_meta($design_post_id, 'preview_mockups_snapshot', $template_snapshot['mockups']);
            } else {
                delete_post_meta($design_post_id, 'preview_mockups_snapshot');
            }
            if (empty($zones_used)) $zones_used = ['front_full'];
            update_post_meta($design_post_id, 'zones_used', array_values($zones_used));
            if (empty($all_zones)) $all_zones = $zones_used;
            if (!empty($zones_layers_paths)) {
                update_post_meta($design_post_id, 'zones_layers_paths', $zones_layers_paths);
            } else {
                delete_post_meta($design_post_id, 'zones_layers_paths');
            }
            if (!empty($zones_orientations)) {
                update_post_meta($design_post_id, 'zones_orientations', $zones_orientations);
            } else {
                delete_post_meta($design_post_id, 'zones_orientations');
            }
            if (!empty($zones_placements)) {
                update_post_meta($design_post_id, 'zones_placements', $zones_placements);
            } else {
                delete_post_meta($design_post_id, 'zones_placements');
            }
            if (empty($zones_manifest) && !empty($all_zones)) {
                foreach ($all_zones as $zid) {
                    $zones_manifest[$zid] = [
                        'id' => $zid,
                        'active' => isset($zones_used[$zid]),
                        'layers_url' => '',
                    ];
                }
            }
            if (!empty($zones_manifest)) {
                update_post_meta($design_post_id, 'zones_manifest', $zones_manifest);
                update_post_meta($design_post_id, 'zones_bundle_id', $uuid);
                $active_zone = '';
                if (is_array($layers_json) && isset($layers_json['active_zone'])) {
                    $active_zone = self::normalize_zone_id($layers_json['active_zone']);
                }
                if ($active_zone !== '' && isset($zones_manifest[$active_zone])) {
                    update_post_meta($design_post_id, 'zones_active_zone', $active_zone);
                }
            } else {
                delete_post_meta($design_post_id, 'zones_manifest');
                delete_post_meta($design_post_id, 'zones_bundle_id');
            }
            update_post_meta($design_post_id, 'user_id', $user_id > 0 ? $user_id : 0);
            update_post_meta($design_post_id, 'visibility', 'private');
        }

        // Successful save consumes one trial action when protection is enabled.
        if (self::protection_enabled() && !self::is_pro_user()) {
            $remoteConsumed = self::remote_trial_consume('save');
            if (!is_array($remoteConsumed) && !self::protection_remote_enforced()) {
                self::trial_set_used(self::trial_get_used() + 1);
            }
        }

        return rest_ensure_response([
            'id' => $uuid,
            'png' => $png_url,
            'zones_png' => $zones_png_urls,
            'zones_used' => array_values($zones_used),
            'preview_images' => $preview_images,
            'design_post_id' => $design_post_id,
            'design_uuid' => $uuid,
            'design_title' => $design_post_id ? get_the_title($design_post_id) : $design_name,
        ]);
    }

    public static function get_design($request) {
        $nonce_ok = self::verify_rest_nonce($request);
        if (is_wp_error($nonce_ok)) return $nonce_ok;

        $design_id = (int) $request['id'];
        if (!$design_id) {
            return new \WP_Error('ds_invalid_params', __('Invalid design ID', 'ds'), ['status' => 400]);
        }

        $post = get_post($design_id);
        if (!$post || $post->post_type !== 'ds_design') {
            return new \WP_Error('ds_not_found', __('Design not found', 'ds'), ['status' => 404]);
        }

        $user_id = (int) get_current_user_id();
        if (!self::user_can_access_design($design_id, $user_id)) {
            return new \WP_Error('ds_forbidden_design', __('You do not have access to this design', 'ds'), ['status' => 403]);
        }

        $template_id = (int) get_post_meta($design_id, 'template_id', true);
        $png_url = (string) get_post_meta($design_id, 'png_url', true);
        $layers_url = (string) get_post_meta($design_id, 'layers_path', true);
        $visibility = self::design_visibility($design_id);
        $zones_used = get_post_meta($design_id, 'zones_used', true);
        if (!is_array($zones_used) || empty($zones_used)) $zones_used = ['front_full'];
        $zones_png_urls = get_post_meta($design_id, 'zones_png_urls', true);
        if (!is_array($zones_png_urls)) $zones_png_urls = [];
        $preview_images = get_post_meta($design_id, 'preview_images', true);
        if (!is_array($preview_images)) $preview_images = [];
        $layers = null;
        $zones_attrs_snapshot = get_post_meta($design_id, 'zones_attrs_snapshot', true);
        if (!is_array($zones_attrs_snapshot)) $zones_attrs_snapshot = [];
        $preview_mockups_snapshot = get_post_meta($design_id, 'preview_mockups_snapshot', true);
        if (!is_array($preview_mockups_snapshot)) $preview_mockups_snapshot = [];

        if ($layers_url) {
            $upload_dir = wp_upload_dir();
            $layers_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $layers_url);
            if (file_exists($layers_path)) {
                $raw = file_get_contents($layers_path);
                if ($raw !== false && $raw !== '') {
                    $decoded = json_decode($raw, true);
                    if (json_last_error() === JSON_ERROR_NONE) {
                        $layers = $decoded;
                    }
                }
            }
        }

        if ($layers === null) {
            $zones_manifest = get_post_meta($design_id, 'zones_manifest', true);
            if (is_array($zones_manifest) && !empty($zones_manifest)) {
                $upload_dir = wp_upload_dir();
                $zones_layers_paths_meta = get_post_meta($design_id, 'zones_layers_paths', true);
                if (!is_array($zones_layers_paths_meta)) $zones_layers_paths_meta = [];
                $zones_payload = [];
                foreach ($zones_manifest as $row) {
                    if (!is_array($row)) continue;
                    $zone_id = self::normalize_zone_id($row['id'] ?? '');
                    if ($zone_id === '') continue;
                    $zone_url = isset($row['layers_url']) ? (string) $row['layers_url'] : '';
                    if ($zone_url === '') {
                        $fallback_url = self::read_zone_map_value($zones_layers_paths_meta, $zone_id, '');
                        if (is_string($fallback_url) && $fallback_url !== '') {
                            $zone_url = $fallback_url;
                        }
                    }
                    if ($zone_url !== '') {
                        $zpath = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $zone_url);
                        if (file_exists($zpath)) {
                            $raw = file_get_contents($zpath);
                            if ($raw !== false && $raw !== '') {
                                $decoded = json_decode($raw, true);
                                if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                                    $zones_payload[$zone_id] = $decoded;
                                    continue;
                                }
                            }
                        }
                    }
                }
                if (!empty($zones_payload)) {
                    $orientations = get_post_meta($design_id, 'zones_orientations', true);
                    if (!is_array($orientations)) $orientations = [];
                    $active_zone = (string) get_post_meta($design_id, 'zones_active_zone', true);
                    if ($active_zone === '' || !isset($zones_payload[$active_zone])) {
                        $keys = array_keys($zones_payload);
                        $active_zone = !empty($keys) ? (string) $keys[0] : 'front_full';
                    }
                    $placements = get_post_meta($design_id, 'zones_placements', true);
                    if (!is_array($placements)) $placements = [];
                    $layers = [
                        'type' => 'zoned',
                        'active_zone' => $active_zone,
                        'zones' => $zones_payload,
                        'orientations' => $orientations,
                        'placements' => $placements,
                    ];
                    $active_used = [];
                    foreach ($zones_manifest as $row) {
                        if (!is_array($row)) continue;
                        $zone_id = self::normalize_zone_id($row['id'] ?? '');
                        if ($zone_id === '') continue;
                        if (!isset($zones_payload[$zone_id])) continue;
                        if (!empty($row['active'])) $active_used[$zone_id] = $zone_id;
                    }
                    if (!empty($active_used)) $zones_used = array_values($active_used);
                }
            }
        }

        if ($layers === null) {
            $zones_layers_paths = get_post_meta($design_id, 'zones_layers_paths', true);
            if (!is_array($zones_layers_paths)) $zones_layers_paths = [];
            if (!empty($zones_layers_paths)) {
                $upload_dir = wp_upload_dir();
                $zones_payload = [];
                foreach ($zones_layers_paths as $zid => $zurl) {
                    $zone_id = self::normalize_zone_id($zid);
                    if ($zone_id === '' || !is_string($zurl) || $zurl === '') continue;
                    $zpath = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $zurl);
                    if (!file_exists($zpath)) continue;
                    $raw = file_get_contents($zpath);
                    if ($raw === false || $raw === '') continue;
                    $decoded = json_decode($raw, true);
                    if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                        $zones_payload[$zone_id] = $decoded;
                    }
                }
                if (!empty($zones_payload)) {
                    $orientations = get_post_meta($design_id, 'zones_orientations', true);
                    if (!is_array($orientations)) $orientations = [];
                    $active_zone = (string) get_post_meta($design_id, 'zones_active_zone', true);
                    if ($active_zone === '' || !isset($zones_payload[$active_zone])) {
                        $keys = array_keys($zones_payload);
                        $active_zone = !empty($keys) ? (string) $keys[0] : 'front_full';
                    }
                    $placements = get_post_meta($design_id, 'zones_placements', true);
                    if (!is_array($placements)) $placements = [];
                    $layers = [
                        'type' => 'zoned',
                        'active_zone' => $active_zone,
                        'zones' => $zones_payload,
                        'orientations' => $orientations,
                        'placements' => $placements,
                    ];
                }
            }
        }

        return rest_ensure_response([
            'design_post_id' => $design_id,
            'title' => get_the_title($post),
            'template_id' => $template_id,
            'png' => $png_url,
            'visibility' => $visibility,
            'zones_used' => array_values($zones_used),
            'zones_png' => $zones_png_urls,
            'preview_images' => $preview_images,
            'zones_attrs' => $zones_attrs_snapshot,
            'preview_mockups' => $preview_mockups_snapshot,
            'layers_url' => $layers_url,
            'layers' => $layers,
        ]);
    }

    public static function get_my_designs($request) {
        $nonce_ok = self::verify_rest_nonce($request);
        if (is_wp_error($nonce_ok)) return $nonce_ok;

        $user_id = (int) get_current_user_id();
        if ($user_id <= 0) {
            return new \WP_Error('ds_forbidden', __('Login required', 'ds'), ['status' => 401]);
        }

        $posts_a = get_posts([
            'post_type' => 'ds_design',
            'post_status' => 'publish',
            'numberposts' => 200,
            'orderby' => 'date',
            'order' => 'DESC',
            'author' => $user_id,
        ]);
        $posts_b = get_posts([
            'post_type' => 'ds_design',
            'post_status' => 'publish',
            'numberposts' => 200,
            'orderby' => 'date',
            'order' => 'DESC',
            'meta_key' => 'user_id',
            'meta_value' => $user_id,
        ]);

        $by_id = [];
        foreach (array_merge($posts_a, $posts_b) as $p) {
            $by_id[(int) $p->ID] = $p;
        }
        $posts = array_values($by_id);
        usort($posts, function($l, $r) {
            return strcmp($r->post_date_gmt ?: $r->post_date, $l->post_date_gmt ?: $l->post_date);
        });

        $items = array_map(function($p) {
            $template_id = (int) get_post_meta($p->ID, 'template_id', true);
            return [
                'id' => (int) $p->ID,
                'title' => get_the_title($p),
                'template_id' => $template_id,
                'template_title' => $template_id ? get_the_title($template_id) : '',
                'png' => (string) get_post_meta($p->ID, 'png_url', true),
                'zones_used' => (function($id){
                    $z = get_post_meta($id, 'zones_used', true);
                    if (!is_array($z) || empty($z)) $z = ['front_full'];
                    return array_values($z);
                })($p->ID),
                'zones_png' => (function($id){
                    $z = get_post_meta($id, 'zones_png_urls', true);
                    return is_array($z) ? $z : [];
                })($p->ID),
                'preview_images' => (function($id){
                    $z = get_post_meta($id, 'preview_images', true);
                    return is_array($z) ? $z : [];
                })($p->ID),
                'visibility' => self::design_visibility($p->ID),
                'created_at' => mysql2date('c', $p->post_date_gmt ?: $p->post_date, false),
            ];
        }, $posts);

        return rest_ensure_response([
            'items' => $items,
        ]);
    }

    public static function get_design_library($request) {
        $nonce_ok = self::verify_rest_nonce($request);
        if (is_wp_error($nonce_ok)) return $nonce_ok;

        $user_id = (int) get_current_user_id();
        $by_id = [];

        if ($user_id > 0) {
            $posts_a = get_posts([
                'post_type' => 'ds_design',
                'post_status' => 'publish',
                'numberposts' => 200,
                'orderby' => 'date',
                'order' => 'DESC',
                'author' => $user_id,
            ]);
            $posts_b = get_posts([
                'post_type' => 'ds_design',
                'post_status' => 'publish',
                'numberposts' => 200,
                'orderby' => 'date',
                'order' => 'DESC',
                'meta_key' => 'user_id',
                'meta_value' => $user_id,
            ]);
            foreach (array_merge($posts_a, $posts_b) as $p) {
                $by_id[(int) $p->ID] = $p;
            }
        }

        $shared_posts = get_posts([
            'post_type' => 'ds_design',
            'post_status' => 'publish',
            'numberposts' => 300,
            'orderby' => 'date',
            'order' => 'DESC',
            'meta_key' => 'visibility',
            'meta_value' => 'shared',
        ]);
        foreach ($shared_posts as $p) {
            $by_id[(int) $p->ID] = $p;
        }

        $posts = array_values($by_id);
        usort($posts, function($l, $r) {
            return strcmp($r->post_date_gmt ?: $r->post_date, $l->post_date_gmt ?: $l->post_date);
        });

        $items = array_map(function($p) use ($user_id) {
            $template_id = (int) get_post_meta($p->ID, 'template_id', true);
            $owner_id = self::design_owner_id((int) $p->ID);
            $is_owner = ($user_id > 0 && $owner_id > 0 && $owner_id === $user_id);
            return [
                'id' => (int) $p->ID,
                'title' => get_the_title($p),
                'template_id' => $template_id,
                'template_title' => $template_id ? get_the_title($template_id) : '',
                'png' => (string) get_post_meta($p->ID, 'png_url', true),
                'zones_used' => (function($id){
                    $z = get_post_meta($id, 'zones_used', true);
                    if (!is_array($z) || empty($z)) $z = ['front_full'];
                    return array_values($z);
                })($p->ID),
                'zones_png' => (function($id){
                    $z = get_post_meta($id, 'zones_png_urls', true);
                    return is_array($z) ? $z : [];
                })($p->ID),
                'preview_images' => (function($id){
                    $z = get_post_meta($id, 'preview_images', true);
                    return is_array($z) ? $z : [];
                })($p->ID),
                'visibility' => self::design_visibility($p->ID),
                'is_owner' => $is_owner ? 1 : 0,
                'created_at' => mysql2date('c', $p->post_date_gmt ?: $p->post_date, false),
            ];
        }, $posts);

        return rest_ensure_response([
            'items' => $items,
        ]);
    }

    public static function get_products_by_template($request) {
        if (!class_exists('WooCommerce')) {
            return rest_ensure_response(['items' => []]);
        }
        $template_id = isset($request['id']) ? (int) $request['id'] : 0;
        if ($template_id <= 0) {
            return rest_ensure_response(['items' => []]);
        }

        $products = get_posts([
            'post_type' => 'product',
            'post_status' => 'publish',
            'numberposts' => 200,
            'meta_query' => [
                [
                    'key' => \DS_Plugin\Woo::PRODUCT_META_ENABLE,
                    'value' => '1',
                    'compare' => '=',
                ],
            ],
        ]);

        $items = [];
        foreach ($products as $p) {
            $id = (int) $p->ID;
            if (!\DS_Plugin\Woo::product_uses_template($id, $template_id)) continue;
            $thumb = get_the_post_thumbnail_url($id, 'medium');
            $price_html = '';
            if (function_exists('wc_get_product')) {
                $wc_product = wc_get_product($id);
                if ($wc_product && is_a($wc_product, '\WC_Product')) {
                    $price_html = (string) $wc_product->get_price_html();
                }
            }
            $items[] = [
                'id' => $id,
                'title' => get_the_title($id),
                'url' => get_permalink($id),
                'thumb' => $thumb ? (string) $thumb : '',
                'price_html' => $price_html,
            ];
        }

        return rest_ensure_response(['items' => $items]);
    }

    public static function save_design_zone_layer($request) {
        $nonce_ok = self::verify_rest_nonce($request);
        if (is_wp_error($nonce_ok)) return $nonce_ok;

        $design_id = isset($request['id']) ? (int) $request['id'] : 0;
        if ($design_id <= 0) {
            return new \WP_Error('ds_invalid_params', __('Invalid design ID', 'ds'), ['status' => 400]);
        }
        $post = get_post($design_id);
        if (!$post || $post->post_type !== 'ds_design') {
            return new \WP_Error('ds_not_found', __('Design not found', 'ds'), ['status' => 404]);
        }

        $user_id = (int) get_current_user_id();
        $guest_uuid = sanitize_text_field((string) $request->get_param('designUuid'));
        if (!self::user_can_edit_design($design_id, $user_id, $guest_uuid)) {
            return new \WP_Error('ds_forbidden_design', __('You cannot edit this design', 'ds'), ['status' => 403]);
        }

        $zone_id = self::normalize_zone_id($request->get_param('zoneId'));
        $snapshot = $request->get_param('snapshot');
        $orientation = sanitize_key((string) $request->get_param('orientation'));
        $active_zone = self::normalize_zone_id($request->get_param('activeZone'));
        $zone_active = (bool) $request->get_param('zoneActive');
        $all_zones_raw = $request->get_param('allZones');
        $placements = $request->get_param('placements');
        $png_data = $request->get_param('pngData');
        $zones_used_raw = $request->get_param('zonesUsed');
        $clear_first = (bool) $request->get_param('clearFirst');

        if ($zone_id === '' || !is_array($snapshot)) {
            return new \WP_Error('ds_invalid_params', __('Invalid zone payload', 'ds'), ['status' => 400]);
        }
        if (!in_array($orientation, ['portrait', 'landscape'], true)) $orientation = 'portrait';
        if (!is_array($placements)) $placements = [];

        $png_url = (string) get_post_meta($design_id, 'png_url', true);
        if ($png_url === '') {
            return new \WP_Error('ds_missing_base', __('Base design file missing', 'ds'), ['status' => 500]);
        }

        $upload_dir = wp_upload_dir();
        $base_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $png_url);
        $base_dir = dirname($base_path);
        if (!is_dir($base_dir)) {
            wp_mkdir_p($base_dir);
        }
        $base_file = basename($base_path);
        $uuid = preg_replace('/\.png$/i', '', $base_file);
        if (!$uuid) $uuid = 'design-' . $design_id;

        $zone_file = $uuid . '-' . self::zone_file_key($zone_id) . '.json';
        $zone_path = trailingslashit($base_dir) . $zone_file;
        $zone_url = trailingslashit(dirname($png_url)) . $zone_file;

        $ok = file_put_contents($zone_path, wp_json_encode($snapshot));
        if ($ok === false) {
            return new \WP_Error('ds_write_failed', __('Could not write zone data', 'ds'), ['status' => 500]);
        }

        $paths = get_post_meta($design_id, 'zones_layers_paths', true);
        if (!is_array($paths) || $clear_first) $paths = [];
        $paths[$zone_id] = $zone_url;
        update_post_meta($design_id, 'zones_layers_paths', $paths);

        $zone_png_urls = get_post_meta($design_id, 'zones_png_urls', true);
        if (!is_array($zone_png_urls) || $clear_first) $zone_png_urls = [];
        if (is_string($png_data) && strpos($png_data, 'data:image/png;base64,') === 0) {
            $png_raw = base64_decode(str_replace('data:image/png;base64,', '', $png_data));
            if ($png_raw !== false) {
                $zone_png_file = $uuid . '-' . self::zone_file_key($zone_id) . '.png';
                $zone_png_path = trailingslashit($base_dir) . $zone_png_file;
                $zone_png_url = trailingslashit(dirname($png_url)) . $zone_png_file;
                if (file_put_contents($zone_png_path, $png_raw)) {
                    $zone_png_urls[$zone_id] = $zone_png_url;
                }
            }
        }
        update_post_meta($design_id, 'zones_png_urls', $zone_png_urls);

        $orient_map = get_post_meta($design_id, 'zones_orientations', true);
        if (!is_array($orient_map) || $clear_first) $orient_map = [];
        $orient_map[$zone_id] = $orientation;
        update_post_meta($design_id, 'zones_orientations', $orient_map);

        if ($active_zone !== '') {
            update_post_meta($design_id, 'zones_active_zone', $active_zone);
        }
        update_post_meta($design_id, 'zones_placements', $placements);

        $all_zones = [];
        if (is_array($all_zones_raw)) {
            foreach ($all_zones_raw as $z) {
                $k = self::normalize_zone_id($z);
                if ($k !== '') $all_zones[$k] = $k;
            }
        } elseif (is_string($all_zones_raw) && $all_zones_raw !== '') {
            $decoded_a = json_decode($all_zones_raw, true);
            if (is_array($decoded_a)) {
                foreach ($decoded_a as $z) {
                    $k = self::normalize_zone_id($z);
                    if ($k !== '') $all_zones[$k] = $k;
                }
            }
        }
        if (empty($all_zones)) {
            $all_zones = [$zone_id => $zone_id];
        }

        $manifest = get_post_meta($design_id, 'zones_manifest', true);
        if (!is_array($manifest) || $clear_first) $manifest = [];
        foreach ($all_zones as $zid) {
            if (!isset($manifest[$zid]) || !is_array($manifest[$zid])) {
                $manifest[$zid] = [
                    'id' => $zid,
                    'active' => false,
                    'layers_url' => '',
                ];
            }
        }
        $manifest[$zone_id]['id'] = $zone_id;
        $manifest[$zone_id]['active'] = $zone_active;
        $manifest[$zone_id]['layers_url'] = $zone_url;
        update_post_meta($design_id, 'zones_manifest', $manifest);

        $zones_used = [];
        if (is_array($zones_used_raw)) {
            foreach ($zones_used_raw as $z) {
                $k = self::normalize_zone_id($z);
                if ($k !== '') $zones_used[$k] = $k;
            }
        }
        if (empty($zones_used)) $zones_used = [$zone_id];
        update_post_meta($design_id, 'zones_used', array_values($zones_used));

        return rest_ensure_response([
            'ok' => true,
            'design_post_id' => $design_id,
            'zone_id' => $zone_id,
            'zone_active' => $zone_active,
            'zone_url' => $zone_url,
            'zone_png' => isset($zone_png_urls[$zone_id]) ? $zone_png_urls[$zone_id] : '',
        ]);
    }

    public static function update_design_visibility($request) {
        $nonce_ok = self::verify_rest_nonce($request);
        if (is_wp_error($nonce_ok)) return $nonce_ok;

        $user_id = (int) get_current_user_id();
        if ($user_id <= 0) {
            return new \WP_Error('ds_forbidden', __('Login required', 'ds'), ['status' => 401]);
        }

        $design_id = (int) $request['id'];
        if ($design_id <= 0) {
            return new \WP_Error('ds_invalid_params', __('Invalid design ID', 'ds'), ['status' => 400]);
        }
        $post = get_post($design_id);
        if (!$post || $post->post_type !== 'ds_design') {
            return new \WP_Error('ds_not_found', __('Design not found', 'ds'), ['status' => 404]);
        }

        $owner_id = self::design_owner_id($design_id);
        if ($owner_id <= 0 || $owner_id !== $user_id) {
            return new \WP_Error('ds_forbidden_design', __('Only the owner can change visibility', 'ds'), ['status' => 403]);
        }

        $visibility = sanitize_text_field((string) $request->get_param('visibility'));
        if (!in_array($visibility, ['private', 'shared'], true)) {
            return new \WP_Error('ds_invalid_visibility', __('Invalid visibility', 'ds'), ['status' => 400]);
        }

        update_post_meta($design_id, 'visibility', $visibility);

        return rest_ensure_response([
            'id' => $design_id,
            'visibility' => $visibility,
        ]);
    }

    public static function remove_background($request) {
        $nonce_ok = self::verify_rest_nonce($request);
        if (is_wp_error($nonce_ok)) return $nonce_ok;

        $opts = \DS_Plugin\Settings::get();
        $api_key = isset($opts['removebg_api_key']) ? trim((string) $opts['removebg_api_key']) : '';
        if ($api_key === '') {
            return new \WP_Error('ds_ai_no_key', __('AI Background Remove is not configured. Missing API key.', 'ds'), ['status' => 400]);
        }

        $image_data = (string) $request->get_param('imageData');
        if ($image_data === '' || strpos($image_data, 'data:image/') !== 0) {
            return new \WP_Error('ds_ai_invalid_image', __('Invalid image data URL.', 'ds'), ['status' => 400]);
        }

        $comma = strpos($image_data, ',');
        if ($comma === false) {
            return new \WP_Error('ds_ai_invalid_image', __('Invalid image payload.', 'ds'), ['status' => 400]);
        }

        $base64 = substr($image_data, $comma + 1);
        $raw = base64_decode($base64, true);
        if ($raw === false || $raw === '') {
            return new \WP_Error('ds_ai_decode_failed', __('Could not decode image.', 'ds'), ['status' => 400]);
        }
        if (strlen($raw) > 12 * 1024 * 1024) {
            return new \WP_Error('ds_ai_too_large', __('Image is too large for processing.', 'ds'), ['status' => 413]);
        }

        $response = wp_remote_post('https://api.remove.bg/v1.0/removebg', [
            'timeout' => 60,
            'headers' => [
                'X-Api-Key' => $api_key,
            ],
            'body' => [
                'image_file_b64' => base64_encode($raw),
                'size' => 'auto',
            ],
        ]);

        if (is_wp_error($response)) {
            return new \WP_Error('ds_ai_request_failed', __('Background remove request failed.', 'ds'), ['status' => 502]);
        }

        $status = (int) wp_remote_retrieve_response_code($response);
        $body = wp_remote_retrieve_body($response);
        if ($status < 200 || $status >= 300 || !$body) {
            $error_message = __('Background remove failed.', 'ds');
            $decoded = json_decode((string) $body, true);
            if (is_array($decoded) && !empty($decoded['errors'][0]['title'])) {
                $error_message = sanitize_text_field((string) $decoded['errors'][0]['title']);
            } elseif (is_string($body) && $body !== '') {
                $error_message = sanitize_text_field($body);
            }
            return new \WP_Error('ds_ai_upstream_error', $error_message, ['status' => 502]);
        }

        $output_data_url = 'data:image/png;base64,' . base64_encode($body);
        return rest_ensure_response([
            'imageData' => $output_data_url,
        ]);
    }
}
