<?php
if (!defined('ABSPATH')) exit;

/**
 * Pinery Flow — Core Pipeline
 *
 * Scrapes Amazon Best Sellers, generates lifestyle images via AI,
 * writes product copy, and creates WordPress posts with affiliate metadata.
 *
 * Supported AI providers: xAI (Grok), OpenAI (GPT/DALL-E), Google Gemini
 */
class Pinery_Flow_Pipeline {

    /**
     * Get AI provider configuration.
     */
    public static function get_provider_config() {
        $provider = get_option('pinery_flow_ai_provider', 'xai');
        $providers = [
            'xai' => [
                'base_url'    => 'https://api.x.ai/v1',
                'text_model'  => 'grok-3-mini',
                'image_model' => 'grok-imagine-image',
                'format'      => 'openai',
                'label'       => 'xAI',
                'key_link'    => 'https://console.x.ai',
            ],
            'openai' => [
                'base_url'    => 'https://api.openai.com/v1',
                'text_model'  => 'gpt-4o-mini',
                'image_model' => 'gpt-image-2',
                'format'      => 'openai',
                'label'       => 'OpenAI',
                'key_link'    => 'https://platform.openai.com/api-keys',
            ],
            'gemini' => [
                'base_url'    => 'https://generativelanguage.googleapis.com/v1beta',
                'text_model'  => 'gemini-2.5-flash',
                'image_model' => 'gemini-2.5-flash-image',
                'format'      => 'gemini',
                'label'       => 'Google Gemini',
                'key_link'    => 'https://aistudio.google.com/apikey',
            ],
        ];
        return isset($providers[$provider]) ? $providers[$provider] : $providers['xai'];
    }

    /**
     * Get the API key, with backward compat migration from old option name.
     */
    public static function get_api_key() {
        $key = get_option('pinery_flow_api_key', '');
        if (empty($key)) {
            // Migrate from old xAI-specific key
            $old_key = get_option('pinery_flow_xai_api_key', '');
            if (!empty($old_key)) {
                update_option('pinery_flow_api_key', $old_key);
                return $old_key;
            }
        }
        return $key;
    }

    /**
     * Write a detailed debug log entry for the Logs page.
     */
    public static function debug_log($level, $step, $message, $context = '') {
        $logs = get_option('pinery_flow_detail_logs', []);
        $logs[] = [
            'time'    => current_time('mysql'),
            'level'   => $level,
            'step'    => $step,
            'message' => $message,
            'context' => $context ? (is_array($context) ? json_encode($context, JSON_UNESCAPED_UNICODE) : $context) : '',
        ];
        if (count($logs) > 200) $logs = array_slice($logs, -200);
        update_option('pinery_flow_detail_logs', $logs);
    }

    /**
     * Run the full pipeline and return created post IDs.
     */
    public static function run($products_per_run = null, $categories = null) {
        // License gate — block pipeline execution without a valid license
        if (function_exists('pinery_flow_has_license') && !pinery_flow_has_license()) {
            return new WP_Error('no_license', __('A valid Pinery Flow license is required to run the pipeline.', 'pinery-flow'));
        }

        @set_time_limit(300);
        if (!$products_per_run) {
            $products_per_run = (int) get_option('pinery_flow_products_per_run', 5);
        }
        if (!$categories) {
            $categories = self::resolve_categories();
        }


        // Clear old detail logs so each run starts fresh
        update_option('pinery_flow_detail_logs', []);

        $config  = self::get_provider_config();
        $api_key = self::get_api_key();

        self::debug_log('info', 'Start', sprintf('Pipeline starting — provider=%s, text_model=%s, image_model=%s, products=%d, categories=%d', get_option('pinery_flow_ai_provider', 'xai'), $config['text_model'], $config['image_model'], $products_per_run, count($categories)));
        if (!empty($categories)) {
            $cat_names = array_map(function($s) { return str_replace('-', ' ', $s); }, array_keys($categories));
            self::debug_log('info', 'Start', 'Categories: ' . implode(', ', $cat_names));
        }
        if (empty($api_key)) {
            self::debug_log('error', 'Config', 'No API key configured');
            return new WP_Error('no_api_key', sprintf(__('API key is required. Get one from %s', 'pinery-flow'), $config['key_link']));
        }

        if (empty($categories)) {
            self::debug_log('error', 'Config', 'No categories selected');
            return new WP_Error('no_categories', __('No Amazon categories selected. Please configure categories in settings.', 'pinery-flow'));
        }

        // 1. Scrape
        self::debug_log('info', 'Scraping', sprintf('Scraping %d categories...', count($categories)));
        $all_products = self::scrape_all($categories);
        if (empty($all_products)) {
            self::debug_log('error', 'Scraping', 'No products scraped from any category');
            return new WP_Error('no_products', __('No products scraped.', 'pinery-flow'));
        }
        self::debug_log('info', 'Scraping', sprintf('Total scraped: %d products', count($all_products)));

        // 2. Filter already published
        $new_products = self::filter_new($all_products);
        $skipped = count($all_products) - count($new_products);
        if (empty($new_products)) {
            self::debug_log('info', 'Filter', sprintf('All %d products already published. Skipped: %d', count($all_products), $skipped));
            return new WP_Error('no_new', __('All products already published.', 'pinery-flow'));
        }
        self::debug_log('info', 'Filter', sprintf('%d new, %d already published', count($new_products), $skipped));

        // 3. Select diverse products across categories
        $selected = self::select_diverse($new_products, $products_per_run);
        self::debug_log('info', 'Select', sprintf('Selected %d products across categories', count($selected)));

        // 4. Process each product
        $post_ids = [];
        $published = get_option('pinery_flow_published_products', []);
        $tag = get_option('pinery_flow_amazon_tag', '');

        foreach ($selected as $product) {
            $title_short = mb_substr($product['title'], 0, 50);
            self::debug_log('info', 'AI Copy', sprintf('Generating copy: %s', $title_short));
            self::debug_log('info', 'AI Image', sprintf('Generating image: %s', $title_short));

            $post_id = self::process_product($product, $api_key, $tag);
            if ($post_id && !is_wp_error($post_id)) {
                $post_ids[] = $post_id;
                $published[] = self::product_signature($product);
                self::debug_log('info', 'Post', sprintf('Created post #%d: %s', $post_id, $title_short));
            } else {
                $err_msg = is_wp_error($post_id) ? $post_id->get_error_message() : 'Unknown error';
                self::debug_log('error', 'Post', sprintf('Failed: %s — %s', $title_short, $err_msg));
            }
        }

        // 5. Mark as published
        update_option('pinery_flow_published_products', array_unique($published));
        update_option('pinery_flow_last_run', current_time('mysql'));

        // 6. Clean up temp images
        self::cleanup_temp_images();

        self::debug_log('info', 'Done', sprintf('Pipeline complete: %d posts created', count($post_ids)));
        return $post_ids;
    }

    // ==================== SCRAPING ====================

    /**
     * Full Amazon Best Sellers department hierarchy.
     * Each department has subcategories with real Amazon Best Sellers URLs.
     * For departments without pre-scraped subcategories, scrape the department URL directly.
     */
    public static function amazon_departments() {
        return [
            'fashion' => [
                'name' => 'Clothing, Shoes & Jewelry',
                'url'  => 'https://www.amazon.com/Best-Sellers-Clothing-Shoes-Jewelry/zgbs/fashion/',
                'subcategories' => [
                    'women'           => ['name' => 'Women',           'url' => 'https://www.amazon.com/Best-Sellers-Clothing-Shoes-Jewelry-Womens-Fashion/zgbs/fashion/7147440011'],
                    'men'             => ['name' => 'Men',             'url' => 'https://www.amazon.com/Best-Sellers-Clothing-Shoes-Jewelry-Mens-Fashion/zgbs/fashion/7147441011'],
                    'girls'           => ['name' => 'Girls',           'url' => 'https://www.amazon.com/Best-Sellers-Clothing-Shoes-Jewelry-Girls-Fashion/zgbs/fashion/7147442011'],
                    'boys'            => ['name' => 'Boys',            'url' => 'https://www.amazon.com/Best-Sellers-Clothing-Shoes-Jewelry-Boys-Fashion/zgbs/fashion/7147443011'],
                    'baby'            => ['name' => 'Baby',            'url' => 'https://www.amazon.com/Best-Sellers-Clothing-Shoes-Jewelry-Baby-Clothing-Shoes/zgbs/fashion/7147444011'],
                    'luggage'         => ['name' => 'Luggage & Travel Gear', 'url' => 'https://www.amazon.com/Best-Sellers-Clothing-Shoes-Jewelry-Luggage-Travel-Gear/zgbs/fashion/9479199011'],
                    'costumes'        => ['name' => 'Costumes & Accessories', 'url' => 'https://www.amazon.com/Best-Sellers-Clothing-Shoes-Jewelry-Costumes-Accessories/zgbs/fashion/7586165011'],
                    'shoe-accessories'=> ['name' => 'Shoe, Jewelry & Watch Accessories', 'url' => 'https://www.amazon.com/Best-Sellers-Clothing-Shoes-Jewelry-Shoe-Jewelry-Watch-Accessories/zgbs/fashion/7586146011'],
                    'sport-clothing'  => ['name' => 'Sport Specific Clothing', 'url' => 'https://www.amazon.com/Best-Sellers-Clothing-Shoes-Jewelry-Sport-Specific-Clothing/zgbs/fashion/23575629011'],
                    'uniforms'        => ['name' => 'Uniforms, Work & Safety', 'url' => 'https://www.amazon.com/Best-Sellers-Clothing-Shoes-Jewelry-Uniforms-Work-Safety/zgbs/fashion/7586144011'],
                    'novelty'         => ['name' => 'Novelty & More',   'url' => 'https://www.amazon.com/Best-Sellers-Clothing-Shoes-Jewelry-Novelty-More/zgbs/fashion/7147445011'],
                ],
            ],
            'beauty' => [
                'name' => 'Beauty & Personal Care',
                'url'  => 'https://www.amazon.com/Best-Sellers-Beauty-Personal-Care/zgbs/beauty/',
                'subcategories' => [
                    'makeup'          => ['name' => 'Makeup',          'url' => 'https://www.amazon.com/Best-Sellers-Beauty-Personal-Care-Makeup/zgbs/beauty/11058281'],
                    'skin-care'       => ['name' => 'Skin Care',       'url' => 'https://www.amazon.com/Best-Sellers-Beauty-Personal-Care-Skin-Care-Products/zgbs/beauty/11060451'],
                    'hair-care'       => ['name' => 'Hair Care',       'url' => 'https://www.amazon.com/Best-Sellers-Beauty-Personal-Care-Hair-Care-Products/zgbs/beauty/11057241'],
                    'fragrance'       => ['name' => 'Fragrance',       'url' => 'https://www.amazon.com/Best-Sellers-Beauty-Personal-Care-Perfumes-Fragrances/zgbs/beauty/11056591'],
                    'personal-care'   => ['name' => 'Personal Care',   'url' => 'https://www.amazon.com/Best-Sellers-Beauty-Personal-Care-Personal-Care-Products/zgbs/beauty/3777891'],
                    'tools'           => ['name' => 'Tools & Accessories', 'url' => 'https://www.amazon.com/Best-Sellers-Beauty-Personal-Care-Beauty-Tools-Accessories/zgbs/beauty/11062741'],
                    'shave'           => ['name' => 'Shave & Hair Removal', 'url' => 'https://www.amazon.com/Best-Sellers-Beauty-Personal-Care-Shaving-Hair-Removal-Products/zgbs/beauty/3778591'],
                    'foot-hand-nail'  => ['name' => 'Foot, Hand & Nail Care', 'url' => 'https://www.amazon.com/Best-Sellers-Beauty-Personal-Care-Foot-Hand-Nail-Care-Products/zgbs/beauty/17242866011'],
                    'gift-sets'       => ['name' => 'Gift Sets',       'url' => 'https://www.amazon.com/Best-Sellers-Beauty-Personal-Care-Beauty-Gift-Sets/zgbs/beauty/120225719011'],
                    'salon-spa'       => ['name' => 'Salon & Spa Equipment', 'url' => 'https://www.amazon.com/Best-Sellers-Beauty-Personal-Care-Salon-Spa-Equipment/zgbs/beauty/15144566011'],
                ],
            ],
            'home-garden' => [
                'name' => 'Home & Kitchen',
                'url'  => 'https://www.amazon.com/Best-Sellers-Home-Kitchen/zgbs/home-garden/',
                'subcategories' => [
                    'bedding'         => ['name' => 'Bedding',         'url' => 'https://www.amazon.com/Best-Sellers-Home-Kitchen-Bedding/zgbs/home-garden/1063252'],
                    'bath'            => ['name' => 'Bath',            'url' => 'https://www.amazon.com/Best-Sellers-Home-Kitchen-Bath-Products/zgbs/home-garden/1063236'],
                    'furniture'       => ['name' => 'Furniture',       'url' => 'https://www.amazon.com/Best-Sellers-Home-Kitchen-Furniture/zgbs/home-garden/1063306'],
                    'home-decor'      => ['name' => 'Home Decor',      'url' => 'https://www.amazon.com/Best-Sellers-Home-Kitchen-Home-Dcor-Products/zgbs/home-garden/1063278'],
                    'kitchen-dining'  => ['name' => 'Kitchen & Dining','url' => 'https://www.amazon.com/Best-Sellers-Home-Kitchen-Kitchen-Dining/zgbs/home-garden/284507'],
                    'storage'         => ['name' => 'Storage & Organization', 'url' => 'https://www.amazon.com/Best-Sellers-Home-Kitchen-Home-Storage-Organization/zgbs/home-garden/3610841'],
                    'vacuums'         => ['name' => 'Vacuums & Floor Care', 'url' => 'https://www.amazon.com/Best-Sellers-Home-Kitchen-Vacuum-Cleaners-Floor-Care/zgbs/home-garden/510106'],
                    'cleaning'        => ['name' => 'Cleaning Supplies', 'url' => 'https://www.amazon.com/Best-Sellers-Home-Kitchen-Household-Cleaning-Supplies/zgbs/home-garden/10802561'],
                    'wall-decor'      => ['name' => 'Wall Decor',      'url' => 'https://www.amazon.com/Best-Sellers-Home-Kitchen-Wall-Art/zgbs/home-garden/3736081'],
                    'seasonal'        => ['name' => 'Seasonal Decor',  'url' => 'https://www.amazon.com/Best-Sellers-Home-Kitchen-Seasonal-Dcor/zgbs/home-garden/13679381'],
                    'party'           => ['name' => 'Party Supplies',  'url' => 'https://www.amazon.com/Best-Sellers-Home-Kitchen-Event-Party-Supplies/zgbs/home-garden/901590'],
                    'hvac'            => ['name' => 'Heating, Cooling & Air Quality', 'url' => 'https://www.amazon.com/Best-Sellers-Home-Kitchen-Heating-Cooling-Air-Quality/zgbs/home-garden/3206324011'],
                    'irons'           => ['name' => 'Irons & Steamers', 'url' => 'https://www.amazon.com/Best-Sellers-Home-Kitchen-Ironing-Products/zgbs/home-garden/510240'],
                    'kids-home'       => ['name' => "Kids' Home Store", 'url' => 'https://www.amazon.com/Best-Sellers-Home-Kitchen-Kids-Home-Store/zgbs/home-garden/3206325011'],
                ],
            ],
            'electronics' => [
                'name' => 'Electronics',
                'url'  => 'https://www.amazon.com/Best-Sellers-Electronics/zgbs/electronics/',
                'subcategories' => [
                    'headphones'      => ['name' => 'Headphones',      'url' => 'https://www.amazon.com/Best-Sellers-Electronics-Headphones-Earbuds/zgbs/electronics/172541'],
                    'cell-phones'     => ['name' => 'Cell Phones & Accessories', 'url' => 'https://www.amazon.com/Best-Sellers-Electronics-Cell-Phones-Accessories/zgbs/electronics/2811119011'],
                    'computers'       => ['name' => 'Computers & Accessories', 'url' => 'https://www.amazon.com/Best-Sellers-Electronics-Computers-Accessories/zgbs/electronics/541966'],
                    'tv-video'        => ['name' => 'Televisions & Video', 'url' => 'https://www.amazon.com/Best-Sellers-Electronics-Televisions-Video-Products/zgbs/electronics/1266092011'],
                    'home-audio'      => ['name' => 'Home Audio & Theater', 'url' => 'https://www.amazon.com/Best-Sellers-Electronics-Home-Audio-Theater-Products/zgbs/electronics/667846011'],
                    'camera-photo'    => ['name' => 'Camera & Photo',  'url' => 'https://www.amazon.com/Best-Sellers-Electronics-Camera-Photo-Products/zgbs/electronics/502394'],
                    'car-electronics' => ['name' => 'Car Electronics', 'url' => 'https://www.amazon.com/Best-Sellers-Electronics-Car-Electronics/zgbs/electronics/1077068'],
                    'wearable'        => ['name' => 'Wearable Technology', 'url' => 'https://www.amazon.com/Best-Sellers-Electronics-Wearable-Technology/zgbs/electronics/10048700011'],
                    'security'        => ['name' => 'Security & Surveillance', 'url' => 'https://www.amazon.com/Best-Sellers-Electronics-Security-Surveillance-Equipment/zgbs/electronics/524136'],
                    'portable-audio'  => ['name' => 'Portable Audio & Video', 'url' => 'https://www.amazon.com/Best-Sellers-Electronics-Portable-Audio-Video/zgbs/electronics/172623'],
                    'gps'             => ['name' => 'GPS & Navigation','url' => 'https://www.amazon.com/Best-Sellers-Electronics-GPS-Finders-Accessories/zgbs/electronics/172526'],
                    'office-electronics' => ['name' => 'Office Electronics', 'url' => 'https://www.amazon.com/Best-Sellers-Electronics-Office-Electronics-Products/zgbs/electronics/172574'],
                    'projectors'      => ['name' => 'Video Projectors','url' => 'https://www.amazon.com/Best-Sellers-Electronics-Video-Projectors/zgbs/electronics/300334'],
                    'marine'          => ['name' => 'Marine Electronics', 'url' => 'https://www.amazon.com/Best-Sellers-Electronics-Marine-Electronics/zgbs/electronics/319574011'],
                    'accessories'     => ['name' => 'Accessories & Supplies', 'url' => 'https://www.amazon.com/Best-Sellers-Electronics-Electronics-Accessories-Supplies/zgbs/electronics/281407'],
                    'ereaders'        => ['name' => 'eBook Readers & Accessories', 'url' => 'https://www.amazon.com/Best-Sellers-Electronics-eBook-Readers-Accessories/zgbs/electronics/2642125011'],
                    'game-consoles'   => ['name' => 'Video Game Consoles & Accessories', 'url' => 'https://www.amazon.com/Best-Sellers-Electronics-Video-Game-Consoles-Accessories/zgbs/electronics/7926841011'],
                    'service-plans'   => ['name' => 'Service & Replacement Plans', 'url' => 'https://www.amazon.com/Best-Sellers-Electronics-Computers-Electronics-Service-Plans/zgbs/electronics/16285901'],
                ],
            ],
            'sporting-goods' => [
                'name' => 'Sports & Outdoors',
                'url'  => 'https://www.amazon.com/Best-Sellers-Sports-Outdoors/zgbs/sporting-goods/',
                'subcategories' => [
                    'fitness'         => ['name' => 'Exercise & Fitness', 'url' => 'https://www.amazon.com/Best-Sellers-Sports-Outdoors-Exercise-Fitness-Equipment/zgbs/sporting-goods/3407731'],
                    'outdoor-rec'     => ['name' => 'Outdoor Recreation', 'url' => 'https://www.amazon.com/Best-Sellers-Sports-Outdoors-Outdoor-Recreation/zgbs/sporting-goods/706814011'],
                    'sports'          => ['name' => 'Sports',          'url' => 'https://www.amazon.com/Best-Sellers-Sports-Outdoors-Sports-Apparel-Equipment/zgbs/sporting-goods/10971181011'],
                    'hunting-fishing' => ['name' => 'Hunting & Fishing', 'url' => 'https://www.amazon.com/Best-Sellers-Sports-Outdoors-Hunting-Fishing-Products/zgbs/sporting-goods/706813011'],
                    'fan-shop'        => ['name' => 'Fan Shop',        'url' => 'https://www.amazon.com/Best-Sellers-Sports-Outdoors-Sports-Fan-Shop/zgbs/sporting-goods/3386071'],
                    'accessories'     => ['name' => 'Accessories',     'url' => 'https://www.amazon.com/Best-Sellers-Sports-Outdoors-Sports-Outdoor-Recreation-Accessories/zgbs/sporting-goods/3394801'],
                    'sports-medicine' => ['name' => 'Sports Medicine', 'url' => 'https://www.amazon.com/Best-Sellers-Sports-Outdoors-Sports-Medicine-Products/zgbs/sporting-goods/3422351'],
                    'memorabilia'     => ['name' => 'Memorabilia Display & Storage', 'url' => 'https://www.amazon.com/Best-Sellers-Sports-Outdoors-Memorabilia-Display-Storage/zgbs/sporting-goods/2358921011'],
                ],
            ],
            'toys-and-games' => [
                'name' => 'Toys & Games',
                'url'  => 'https://www.amazon.com/Best-Sellers-Toys-Games/zgbs/toys-and-games/',
                'subcategories' => [
                    'building'        => ['name' => 'Building & Construction', 'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Building-Toys/zgbs/toys-and-games/166092011'],
                    'dolls'           => ['name' => 'Dolls & Accessories', 'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Dolls-Accessories/zgbs/toys-and-games/166118011'],
                    'action-figures'  => ['name' => 'Action & Toy Figures', 'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Toy-Figures-Playsets/zgbs/toys-and-games/165993011'],
                    'games'           => ['name' => 'Games',            'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Games-Accessories/zgbs/toys-and-games/166220011'],
                    'puzzles'         => ['name' => 'Puzzles',          'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Puzzles/zgbs/toys-and-games/166359011'],
                    'arts-crafts'     => ['name' => 'Arts & Crafts',    'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Arts-Crafts-Supplies/zgbs/toys-and-games/166057011'],
                    'learning'        => ['name' => 'Learning & Education', 'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Learning-Education-Toys/zgbs/toys-and-games/166269011'],
                    'stuffed-animals' => ['name' => 'Stuffed Animals & Toys', 'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Stuffed-Animals-Plush-Toys/zgbs/toys-and-games/166461011'],
                    'sports-outdoor'  => ['name' => 'Sports & Outdoor Play', 'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Sports-Outdoor-Play-Toys/zgbs/toys-and-games/166420011'],
                    'baby-toddler'    => ['name' => 'Baby & Toddler Toys', 'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Baby-Toddler-Toys/zgbs/toys-and-games/196601011'],
                    'bikes-rides'     => ['name' => 'Bikes, Skates & Ride-Ons', 'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Tricycles-Scooters-Wagons/zgbs/toys-and-games/256994011'],
                    'play-vehicles'   => ['name' => 'Play Vehicles',    'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Toy-Vehicles/zgbs/toys-and-games/23539911011'],
                    'kids-electronics'=> ['name' => 'Electronics for Kids', 'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Kids-Electronics/zgbs/toys-and-games/166164011'],
                    'dress-up'        => ['name' => 'Dressing Up & Costumes', 'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Kids-Dress-Up-Pretend-Play/zgbs/toys-and-games/166316011'],
                    'remote-control'  => ['name' => 'Remote- & App-Controlled', 'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Hobby-Remote-App-Controlled-Vehicles-Parts/zgbs/toys-and-games/6925830011'],
                    'card-games'      => ['name' => 'Collectible Card Games', 'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Collectible-Card-Games/zgbs/toys-and-games/166242011'],
                    'party-supplies'  => ['name' => 'Party Supplies',   'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Kids-Party-Supplies/zgbs/toys-and-games/1266203011'],
                    'puppets'         => ['name' => 'Puppets & Puppet Theaters', 'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Puppets-Puppet-Theaters/zgbs/toys-and-games/166333011'],
                    'novelty'         => ['name' => 'Novelty & Gag Toys', 'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Novelty-Toys-Amusements/zgbs/toys-and-games/166027011'],
                    'kids-music'      => ['name' => 'Musical Instruments', 'url' => 'https://www.amazon.com/Best-Sellers-Toys-Games-Kids-Musical-Instruments/zgbs/toys-and-games/166326011'],
                ],
            ],
            'hpc' => [
                'name' => 'Health & Household',
                'url'  => 'https://www.amazon.com/Best-Sellers-Health-Household/zgbs/hpc/',
                'subcategories' => [
                    'vitamins'        => ['name' => 'Vitamins, Minerals & Supplements', 'url' => 'https://www.amazon.com/Best-Sellers-Health-Household-Vitamins-Minerals-Supplements/zgbs/hpc/23675621011'],
                    'health-care'     => ['name' => 'Health Care',     'url' => 'https://www.amazon.com/Best-Sellers-Health-Household-Health-Care-Products/zgbs/hpc/3760941'],
                    'diet-nutrition'  => ['name' => 'Diet & Sports Nutrition', 'url' => 'https://www.amazon.com/Best-Sellers-Health-Household-Diet-Sports-Nutrition/zgbs/hpc/3764441'],
                    'sports-nutrition'=> ['name' => 'Sports Nutrition', 'url' => 'https://www.amazon.com/Best-Sellers-Health-Household-Sports-Nutrition-Products/zgbs/hpc/6973663011'],
                    'oral-care'       => ['name' => 'Oral Care',       'url' => 'https://www.amazon.com/Best-Sellers-Health-Household-Oral-Care-Products/zgbs/hpc/10079992011'],
                    'personal-care'   => ['name' => 'Personal Care',   'url' => 'https://www.amazon.com/Best-Sellers-Health-Household-Personal-Care-Products/zgbs/hpc/3777891'],
                    'medical-supplies'=> ['name' => 'Medical Supplies & Equipment', 'url' => 'https://www.amazon.com/Best-Sellers-Health-Household-Home-Use-Medical-Supplies-Equipment/zgbs/hpc/3775161'],
                    'house-supplies'  => ['name' => 'House Supplies',  'url' => 'https://www.amazon.com/Best-Sellers-Health-Household-Household-Supplies/zgbs/hpc/15342811'],
                    'baby-child'      => ['name' => 'Baby & Child Care', 'url' => 'https://www.amazon.com/Best-Sellers-Health-Household-Baby-Child-Care-Products/zgbs/hpc/10787321'],
                    'sexual-wellness' => ['name' => 'Sexual Wellness', 'url' => 'https://www.amazon.com/Best-Sellers-Health-Household-Sexual-Wellness-Products/zgbs/hpc/3777371'],
                    'wellness'        => ['name' => 'Wellness & Relaxation', 'url' => 'https://www.amazon.com/Best-Sellers-Health-Household-Wellness-Relaxation-Products/zgbs/hpc/10079996011'],
                    'vision'          => ['name' => 'Vision Care',     'url' => 'https://www.amazon.com/Best-Sellers-Health-Household-Vision-Products/zgbs/hpc/10079994011'],
                    'stationery'      => ['name' => 'Stationery & Gift Wrapping', 'url' => 'https://www.amazon.com/Best-Sellers-Health-Household-Stationery-Gift-Wrapping-Supplies/zgbs/hpc/723418011'],
                    'sales-deals'     => ['name' => 'Sales & Deals',   'url' => 'https://www.amazon.com/Best-Sellers-Health-Household-Sales-Deals/zgbs/hpc/120225718011'],
                ],
            ],
            'pet-supplies' => [
                'name' => 'Pet Supplies',
                'url'  => 'https://www.amazon.com/Best-Sellers-Pet-Supplies/zgbs/pet-supplies/',
                'subcategories' => [
                    'dogs'          => ['name' => 'Dogs',              'url' => 'https://www.amazon.com/Best-Sellers-Pet-Supplies-Dog-Supplies/zgbs/pet-supplies/2975312011'],
                    'cats'          => ['name' => 'Cats',              'url' => 'https://www.amazon.com/Best-Sellers-Pet-Supplies-Cat-Supplies/zgbs/pet-supplies/2975241011'],
                    'fish'          => ['name' => 'Fish & Aquatic Pets', 'url' => 'https://www.amazon.com/Best-Sellers-Pet-Supplies-Fish-Aquatic-Pets/zgbs/pet-supplies/2975446011'],
                    'birds'         => ['name' => 'Birds',             'url' => 'https://www.amazon.com/Best-Sellers-Pet-Supplies-Pet-Bird-Supplies/zgbs/pet-supplies/2975221011'],
                    'small-animals' => ['name' => 'Small Animals',     'url' => 'https://www.amazon.com/Best-Sellers-Pet-Supplies-Small-Animal-Supplies/zgbs/pet-supplies/2975520011'],
                    'reptiles'      => ['name' => 'Reptiles & Amphibians', 'url' => 'https://www.amazon.com/Best-Sellers-Pet-Supplies-Reptiles-Amphibian-Supplies/zgbs/pet-supplies/2975504011'],
                    'horses'        => ['name' => 'Horses',            'url' => 'https://www.amazon.com/Best-Sellers-Pet-Supplies-Horse-Supplies/zgbs/pet-supplies/2975481011'],
                ],
            ],
            'baby-products' => [
                'name' => 'Baby',
                'url'  => 'https://www.amazon.com/Best-Sellers-Baby/zgbs/baby-products/',
                'subcategories' => [
                    'feeding'       => ['name' => 'Feeding',           'url' => 'https://www.amazon.com/Best-Sellers-Baby-Baby-Toddler-Feeding-Supplies/zgbs/baby-products/166777011'],
                    'diapering'     => ['name' => 'Diapering',         'url' => 'https://www.amazon.com/Best-Sellers-Baby-Baby-Diapering-Products/zgbs/baby-products/166764011'],
                    'nursery'       => ['name' => 'Nursery',           'url' => 'https://www.amazon.com/Best-Sellers-Baby-Nursery-Furniture-Bedding-Dcor/zgbs/baby-products/695338011'],
                    'strollers'     => ['name' => 'Strollers',         'url' => 'https://www.amazon.com/Best-Sellers-Baby-Baby-Strollers/zgbs/baby-products/166842011'],
                    'car-seats'     => ['name' => 'Car Seats',         'url' => 'https://www.amazon.com/Best-Sellers-Baby-Child-Safety-Car-Seats-Accessories/zgbs/baby-products/166835011'],
                    'safety'        => ['name' => 'Safety',            'url' => 'https://www.amazon.com/Best-Sellers-Baby-Baby-Safety-Products/zgbs/baby-products/166863011'],
                    'activity'      => ['name' => 'Activity & Entertainment', 'url' => 'https://www.amazon.com/Best-Sellers-Baby-Baby-Activity-Entertainment-Products/zgbs/baby-products/239225011'],
                    'for-moms'      => ['name' => 'For Moms',          'url' => 'https://www.amazon.com/Best-Sellers-Baby-Pregnancy-Maternity-Products/zgbs/baby-products/166804011'],
                    'gifts'         => ['name' => 'Gifts',             'url' => 'https://www.amazon.com/Best-Sellers-Baby-Baby-Gifts/zgbs/baby-products/239226011'],
                    'travel'        => ['name' => 'Travel Gear',       'url' => 'https://www.amazon.com/Best-Sellers-Baby-Baby-Travel-Gear/zgbs/baby-products/17726796011'],
                    'baby-care'     => ['name' => 'Baby Care',         'url' => 'https://www.amazon.com/Best-Sellers-Baby-Baby-Care-Products/zgbs/baby-products/17720255011'],
                    'potty-training'=> ['name' => 'Potty Training',    'url' => 'https://www.amazon.com/Best-Sellers-Baby-Toilet-Training-Products/zgbs/baby-products/166887011'],
                ],
            ],
            'grocery' => [
                'name' => 'Grocery & Gourmet Food',
                'url'  => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food/zgbs/grocery/',
                'subcategories' => [
                    'snacks'          => ['name' => 'Snacks & Sweets', 'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Snacks-Sweets/zgbs/grocery/23759921011'],
                    'beverages'       => ['name' => 'Beverages',       'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Beverages/zgbs/grocery/16310231'],
                    'pantry'          => ['name' => 'Pantry Staples',  'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Pantry-Staples/zgbs/grocery/18787303011'],
                    'coffee-tea'      => ['name' => 'Coffee, Tea & Cocoa', 'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Beverages/zgbs/grocery/16310231'],
                    'breakfast'       => ['name' => 'Breakfast Foods', 'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Breakfast-Foods/zgbs/grocery/16310251'],
                    'cereal'          => ['name' => 'Breakfast Cereal','url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Breakfast-Cereal/zgbs/grocery/118518014011'],
                    'meat-seafood'    => ['name' => 'Meat & Seafood',  'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Meat-Seafood/zgbs/grocery/371469011'],
                    'dairy-eggs'      => ['name' => 'Dairy, Eggs & Plant-Based', 'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Dairy-Eggs-Plant-Based-Alternatives/zgbs/grocery/371460011'],
                    'breads'          => ['name' => 'Breads & Bakery', 'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Breads-Bakery/zgbs/grocery/16318751'],
                    'frozen'          => ['name' => 'Frozen',          'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Frozen-Foods/zgbs/grocery/6459122011'],
                    'produce'         => ['name' => 'Produce',         'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Fresh-Produce/zgbs/grocery/6506977011'],
                    'baby-foods'      => ['name' => 'Baby Foods',      'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Baby-Foods/zgbs/grocery/16323111'],
                    'deli'            => ['name' => 'Deli & Prepared Foods', 'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Deli-Prepared-Foods/zgbs/grocery/18773724011'],
                    'meal-kits'       => ['name' => 'Fresh Meal Kits', 'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Fresh-Meal-Ingredient-Kits/zgbs/grocery/15709227011'],
                    'meat-substitutes'=> ['name' => 'Meat Substitutes', 'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Meat-Substitutes/zgbs/grocery/6518859011'],
                    'gifts'           => ['name' => 'Food & Beverage Gifts', 'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Food-Beverage-Gifts/zgbs/grocery/2255571011'],
                    'flowers'         => ['name' => 'Fresh Flowers & Live Plants', 'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Fresh-Flowers-Live-Indoor-Plants/zgbs/grocery/3745171'],
                    'brewing'         => ['name' => 'Home Brewing & Winemaking', 'url' => 'https://www.amazon.com/Best-Sellers-Grocery-Gourmet-Food-Home-Brewing-Winemaking/zgbs/grocery/979861011'],
                ],
            ],
            'automotive' => [
                'name' => 'Automotive',
                'url'  => 'https://www.amazon.com/Best-Sellers-Automotive/zgbs/automotive/',
                'subcategories' => [
                    'interior'        => ['name' => 'Interior Accessories', 'url' => 'https://www.amazon.com/Best-Sellers-Automotive-Automotive-Interior-Accessories/zgbs/automotive/15857501'],
                    'exterior'        => ['name' => 'Exterior Accessories', 'url' => 'https://www.amazon.com/Best-Sellers-Automotive-Automotive-Exterior-Accessories/zgbs/automotive/15857511'],
                    'car-care'        => ['name' => 'Car Care',         'url' => 'https://www.amazon.com/Best-Sellers-Automotive-Car-Care/zgbs/automotive/15718271'],
                    'tools'           => ['name' => 'Tools & Equipment','url' => 'https://www.amazon.com/Best-Sellers-Automotive-Automotive-Tools-Equipment/zgbs/automotive/15706941'],
                    'replacement'     => ['name' => 'Replacement Parts','url' => 'https://www.amazon.com/Best-Sellers-Automotive-Automotive-Replacement-Parts/zgbs/automotive/15719731'],
                    'oils-fluids'     => ['name' => 'Oils & Fluids',    'url' => 'https://www.amazon.com/Best-Sellers-Automotive-Oils-Fluids/zgbs/automotive/15718791'],
                    'wheels-tires'    => ['name' => 'Wheels & Tires',   'url' => 'https://www.amazon.com/Best-Sellers-Automotive-Automotive-Tires-Wheels/zgbs/automotive/15706571'],
                    'lights'          => ['name' => 'Light & Lighting Accessories', 'url' => 'https://www.amazon.com/Best-Sellers-Automotive-Automotive-Lights-Bulbs-Indicators/zgbs/automotive/15736321'],
                    'motorcycle'      => ['name' => 'Motorcycle & ATV', 'url' => 'https://www.amazon.com/Best-Sellers-Automotive-Motorcycle-Powersports/zgbs/automotive/346333011'],
                    'performance'     => ['name' => 'Performance Parts & Accessories', 'url' => 'https://www.amazon.com/Best-Sellers-Automotive-Automotive-Performance-Parts-Accessories/zgbs/automotive/15710351'],
                    'rv'              => ['name' => 'RV Parts & Accessories', 'url' => 'https://www.amazon.com/Best-Sellers-Automotive-RV-Parts-Accessories/zgbs/automotive/2258019011'],
                    'paint'           => ['name' => 'Paint & Paint Supplies', 'url' => 'https://www.amazon.com/Best-Sellers-Automotive-Automotive-Paint-Paint-Supplies/zgbs/automotive/13591416011'],
                ],
            ],
            'hi' => [
                'name' => 'Tools & Home Improvement',
                'url'  => 'https://www.amazon.com/Best-Sellers-Tools-Home-Improvement/zgbs/hi/',
                'subcategories' => [
                    'power-hand'     => ['name' => 'Power & Hand Tools', 'url' => 'https://www.amazon.com/Best-Sellers-Tools-Home-Improvement-Power-Tools-Hand-Tools/zgbs/hi/328182011'],
                    'hardware'       => ['name' => 'Hardware',          'url' => 'https://www.amazon.com/Best-Sellers-Tools-Home-Improvement-Hardware/zgbs/hi/511228'],
                    'electrical'     => ['name' => 'Electrical',        'url' => 'https://www.amazon.com/Best-Sellers-Tools-Home-Improvement-Electrical-Equipment/zgbs/hi/495266'],
                    'plumbing'       => ['name' => 'Plumbing',          'url' => 'https://www.amazon.com/Best-Sellers-Tools-Home-Improvement-Pumps-Plumbing-Equipment/zgbs/hi/13749581'],
                    'lighting'       => ['name' => 'Lighting & Ceiling Fans', 'url' => 'https://www.amazon.com/Best-Sellers-Tools-Home-Improvement-Lighting-Ceiling-Fans/zgbs/hi/495224'],
                    'paint'          => ['name' => 'Paint, Wall Treatments & Supplies', 'url' => 'https://www.amazon.com/Best-Sellers-Tools-Home-Improvement-Paint-Wall-Treatments-Supplies/zgbs/hi/228899'],
                    'building'       => ['name' => 'Building Supplies', 'url' => 'https://www.amazon.com/Best-Sellers-Tools-Home-Improvement-Building-Supplies/zgbs/hi/551240'],
                    'kitchen-bath'   => ['name' => 'Kitchen & Bath Fixtures', 'url' => 'https://www.amazon.com/Best-Sellers-Tools-Home-Improvement-Kitchen-Bath-Fixtures/zgbs/hi/3754161'],
                    'safety-security'=> ['name' => 'Safety & Security', 'url' => 'https://www.amazon.com/Best-Sellers-Tools-Home-Improvement-Safety-Security/zgbs/hi/3180231'],
                    'storage'        => ['name' => 'Storage & Home Organization', 'url' => 'https://www.amazon.com/Best-Sellers-Tools-Home-Improvement-Home-Storage-Organization/zgbs/hi/13400631'],
                    'measuring'      => ['name' => 'Measuring & Layout Tools', 'url' => 'https://www.amazon.com/Best-Sellers-Tools-Home-Improvement-Measuring-Layout/zgbs/hi/553244'],
                    'air-tools'      => ['name' => 'Air Tools',         'url' => 'https://www.amazon.com/Best-Sellers-Tools-Home-Improvement-Air-Powered-Tools/zgbs/hi/120225786011'],
                    'appliances'     => ['name' => 'Appliances',        'url' => 'https://www.amazon.com/Best-Sellers-Tools-Home-Improvement-Home-Appliances/zgbs/hi/13397451'],
                ],
            ],
            'kitchen' => [
                'name' => 'Kitchen & Dining',
                'url'  => 'https://www.amazon.com/Best-Sellers-Kitchen-Dining/zgbs/kitchen/',
                'subcategories' => [
                    'cookware'        => ['name' => 'Cookware',         'url' => 'https://www.amazon.com/Best-Sellers-Kitchen-Dining-Kitchen-Cookware/zgbs/kitchen/289814'],
                    'bakeware'        => ['name' => 'Bakeware',         'url' => 'https://www.amazon.com/Best-Sellers-Kitchen-Dining-Bakeware/zgbs/kitchen/289668'],
                    'utensils'        => ['name' => 'Kitchen Utensils & Gadgets', 'url' => 'https://www.amazon.com/Best-Sellers-Kitchen-Dining-Kitchen-Utensils-Gadgets/zgbs/kitchen/289754'],
                    'small-appliances'=> ['name' => 'Small Appliances', 'url' => 'https://www.amazon.com/Best-Sellers-Kitchen-Dining-Kitchen-Small-Appliances/zgbs/kitchen/289913'],
                    'coffee-espresso' => ['name' => 'Coffee, Tea & Espresso', 'url' => 'https://www.amazon.com/Best-Sellers-Kitchen-Dining-Coffee-Tea-Espresso-Appliances/zgbs/kitchen/289742'],
                    'dining'          => ['name' => 'Dining & Entertaining', 'url' => 'https://www.amazon.com/Best-Sellers-Kitchen-Dining-Dining-Entertaining/zgbs/kitchen/13162311'],
                    'bar-drinkware'   => ['name' => 'Bar Tools & Drinkware', 'url' => 'https://www.amazon.com/Best-Sellers-Kitchen-Dining-Bar-Tools-Drinkware/zgbs/kitchen/289728'],
                    'glassware'       => ['name' => 'Glassware & Drinkware', 'url' => 'https://www.amazon.com/Best-Sellers-Kitchen-Dining-Glassware-Drinkware/zgbs/kitchen/13217501'],
                    'storage'         => ['name' => 'Storage & Organization', 'url' => 'https://www.amazon.com/Best-Sellers-Kitchen-Dining-Kitchen-Storage-Organization/zgbs/kitchen/510136'],
                    'linens'          => ['name' => 'Kitchen & Table Linens', 'url' => 'https://www.amazon.com/Best-Sellers-Kitchen-Dining-Kitchen-Table-Linens/zgbs/kitchen/1063916'],
                    'wine'            => ['name' => 'Wine Accessories', 'url' => 'https://www.amazon.com/Best-Sellers-Kitchen-Dining-Wine-Accessories/zgbs/kitchen/13299291'],
                    'brewing'         => ['name' => 'Home Brewing & Wine Making', 'url' => 'https://www.amazon.com/Best-Sellers-Kitchen-Dining-Home-Brewing-Wine-Making/zgbs/kitchen/979832011'],
                ],
            ],
            // 27 additional departments loaded from inc/amazon-departments.php
        ] + (include __DIR__ . '/amazon-departments.php');
    }

    /**
     * Flatten selected categories from the department tree into slug=>url pairs for scraping.
     */
    public static function resolve_categories() {
        $selected = get_option('pinery_flow_categories', []);
        $tree     = self::amazon_departments();
        $categories = [];

        if (!is_array($selected)) {
            $selected = [];
        }

        foreach ($selected as $slug) {
            if (strpos($slug, '/') !== false) {
                // Subcategory: "fashion/women"
                list($dept_id, $sub_id) = explode('/', $slug, 2);
                if (isset($tree[$dept_id]['subcategories'][$sub_id])) {
                    $cat_slug = $dept_id . '-' . $sub_id;
                    $categories[$cat_slug] = $tree[$dept_id]['subcategories'][$sub_id]['url'];
                }
            } else {
                // Department-only (no subcategories): use department URL directly
                if (isset($tree[$slug]) && empty($tree[$slug]['subcategories'])) {
                    $categories[$slug] = $tree[$slug]['url'];
                }
            }
        }

        // Merge custom categories
        $custom = get_option('pinery_flow_custom_categories', []);
        if (is_array($custom)) {
            foreach ($custom as $item) {
                if (!empty($item['name']) && !empty($item['url'])) {
                    $cat_slug = sanitize_title($item['name']);
                    $categories[$cat_slug] = esc_url_raw($item['url']);
                }
            }
        }

        return $categories;
    }

    /** @deprecated Kept for backward compatibility with existing installs */
    public static function default_categories() {
        $tree = self::amazon_departments();
        $flat = [];
        foreach ($tree as $dept_id => $dept) {
            if (!empty($dept['subcategories'])) {
                foreach ($dept['subcategories'] as $sub_id => $sub) {
                    $flat[$dept_id . '/' . $sub_id] = $sub['url'];
                }
            } else {
                $flat[$dept_id] = $dept['url'];
            }
        }
        return $flat;
    }

    public static function scrape_all($categories) {
        $all = [];
        foreach ($categories as $slug => $url) {
            $products = self::scrape_category($slug, $url);
            $count = count($products);
            $name = str_replace('-', ' ', $slug);
            self::debug_log($count ? 'info' : 'warning', 'Scraping', sprintf('%s: %d products', $name, $count));
            $all = array_merge($all, $products);
        }
        return $all;
    }

    public static function scrape_category($slug, $url) {
        // Try Creators API first if configured. Falls back to HTML scraping
        // automatically when API returns empty (e.g. <10 qualifying sales).
        if (Pinery_Creators_API::is_available()) {
            self::debug_log('info', 'Scraper', "Trying Creators API for: {$slug}");
            $result = Pinery_Creators_API::scrape_category($slug, $url);
            self::debug_log('info', 'Scraper', "Creators API returned " . count($result) . " products for: {$slug}");
            if (!empty($result)) return $result;
        }

        $response = wp_remote_get($url, [
            'timeout' => 20,
            'headers' => [
                'User-Agent'      => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
                'Accept-Language' => 'en-US,en;q=0.9',
            ],
        ]);

        if (is_wp_error($response)) return [];

        $html = wp_remote_retrieve_body($response);
        if (empty($html)) return [];

        // Split on product faceout cards — each has id="ASIN" in p13n-sc-uncoverable-faceout
        $parts = preg_split('/<div[^>]*id="([A-Z0-9]{10})"[^>]*class="[^"]*p13n-sc-uncoverable-faceout[^"]*"[^>]*>/', $html, -1, PREG_SPLIT_DELIM_CAPTURE);

        $products = [];
        $idx = 0;

        // $parts[0] is before first match, then pairs of [ASIN, card_html, ASIN, card_html, ...]
        for ($i = 1; $i < count($parts) - 1; $i += 2) {
            $asin  = $parts[$i];
            $chunk = $parts[$i + 1];
            $idx++;

            // Product link — first /dp/ASIN/ref=... in the card
            $link = '';
            if (preg_match('#href="(/[^"]*?/dp/' . preg_quote($asin, '#') . '/[^"]*?)"#', $chunk, $lm)) {
                $link = 'https://www.amazon.com' . $lm[1];
            } elseif (preg_match('#href="(/dp/' . preg_quote($asin, '#') . '[^"]*?)"#', $chunk, $lm)) {
                $link = 'https://www.amazon.com' . $lm[1];
            }

            // Image
            $img = '';
            if (preg_match('/<img[^>]*src="([^"]*)"[^>]*>/', $chunk, $im)) {
                $img = preg_replace('/\._[A-Z_0-9,]+_\./', '._AC_SL1500_.', $im[1]);
            }

            // Title — from image alt attribute (most reliable)
            $title = '';
            if (preg_match('/<img[^>]*alt="([^"]*)"[^>]*>/', $chunk, $tm)) {
                $title = trim(html_entity_decode($tm[1], ENT_QUOTES));
            }

            // Fallback title: from the line-clamp div
            if (empty($title)) {
                if (preg_match('#<div[^>]*class="[^"]*p13n-sc-css-line-clamp[^"]*"[^>]*>(.*?)</div>#s', $chunk, $tm)) {
                    $title = trim(wp_strip_all_tags($tm[1]));
                }
            }

            // Price
            $price = 'N/A';
            if (preg_match('/\$([\d,.]+)/', $chunk, $pm)) {
                $price = '$' . $pm[1];
            }

            // Rating — from a-icon-alt span
            $rating = 'N/A';
            if (preg_match('#<span[^>]*class="[^"]*a-icon-alt[^"]*"[^>]*>([\d.]+) out of 5#', $chunk, $rm)) {
                $rating = $rm[1];
            }

            if (!empty($title) && !empty($link)) {
                $products[] = [
                    'rank'     => $idx,
                    'category' => $slug,
                    'title'    => $title,
                    'price'    => $price,
                    'rating'   => $rating,
                    'link'     => $link,
                    'image'    => $img,
                    'asin'     => $asin,
                ];
            }
        }

        return array_slice($products, 0, 30);
    }

    // ==================== DEDUP ====================

    public static function product_signature($product) {
        return substr($product['title'], 0, 40);
    }

    public static function filter_new($products) {
        $published = get_option('pinery_flow_published_products', []);
        if (!is_array($published)) $published = [];
        return array_filter($products, function($p) use ($published) {
            return !in_array(self::product_signature($p), $published);
        });
    }

    public static function select_diverse($products, $count) {
        // Group by category
        $by_cat = [];
        foreach ($products as $p) {
            $cat = $p['category'] ?? 'other';
            $by_cat[$cat][] = $p;
        }

        // Shuffle each category's products so picks are random
        foreach ($by_cat as &$cat_products) {
            shuffle($cat_products);
        }
        unset($cat_products);

        // Round-robin pick from categories, cycling until count is met
        $cats = array_keys($by_cat);
        shuffle($cats);

        $selected = [];
        $cat_idx  = 0;
        $loops    = 0;
        $max_loops = $count * count($cats) * 2; // Safety limit

        while (count($selected) < $count && $loops < $max_loops) {
            $loops++;
            $cat = $cats[$cat_idx % count($cats)];
            $cat_idx++;

            // Grab next unpicked product from this category
            if (!empty($by_cat[$cat])) {
                $selected[] = array_shift($by_cat[$cat]);
                continue;
            }

            // All categories exhausted
            $any_left = false;
            foreach ($by_cat as $remaining) {
                if (!empty($remaining)) { $any_left = true; break; }
            }
            if (!$any_left) break;
        }

        return $selected;
    }

    // ==================== AI GENERATION ====================

    /**
     * Unified chat completion — works across xAI, OpenAI, and Gemini.
     */
    public static function chat_completion($messages, $params = [], $api_key = null) {
        if (!$api_key) $api_key = self::get_api_key();
        $config = self::get_provider_config();

        if ($config['format'] === 'openai') {
            return self::openai_chat($config, $messages, $params, $api_key);
        } else {
            return self::gemini_chat($config, $messages, $params, $api_key);
        }
    }

    private static function openai_chat($config, $messages, $params, $api_key) {
        $body = [
            'model'       => $config['text_model'],
            'messages'    => $messages,
            'temperature' => $params['temperature'] ?? 0.8,
        ];
        if (!empty($params['max_tokens'])) $body['max_tokens'] = $params['max_tokens'];

        $response = wp_remote_post($config['base_url'] . '/chat/completions', [
            'timeout' => $params['timeout'] ?? 30,
            'headers' => [
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type'  => 'application/json',
            ],
            'body' => json_encode($body),
        ]);

        if (is_wp_error($response)) return null;
        $body = json_decode(wp_remote_retrieve_body($response), true);
        return $body['choices'][0]['message']['content'] ?? null;
    }

    private static function gemini_chat($config, $messages, $params, $api_key) {
        // Convert OpenAI-format messages to Gemini contents
        $contents = [];
        foreach ($messages as $msg) {
            $role = ($msg['role'] === 'assistant') ? 'model' : 'user';
            $contents[] = ['role' => $role, 'parts' => [['text' => $msg['content']]]];
        }

        $body = [
            'contents'         => $contents,
            'generationConfig' => [
                'temperature'    => $params['temperature'] ?? 0.8,
            ],
        ];
        if (!empty($params['max_tokens'])) {
            $body['generationConfig']['maxOutputTokens'] = $params['max_tokens'];
        }

        $url = $config['base_url'] . '/models/' . $config['text_model'] . ':generateContent?key=' . urlencode($api_key);
        $response = wp_remote_post($url, [
            'timeout' => $params['timeout'] ?? 30,
            'headers' => ['Content-Type' => 'application/json'],
            'body'    => json_encode($body),
        ]);

        if (is_wp_error($response)) return null;
        $body = json_decode(wp_remote_retrieve_body($response), true);
        return $body['candidates'][0]['content']['parts'][0]['text'] ?? null;
    }

    /**
     * Unified image generation (text-to-image) — xAI, OpenAI, Gemini.
     */
    public static function image_generation($prompt, $api_key = null) {
        if (!$api_key) $api_key = self::get_api_key();
        $config = self::get_provider_config();

        if ($config['format'] === 'openai') {
            return self::openai_image_gen($config, $prompt, $api_key);
        } else {
            return self::gemini_image_gen($config, $prompt, $api_key);
        }
    }

    private static function openai_image_gen($config, $prompt, $api_key) {
        $response = wp_remote_post($config['base_url'] . '/images/generations', [
            'timeout' => 120,
            'headers' => [
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type'  => 'application/json',
            ],
            'body' => json_encode([
                'model'  => $config['image_model'],
                'prompt' => $prompt,
                'n'      => 1,
            ]),
        ]);

        if (is_wp_error($response)) return false;
        $body = json_decode(wp_remote_retrieve_body($response), true);
        $url = $body['data'][0]['url'] ?? '';
        return $url ? self::download_image($url) : false;
    }

    private static function gemini_image_gen($config, $prompt, $api_key) {
        $url = $config['base_url'] . '/models/' . $config['image_model'] . ':generateContent?key=' . urlencode($api_key);
        $body = json_encode([
            'contents' => [['parts' => [['text' => $prompt]]]],
            'generationConfig' => ['responseModalities' => ['IMAGE', 'TEXT']],
        ]);

        $max_retries = 3;
        for ($attempt = 0; $attempt < $max_retries; $attempt++) {
            if ($attempt > 0) {
                $wait = pow(2, $attempt);
                self::debug_log('info', 'AI Image', "Rate limited, retrying in {$wait}s (attempt " . ($attempt + 1) . "/{$max_retries})");
                sleep($wait);
            }

            $response = wp_remote_post($url, [
                'timeout' => 120,
                'headers' => ['Content-Type' => 'application/json'],
                'body'    => $body,
            ]);

            if (is_wp_error($response)) {
                self::debug_log('error', 'AI Image', 'Gemini API error: ' . $response->get_error_message());
                return false;
            }
            $status = wp_remote_retrieve_response_code($response);
            $resp_body = json_decode(wp_remote_retrieve_body($response), true);
            if ($status === 429) {
                continue;
            }
            if ($status !== 200) {
                $err = $resp_body['error']['message'] ?? wp_remote_retrieve_body($response);
                self::debug_log('error', 'AI Image', "Gemini HTTP $status: " . (is_string($err) ? substr($err, 0, 300) : json_encode($err)));
                return false;
            }

            $parts = $resp_body['candidates'][0]['content']['parts'] ?? [];
            foreach ($parts as $part) {
                if (!empty($part['inlineData']['data'])) {
                    $image_data = base64_decode($part['inlineData']['data']);
                    $upload_dir = wp_upload_dir();
                    $temp_dir   = $upload_dir['basedir'] . '/pinery-temp';
                    wp_mkdir_p($temp_dir);
                    $file_path = $temp_dir . '/generated_' . uniqid() . '.jpg';
                    file_put_contents($file_path, $image_data);
                    return $file_path;
                }
            }
            return false;
        }
        self::debug_log('error', 'AI Image', 'Gemini image gen failed after ' . $max_retries . ' retries (rate limited)');
        return false;
    }

    /**
     * Unified image edit (img2img) — xAI, OpenAI, Gemini.
     */
    public static function image_edit($source_path, $prompt, $api_key = null) {
        if (!$api_key) $api_key = self::get_api_key();
        $config = self::get_provider_config();

        if ($config['format'] === 'openai') {
            return self::openai_image_edit($config, $source_path, $prompt, $api_key);
        } else {
            return self::gemini_image_edit($config, $source_path, $prompt, $api_key);
        }
    }

    private static function openai_image_edit($config, $source_path, $prompt, $api_key) {
        $image_data = base64_encode(file_get_contents($source_path));
        $ext = pathinfo($source_path, PATHINFO_EXTENSION);
        $mime = ($ext === 'png') ? 'image/png' : 'image/jpeg';

        $response = wp_remote_post($config['base_url'] . '/images/edits', [
            'timeout' => 150,
            'headers' => [
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type'  => 'application/json',
            ],
            'body' => json_encode([
                'model'  => $config['image_model'],
                'prompt' => $prompt,
                'image'  => ['url' => 'data:' . $mime . ';base64,' . $image_data, 'type' => 'image_url'],
                'n'      => 1,
            ]),
        ]);

        if (is_wp_error($response)) return false;
        $body = json_decode(wp_remote_retrieve_body($response), true);
        $url = $body['data'][0]['url'] ?? '';
        return $url ? self::download_image($url) : false;
    }

    private static function gemini_image_edit($config, $source_path, $prompt, $api_key) {
        $image_data = base64_encode(file_get_contents($source_path));
        $ext = pathinfo($source_path, PATHINFO_EXTENSION);
        $mime = ($ext === 'png') ? 'image/png' : 'image/jpeg';

        $url = $config['base_url'] . '/models/' . $config['image_model'] . ':generateContent?key=' . urlencode($api_key);
        $body = json_encode([
            'contents' => [[
                'parts' => [
                    ['text' => $prompt],
                    ['inlineData' => ['mimeType' => $mime, 'data' => $image_data]],
                ],
            ]],
            'generationConfig' => ['responseModalities' => ['IMAGE', 'TEXT']],
        ]);

        $max_retries = 3;
        for ($attempt = 0; $attempt < $max_retries; $attempt++) {
            if ($attempt > 0) {
                $wait = pow(2, $attempt);
                self::debug_log('info', 'AI Image', "Rate limited, retrying in {$wait}s (attempt " . ($attempt + 1) . "/{$max_retries})");
                sleep($wait);
            }

            $response = wp_remote_post($url, [
                'timeout' => 150,
                'headers' => ['Content-Type' => 'application/json'],
                'body'    => $body,
            ]);

            if (is_wp_error($response)) {
                self::debug_log('error', 'AI Image', 'Gemini edit API error: ' . $response->get_error_message());
                return false;
            }
            $status = wp_remote_retrieve_response_code($response);
            $resp_body = json_decode(wp_remote_retrieve_body($response), true);
            if ($status === 429) {
                continue;
            }
            if ($status !== 200) {
                $err = $resp_body['error']['message'] ?? wp_remote_retrieve_body($response);
                self::debug_log('error', 'AI Image', "Gemini edit HTTP $status: " . (is_string($err) ? substr($err, 0, 300) : json_encode($err)));
                return false;
            }

            $parts = $resp_body['candidates'][0]['content']['parts'] ?? [];
            foreach ($parts as $part) {
                if (!empty($part['inlineData']['data'])) {
                    $img_data = base64_decode($part['inlineData']['data']);
                    $upload_dir = wp_upload_dir();
                    $temp_dir   = $upload_dir['basedir'] . '/pinery-temp';
                    wp_mkdir_p($temp_dir);
                    $file_path = $temp_dir . '/generated_' . uniqid() . '.jpg';
                    file_put_contents($file_path, $img_data);
                    return $file_path;
                }
            }
            return false;
        }
        self::debug_log('error', 'AI Image', 'Gemini image edit failed after ' . $max_retries . ' retries (rate limited)');
        return false;
    }

    /**
     * Generate a unique lifestyle scene description on-the-fly.
     * This avoids the repetition problem of hardcoded scene lists.
     */
    public static function generate_scene($product_title, $category, $api_key) {
        $cat_name = str_replace('-', ' ', $category);
        $prompt = sprintf(
            "You are a commercial product photographer. Suggest 1 beautiful scene for: \"%s\" (%s). " .
            "For physical products: lifestyle setting with location, lighting, mood. " .
            "For media/digital: appealing display context. For food/consumables: appetizing presentation. " .
            "Be specific and fresh. Max 20 words. Output only the description, no formatting.",
            $product_title, $cat_name
        );

        $result = self::chat_completion(
            [['role' => 'user', 'content' => $prompt]],
            ['temperature' => 0.9, 'max_tokens' => 60, 'timeout' => 30],
            $api_key
        );

        if (empty($result)) return 'bright natural daylight, outdoor lifestyle setting';
        return rtrim(trim($result), '. ') . '.';
    }

    /**
     * Generate product copy (title + description) via Grok.
     */
    public static function generate_copy($product, $api_key) {
        $content = self::chat_completion(
            [[
                'role'    => 'user',
                'content' => sprintf(
                    "You are a product copywriter. Write engaging product copy:\n\n" .
                    "Product: %s\nPrice: %s\nRating: %s\n\n" .
                    "Output format:\nTITLE: (max 8 words, engaging)\nDESCRIPTION: (80-100 words, highlight benefits, natural tone, " .
                    "end with 5 relevant hashtags on one line prefixed with #, no asterisks)",
                    $product['title'], $product['price'], $product['rating']
                ),
            ]],
            ['temperature' => 0.8, 'timeout' => 30],
            $api_key
        );

        if (empty($content)) {
            self::debug_log('warning', 'AI Copy', 'Chat returned empty, using fallback copy');
            return ['Fashion Find', 'Check out this amazing product on Amazon!'];
        }

        $title = 'Fashion Find';
        $desc  = 'Check out this amazing product on Amazon!';

        if (preg_match('/TITLE:\s*(.+)/i', $content, $tm)) {
            $title = trim(preg_replace('/[*#]/', '', $tm[1]));
        }
        if (preg_match('/DESCRIPTION:\s*(.+)/is', $content, $dm)) {
            $desc = trim(preg_replace('/[*]/', '', $dm[1]));
        }

        self::debug_log('info', 'AI Copy', sprintf('Copy generated — title: "%s", desc: %d chars, %d words', $title, strlen($desc), str_word_count($desc)));

        return [$title, $desc];
    }

    // ==================== IMAGE GENERATION ====================

    /**
     * Generate a lifestyle image via xAI Grok Imagine.
     * First tries img2img (garment on model), falls back to text2img.
     */
    public static function generate_image($product, $api_key) {
        $scene     = self::generate_scene($product['title'], $product['category'], $api_key);
        $light_var = ['golden hour sunlight', 'soft natural daylight', 'dramatic sunset glow', 'bright morning light', 'warm afternoon sun'];
        $light     = $light_var[array_rand($light_var)];

        self::debug_log('info', 'AI Image', sprintf('Scene: %s | Lighting: %s', $scene, $light));

        $upload_dir = wp_upload_dir();
        $temp_dir   = $upload_dir['basedir'] . '/pinery-temp';
        wp_mkdir_p($temp_dir);

        $source_path = '';
        $image_url   = $product['image'] ?? '';

        // Download source image
        if (!empty($image_url)) {
            $source_path = $temp_dir . '/source_' . uniqid() . '.jpg';
            $img_response = wp_remote_get($image_url, ['timeout' => 20]);
            if (!is_wp_error($img_response)) {
                file_put_contents($source_path, wp_remote_retrieve_body($img_response));
                // Resize to target ratio
                self::resize_image($source_path);
                self::debug_log('info', 'AI Image', 'Source image downloaded, attempting img2img');
            } else {
                self::debug_log('warning', 'AI Image', 'Failed to download source image, will use text2img');
            }
        } else {
            self::debug_log('info', 'AI Image', 'No source image URL, using text2img');
        }

        // Try img2img
        if (!empty($source_path) && file_exists($source_path)) {
            $result = self::img_to_img($source_path, $scene, $light, $api_key);
            if ($result) {
                self::debug_log('info', 'AI Image', 'img2img succeeded');
                return $result;
            }
            self::debug_log('warning', 'AI Image', 'img2img failed, falling back to text2img');
        }

        // Fallback: text2img
        self::debug_log('info', 'AI Image', 'Using text2img');
        return self::text_to_img($product['title'], $scene, $light, $api_key);
    }

    public static function img_to_img($source_path, $scene, $light, $api_key) {
        $prompt = sprintf(
            "Take this product from the photo and naturally present it in this lifestyle scene: %s, %s. " .
            "Keep the exact color, pattern, and details of the original product. " .
            "Professional commercial photography, high-end catalog style, 8k resolution, sharp focus, Pinterest aesthetic.",
            $scene, $light
        );
        return self::image_edit($source_path, $prompt, $api_key);
    }

    public static function text_to_img($title, $scene, $light, $api_key) {
        $prompt = sprintf(
            "Professional commercial product photography of %s. " .
            "Beautifully presented in %s, %s. " .
            "High-end catalog style, sharp focus, 8k resolution, " .
            "clean composition, Pinterest aesthetic.",
            $title, $scene, $light
        );
        return self::image_generation($prompt, $api_key);
    }

    public static function download_image($url) {
        $upload_dir = wp_upload_dir();
        $temp_dir   = $upload_dir['basedir'] . '/pinery-temp';
        wp_mkdir_p($temp_dir);

        $file_path = $temp_dir . '/generated_' . uniqid() . '.jpg';
        $response  = wp_remote_get($url, ['timeout' => 40]);

        if (is_wp_error($response)) return false;

        file_put_contents($file_path, wp_remote_retrieve_body($response));
        return $file_path;
    }

    public static function resize_image($path, $target_ratio = null) {
        if (!$target_ratio) {
            $target_ratio = get_option('pinery_flow_image_ratio', '9:16');
            list($w, $h) = explode(':', $target_ratio);
            $target_ratio = (float)$w / (float)$h;
        }

        if (!function_exists('wp_get_image_editor')) return;

        $editor = wp_get_image_editor($path);
        if (is_wp_error($editor)) return;

        $size = $editor->get_size();
        $current_ratio = $size['width'] / $size['height'];

        if (abs($current_ratio - $target_ratio) < 0.01) return; // Already correct

        if ($current_ratio > $target_ratio) {
            // Too wide — crop horizontally
            $new_width = (int)($size['height'] * $target_ratio);
            $x = (int)(($size['width'] - $new_width) / 2);
            $editor->crop($x, 0, $new_width, $size['height']);
        } else {
            // Too tall — pad vertically with white
            $new_height = (int)($size['width'] / $target_ratio);
            // We can't easily pad with GD, so just save as is
            return;
        }

        $editor->save($path);
    }

    /**
     * Add semi-transparent overlay with price (left) and stars (right) at bottom of image.
     */
    public static function add_price_overlay($image_path, $price, $rating) {
        if (!function_exists('imagecreatefromjpeg')) return $image_path;

        $img = @imagecreatefromjpeg($image_path);
        if (!$img) return $image_path;

        $width  = imagesx($img);
        $height = imagesy($img);
        $bar_h  = max(60, (int)($height * 0.10)); // ~10% of image height

        imagealphablending($img, true);
        imagesavealpha($img, true);

        // Draw semi-transparent dark gradient bar at the bottom
        // Use multiple rectangles from transparent to opaque
        $steps = 20;
        $y_start = $height - $bar_h;
        for ($i = 0; $i < $steps; $i++) {
            $alpha = 100 - (int)(60 * $i / $steps); // fade from transparent to more opaque
            $y1 = $y_start + (int)($bar_h * $i / $steps);
            $y2 = $y_start + (int)($bar_h * ($i + 1) / $steps);
            $dark = imagecolorallocatealpha($img, 0, 0, 0, max(0, min(127, $alpha)));
            imagefilledrectangle($img, 0, $y1, $width, $y2, $dark);
            imagecolordeallocate($img, $dark);
        }

        // Solid dark bar at the very bottom for text background
        $solid = imagecolorallocatealpha($img, 0, 0, 0, 50);
        imagefilledrectangle($img, 0, $height - (int)($bar_h * 0.65), $width, $height, $solid);
        imagecolordeallocate($img, $solid);

        $font = '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf';

        // Price — left side
        $price_text = $price;
        $price_size = max(14, (int)($bar_h * 0.42));
        $price_x    = max(20, (int)($width * 0.03));
        $price_y    = $height - (int)($bar_h * 0.18);
        $white      = imagecolorallocate($img, 255, 255, 255);
        imagettftext($img, $price_size, 0, $price_x, $price_y, $white, $font, $price_text);
        imagecolordeallocate($img, $white);

        // Star rating — right side
        $rate_text = $rating . ' ★';
        $rate_size = max(12, (int)($bar_h * 0.32));
        $rate_box  = imagettfbbox($rate_size, 0, $font, $rate_text);
        $rate_w    = $rate_box[2] - $rate_box[0];
        $rate_x    = max($price_x + 200, $width - $rate_w - max(20, (int)($width * 0.03)));
        $rate_y    = $height - (int)($bar_h * 0.2);
        $gold      = imagecolorallocate($img, 255, 215, 0);
        imagettftext($img, $rate_size, 0, $rate_x, $rate_y, $gold, $font, $rate_text);
        imagecolordeallocate($img, $gold);

        imagealphablending($img, false);
        imagesavealpha($img, false);

        imagejpeg($img, $image_path, 92);
        imagedestroy($img);

        return $image_path;
    }

    public static function cleanup_temp_images() {
        $upload_dir = wp_upload_dir();
        $temp_dir   = $upload_dir['basedir'] . '/pinery-temp';
        if (!is_dir($temp_dir)) return;

        $files = glob($temp_dir . '/*');
        $now   = time();
        foreach ($files as $f) {
            if ($now - filemtime($f) > 3600) { // older than 1 hour
                @unlink($f);
            }
        }
    }

    // ==================== WORDPRESS POST CREATION ====================

    public static function process_product($product, $api_key, $amazon_tag) {
        // 1. Generate copy
        list($pin_title, $pin_desc) = self::generate_copy($product, $api_key);

        // 2. Generate image (or use Amazon original as hotlink)
        $amazon_image = false;
        if (get_option('pinery_flow_image_source', 'ai') === 'amazon' && !empty($product['image'])) {
            // Store URL only — never download Amazon product photos (API TOS).
            $amazon_image = $product['image'];
            $image_path   = $product['image']; // URL string, not local path
            self::debug_log('info', 'Image', 'Using Amazon original image (hotlink)');
        } else {
            $image_path = self::generate_image($product, $api_key);
        }

        if (empty($image_path)) {
            self::debug_log('error', 'AI Image', sprintf('Image generation failed: %s', mb_substr($product['title'], 0, 50)));
            return new WP_Error('no_image', __('Failed to generate image.', 'pinery-flow'));
        }
        // Only check file_exists for AI-generated local files
        if (!$amazon_image && !file_exists($image_path)) {
            self::debug_log('error', 'AI Image', sprintf('Generated image missing: %s', $image_path));
            return new WP_Error('no_image', __('Failed to generate image.', 'pinery-flow'));
        }

        // 2.5. If Creators API is configured, refresh price in real-time
        if (Pinery_Creators_API::is_available() && !empty($product['asin']) && $product['asin'] !== 'N/A') {
            $live_price = Pinery_Creators_API::get_item_price($product['asin']);
            if ($live_price && $live_price !== 'N/A') {
                $product['price'] = $live_price;
                $product['price_updated'] = time();
                self::debug_log('info', 'Creators API', 'Live price: ' . $live_price);
            }
        }

        // 3. Build affiliate link
        $asin = '';
        if (preg_match('/\/dp\/([A-Z0-9]+)/', $product['link'], $m)) {
            $asin = $m[1];
        }
        $affiliate_link = $asin ? "https://www.amazon.com/dp/{$asin}?tag={$amazon_tag}" : $product['link'];

        // 4. Build post content
        $content = self::build_post_content($product, $pin_desc, $affiliate_link);

        // 5. SEO metadata
        $auto_seo = get_option('pinery_flow_auto_seo', 1);
        $seo_meta = [];
        if ($auto_seo) {
            $seo_meta['_pinery_seo_title']       = $pin_title . ' — Best Price & Review';
            $seo_meta['_pinery_seo_description'] = mb_substr(wp_strip_all_tags($pin_desc), 0, 155);
            $seo_meta['_pinery_seo_keyword']     = self::extract_focus_keyword($product);
            $seo_meta['rank_math_title']         = $seo_meta['_pinery_seo_title'];
            $seo_meta['rank_math_description']   = $seo_meta['_pinery_seo_description'];
            $seo_meta['rank_math_focus_keyword'] = $seo_meta['_pinery_seo_keyword'];
        }

        // 6. Create post
        $post_status = get_option('pinery_flow_post_status', 'publish');
        $post_data   = [
            'post_title'   => $pin_title,
            'post_content' => $content,
            'post_status'  => $post_status,
            'post_author'  => (int) get_option('pinery_flow_default_author', 1),
            'post_category'=> self::get_category_ids($product['category']),
            'tags_input'   => self::extract_hashtags($pin_desc),
            'meta_input'   => array_merge([
                'affiliate_url'           => esc_url_raw($affiliate_link),
                '_pinery_flow_product'    => $product,
                '_pinery_amazon_image'    => $amazon_image ? esc_url_raw($amazon_image) : '',
            ], $seo_meta),
        ];

        $post_id = wp_insert_post($post_data, true);
        if (is_wp_error($post_id)) return $post_id;

        // 6. Set featured image (skip for Amazon hotlink — stored as meta)
        if (!$amazon_image) {
            self::set_featured_image($post_id, $image_path, $pin_title);
        }

        // 7. Pinterest
        if (get_option('pinery_flow_pinterest_enabled', 0)) {
            self::publish_to_pinterest($post_id, $product);
        }

        return $post_id;
    }

    public static function build_post_content($product, $description, $affiliate_link) {
        $btn_text = get_option('pinery_flow_btn_text', 'Shop on Amazon');
        $price    = esc_html($product['price']);
        $rating   = esc_html($product['rating']);
        $desc     = wp_kses_post($description);

        $html  = "<!-- wp:paragraph -->\n<p>{$desc}</p>\n<!-- /wp:paragraph -->\n\n";
        $html .= "<!-- wp:paragraph -->\n<p>";
        $html .= '<strong>' . esc_html__('Price:', 'pinery-flow') . "</strong> {$price} &nbsp;|&nbsp; ";
        $html .= '<strong>' . esc_html__('Rating:', 'pinery-flow') . "</strong> {$rating}";
        $html .= "</p>\n<!-- /wp:paragraph -->\n\n";
        // Price freshness indicator — updated by frontend AJAX when stale
        $price_ts = $product['price_updated'] ?? time();
        $time_ago = human_time_diff($price_ts, time());
        $asin_attr = esc_attr($product['asin'] ?? '');
        $html .= "<!-- wp:paragraph -->\n<p class=\"pinery-price-meta\" data-asin=\"{$asin_attr}\" data-price-ts=\"{$price_ts}\">";
        $html .= '<small>' . sprintf(esc_html__('Price updated %s ago', 'pinery-flow'), $time_ago) . '</small>';
        $html .= "</p>\n<!-- /wp:paragraph -->\n\n";
        $html .= "<!-- wp:button -->\n";
        $html .= '<div class="wp-block-button"><a class="wp-block-button__link" href="' . esc_url($affiliate_link) . '" target="_blank" rel="nofollow sponsored noopener">' . esc_html($btn_text) . '</a></div>';
        $html .= "\n<!-- /wp:button -->";

        return $html;
    }

    public static function set_featured_image($post_id, $image_path, $title) {
        if (!file_exists($image_path)) return;

        $upload_dir = wp_upload_dir();
        $filename   = 'pinery-' . sanitize_file_name(substr($title, 0, 40)) . '-' . $post_id . '.jpg';
        $dest       = $upload_dir['path'] . '/' . $filename;

        copy($image_path, $dest);

        $attachment = [
            'post_mime_type' => 'image/jpeg',
            'post_title'     => sanitize_text_field($title),
            'post_content'   => '',
            'post_status'    => 'inherit',
        ];

        $attach_id = wp_insert_attachment($attachment, $dest, $post_id);

        if (!is_wp_error($attach_id)) {
            require_once ABSPATH . 'wp-admin/includes/image.php';
            $metadata = wp_generate_attachment_metadata($attach_id, $dest);
            wp_update_attachment_metadata($attach_id, $metadata);
            set_post_thumbnail($post_id, $attach_id);
        }

        @unlink($image_path);
    }

    public static function get_category_ids($category_slug) {
        $tree = self::amazon_departments();

        // Match known department IDs by longest prefix — avoids splitting
        // multi-hyphen IDs like "home-garden" or "sporting-goods" incorrectly.
        $dept_id = '';
        foreach (array_keys($tree) as $id) {
            if ($category_slug === $id || strpos($category_slug, $id . '-') === 0) {
                if (strlen($id) > strlen($dept_id)) {
                    $dept_id = $id;
                }
            }
        }

        // Get department display name from tree, or format slug as fallback
        $dept_name = ($dept_id !== '')
            ? $tree[$dept_id]['name']
            : ucwords(str_replace('-', ' ', $category_slug));

        // 1) Try exact slug match (e.g., sanitize_title("Clothing, Shoes & Jewelry") → "clothing-shoes-jewelry")
        $cat = get_category_by_slug(sanitize_title($dept_name));
        if ($cat) {
            return [$cat->term_id];
        }

        // 2) Try case-insensitive name match among existing categories
        $cats = get_categories(['hide_empty' => false]);
        foreach ($cats as $cat) {
            if (strtolower($cat->name) === strtolower($dept_name)) {
                return [$cat->term_id];
            }
        }

        // 3) Auto-create category to match department
        $result = wp_insert_term($dept_name, 'category', ['slug' => sanitize_title($dept_name)]);
        if (!is_wp_error($result)) {
            self::debug_log('info', 'Category', "Auto-created category: {$dept_name}");
            return [$result['term_id']];
        }

        // 4) If wp_insert_term fails (e.g., slug collision), try the existing term
        if (isset($result->error_data['term_exists'])) {
            return [$result->error_data['term_exists']];
        }

        return [];
    }

    public static function extract_hashtags($text) {
        preg_match_all('/#(\w+)/', $text, $matches);
        return $matches[1] ?? [];
    }

    public static function extract_focus_keyword($product) {
        $title = $product['title'] ?? '';
        $cat   = $product['category'] ?? '';
        // Stop words to remove
        $stop = ['for','the','and','with','your','this','that','from','best','top','new','our','its','you','are','has','been','was','can','will','all','most','more','buy','get','set','use','used','high','low','big','small','pro','max','mini','plus','ultra','lite'];
        $words = preg_split('/[\s,\-–—()\[\]{}]+/', strtolower($title), -1, PREG_SPLIT_NO_EMPTY);
        $words = array_diff($words, $stop);
        // Pick 3-5 meaningful words
        $kw = array_slice($words, 0, 4);
        // Prepend category if available
        if ($cat && !in_array($cat, $kw)) {
            array_unshift($kw, $cat);
        }
        return implode(' ', array_slice($kw, 0, 5));
    }

    public static function get_asin($url) {
        if (preg_match('/\/dp\/([A-Z0-9]+)/', $url, $m)) return $m[1];
        return null;
    }

    /**
     * Get product gallery images for an ASIN — always hotlinked, never scraped on frontend.
     * Delegates to Creators API (which caches for 1 h via transient).
     *
     * @param string $asin
     * @return string[]  Array of image URLs
     */
    public static function get_product_gallery($asin) {
        if (empty($asin) || $asin === 'N/A') return [];
        if (!class_exists('Pinery_Creators_API') || !Pinery_Creators_API::is_available()) return [];
        return Pinery_Creators_API::get_product_images($asin);
    }

    // ==================== PINTEREST ====================

    public static function get_pinterest_token() {
        $token = get_option('pinery_flow_pinterest_token', '');
        if (empty($token)) return '';

        $expiry = (int) get_option('pinery_flow_pinterest_token_expiry', 0);
        if ($expiry && $expiry < time() + 60) {
            return self::refresh_pinterest_token() ?: $token;
        }
        return $token;
    }

    public static function refresh_pinterest_token() {
        $refresh = get_option('pinery_flow_pinterest_refresh_token', '');
        if (empty($refresh)) return '';

        $app_id     = get_option('pinery_flow_pinterest_app_id', '');
        $app_secret = get_option('pinery_flow_pinterest_app_secret', '');

        $body = ['grant_type' => 'refresh_token', 'refresh_token' => $refresh];

        if ($app_secret) {
            $response = wp_remote_post('https://api.pinterest.com/v5/oauth/token', [
                'headers' => [
                    'Authorization' => 'Basic ' . base64_encode($app_id . ':' . $app_secret),
                    'Content-Type'  => 'application/x-www-form-urlencoded',
                ],
                'body' => http_build_query($body),
                'timeout' => 15,
            ]);
        } else {
            $body['client_id'] = $app_id;
            $response = wp_remote_post('https://api.pinterest.com/v5/oauth/token', [
                'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
                'body'    => http_build_query($body),
                'timeout' => 15,
            ]);
        }

        if (is_wp_error($response)) return '';

        $body = json_decode(wp_remote_retrieve_body($response), true);
        if (!empty($body['access_token'])) {
            update_option('pinery_flow_pinterest_token', sanitize_text_field($body['access_token']));
            if (!empty($body['refresh_token'])) {
                update_option('pinery_flow_pinterest_refresh_token', sanitize_text_field($body['refresh_token']));
            }
            if (!empty($body['expires_in'])) {
                update_option('pinery_flow_pinterest_token_expiry', time() + (int) $body['expires_in']);
            }
            self::debug_log('info', 'Pinterest', 'Token refreshed');
            return $body['access_token'];
        }
        return '';
    }

    public static function publish_to_pinterest($post_id, $product) {
        $board = get_option('pinery_flow_pinterest_board', '');
        if (empty($board)) return false;

        $token = self::get_pinterest_token();
        if (empty($token)) return false;

        $post      = get_post($post_id);
        $image_id  = get_post_thumbnail_id($post_id);
        $image_url = wp_get_attachment_image_url($image_id, 'full');
        $aff_url   = get_post_meta($post_id, 'affiliate_url', true);

        if (empty($image_url)) {
            self::debug_log('warning', 'Pinterest', 'No featured image, skipping Pin');
            return false;
        }

        $response = wp_remote_post('https://api.pinterest.com/v5/pins', [
            'headers' => [
                'Authorization' => 'Bearer ' . $token,
                'Content-Type'  => 'application/json',
            ],
            'body' => json_encode([
                'title'        => $post->post_title,
                'description'  => wp_trim_words(wp_strip_all_tags($post->post_content), 50, ''),
                'link'         => $aff_url ?: get_permalink($post_id),
                'board_id'     => $board,
                'media_source' => [
                    'source_type' => 'image_url',
                    'url'         => $image_url,
                ],
            ]),
            'timeout' => 30,
        ]);

        if (is_wp_error($response)) {
            self::debug_log('error', 'Pinterest', 'HTTP error: ' . $response->get_error_message());
            return false;
        }

        $body  = json_decode(wp_remote_retrieve_body($response), true);
        $code  = wp_remote_retrieve_response_code($response);

        if ($code >= 200 && $code < 300 && !empty($body['id'])) {
            update_post_meta($post_id, '_pinery_pinterest_id', $body['id']);
            self::debug_log('info', 'Pinterest', sprintf('Pin created: %s', $body['id']));
            return $body['id'];
        }

        $err = $body['message'] ?? sprintf('HTTP %d', $code);
        self::debug_log('error', 'Pinterest', sprintf('Failed: %s', $err), json_encode($body));
        return false;
    }
}
