<?php

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

class AIEO_DMM_Promo_Surfaces extends AIEO_DMM_Module_Base {

    const BLOCK_NAME = 'aieo/promo-surface';

    public function get_id()       { return 'promo_surfaces'; }
    public function get_label()    { return __('Promo surfaces', 'ai-eshop-optimizer'); }
    public function get_priority() { return 60; }
    public function get_group()    { return 'campaigns'; }

    
    public function enqueue_admin_assets() {
        if (function_exists('wp_enqueue_media')) {
            wp_enqueue_media();
        }
        
        
        
        wp_enqueue_script('jquery-ui-sortable');
    }

    public function register_hooks() {
        
        
        
        
        $schema = AIEO_DIR . 'includes/dmm/sql/promo-surfaces-schema.php';
        if (file_exists($schema)) {
            require_once $schema;
            if (function_exists('aieo_dmm_install_promo_surfaces_schema')) {
                aieo_dmm_install_promo_surfaces_schema();
            }
        }

        
        add_action('wp_ajax_aieo_promo_save_surface', array($this, 'ajax_save_surface'));
        add_action('wp_ajax_aieo_promo_save_slot',    array($this, 'ajax_save_slot'));
        add_action('wp_ajax_aieo_promo_delete_slot',  array($this, 'ajax_delete_slot'));
        add_action('wp_ajax_aieo_promo_reorder',      array($this, 'ajax_reorder'));

        
        
        
        
        add_action('init', array($this, 'register_block'));

        
        
        
        
        add_shortcode('aieo_promo_surface', array($this, 'shortcode'));

        
        
        
        
        
        
        add_action('enqueue_block_editor_assets', array($this, 'enqueue_block_editor_assets'));

        
        
        add_action('wp_footer', array($this, 'maybe_emit_tracker'), 50);
    }

    
    
    

    public static function tbl_surfaces() { global $wpdb; return $wpdb->prefix . 'aieo_dmm_promo_surfaces'; }
    public static function tbl_slots()    { global $wpdb; return $wpdb->prefix . 'aieo_dmm_promo_slots'; }

    public static function get_surface_by_slug($slug) {
        global $wpdb;
        $tbl = self::tbl_surfaces();
        
        return $wpdb->get_row($wpdb->prepare("SELECT * FROM {$tbl} WHERE slug = %s LIMIT 1", $slug), ARRAY_A);
    }

    public static function get_surface($surface_id) {
        global $wpdb;
        $tbl = self::tbl_surfaces();
        
        return $wpdb->get_row($wpdb->prepare("SELECT * FROM {$tbl} WHERE surface_id = %d LIMIT 1", (int) $surface_id), ARRAY_A);
    }

    public static function list_surfaces() {
        global $wpdb;
        $tbl = self::tbl_surfaces();
        
        return $wpdb->get_results("SELECT * FROM {$tbl} ORDER BY label, slug", ARRAY_A) ?: array();
    }

    public static function list_slots($surface_id) {
        global $wpdb;
        $tbl = self::tbl_slots();
        
        return $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$tbl} WHERE surface_id = %d ORDER BY position, sort_order, slot_id",
            (int) $surface_id
        ), ARRAY_A) ?: array();
    }

    
    
    

    public function ajax_save_surface() {
        AIEO_DMM::verify_ajax_request();
        global $wpdb;

        $surface_id = isset($_POST['surface_id']) ? (int) $_POST['surface_id'] : 0;
        $slug       = sanitize_key(wp_unslash($_POST['slug'] ?? ''));
        $label      = sanitize_text_field(wp_unslash($_POST['label'] ?? ''));
        $layout     = sanitize_key(wp_unslash($_POST['layout_preset'] ?? 'hero_plus_3banners'));
        $is_active  = !empty($_POST['is_active']) ? 1 : 0;

        if ($slug === '') { wp_send_json_error(array('message' => __('Slug is required.', 'ai-eshop-optimizer'))); }

        $allowed_layouts = array('hero_plus_3banners', 'hero_only', 'banners_only');
        if (!in_array($layout, $allowed_layouts, true)) { $layout = 'hero_plus_3banners'; }

        $tbl = self::tbl_surfaces();
        $data = array(
            'slug'          => $slug,
            'label'         => $label,
            'layout_preset' => $layout,
            'is_active'     => $is_active,
        );

        if ($surface_id > 0) {
            $wpdb->update($tbl, $data, array('surface_id' => $surface_id));
        } else {
            $wpdb->insert($tbl, $data);
            $surface_id = (int) $wpdb->insert_id;
        }

        wp_send_json_success(array(
            'message'    => __('Surface saved.', 'ai-eshop-optimizer'),
            'surface_id' => $surface_id,
        ));
    }

    public function ajax_save_slot() {
        AIEO_DMM::verify_ajax_request();
        global $wpdb;

        $slot_id    = isset($_POST['slot_id']) ? (int) $_POST['slot_id'] : 0;
        $surface_id = isset($_POST['surface_id']) ? (int) sanitize_text_field( wp_unslash( $_POST['surface_id'] ) ) : 0;
        if ($surface_id <= 0) { wp_send_json_error(array('message' => __('Missing surface_id.', 'ai-eshop-optimizer'))); }

        $position = max(0, (int) (sanitize_text_field( wp_unslash( $_POST['position'] ) ) ?? 0));
        $type     = sanitize_key(wp_unslash($_POST['slot_type'] ?? 'slide'));
        if (!in_array($type, array('slide', 'banner'), true)) { $type = 'slide'; }

        $data = array(
            'surface_id'       => $surface_id,
            'position'         => $position,
            'slot_type'        => $type,
            'sort_order'       => max(0, (int) (sanitize_text_field( wp_unslash( $_POST['sort_order'] ) ) ?? 0)),
            'desktop_image_id' => max(0, (int) (sanitize_text_field( wp_unslash( $_POST['desktop_image_id'] ) ) ?? 0)),
            'mobile_image_id'  => max(0, (int) (sanitize_text_field( wp_unslash( $_POST['mobile_image_id'] ) ) ?? 0)),
            'youtube_id'       => sanitize_text_field(wp_unslash($_POST['youtube_id'] ?? '')),
            'target_url'       => esc_url_raw(wp_unslash($_POST['target_url'] ?? '')),
            'alt_text'         => sanitize_text_field(wp_unslash($_POST['alt_text'] ?? '')),
            'time_delay_ms'    => max(0, (int) (sanitize_text_field( wp_unslash( $_POST['time_delay_ms'] ) ) ?? 4000)),
            'campaign_id'      => max(0, (int) (sanitize_text_field( wp_unslash( $_POST['campaign_id'] ) ) ?? 0)),
            'is_active'        => !empty($_POST['is_active']) ? 1 : 0,
        );

        $tbl = self::tbl_slots();
        if ($slot_id > 0) {
            $wpdb->update($tbl, $data, array('slot_id' => $slot_id));
        } else {
            $wpdb->insert($tbl, $data);
            $slot_id = (int) $wpdb->insert_id;
        }

        wp_send_json_success(array('message' => __('Slot saved.', 'ai-eshop-optimizer'), 'slot_id' => $slot_id));
    }

    public function ajax_delete_slot() {
        AIEO_DMM::verify_ajax_request();
        global $wpdb;
        $slot_id = isset($_POST['slot_id']) ? (int) $_POST['slot_id'] : 0;
        if ($slot_id <= 0) { wp_send_json_error(array('message' => 'missing slot_id')); }
        $wpdb->delete(self::tbl_slots(), array('slot_id' => $slot_id));
        wp_send_json_success(array('message' => __('Slot deleted.', 'ai-eshop-optimizer')));
    }

    
    public function ajax_reorder() {
        AIEO_DMM::verify_ajax_request();
        global $wpdb;
        $raw = isset($_POST['order']) ? wp_unslash((string) sanitize_text_field( wp_unslash( $_POST['order'] ) )) : '';
        $rows = $raw !== '' ? json_decode($raw, true) : null;
        if (!is_array($rows)) { wp_send_json_error(array('message' => 'bad payload')); }
        $tbl = self::tbl_slots();
        foreach ($rows as $r) {
            if (!is_array($r)) { continue; }
            $slot_id    = isset($r['slot_id']) ? (int) $r['slot_id'] : 0;
            $sort_order = isset($r['sort_order']) ? (int) $r['sort_order'] : 0;
            if ($slot_id > 0) {
                $wpdb->update($tbl, array('sort_order' => $sort_order), array('slot_id' => $slot_id));
            }
        }
        wp_send_json_success(array('message' => __('Order updated.', 'ai-eshop-optimizer')));
    }

    
    
    

    
    public function enqueue_block_editor_assets() {
        $handle = 'aieo-promo-surface-editor';
        if (wp_script_is($handle, 'registered')) {
            wp_enqueue_script($handle);
        }
    }

    public function register_block() {
        if (!function_exists('register_block_type')) { return; }

        
        
        
        
        $editor_handle = 'aieo-promo-surface-editor';
        $editor_rel    = 'assets/js/aieo-promo-surface-editor.js';
        $editor_path   = AIEO_DIR . $editor_rel;
        $editor_url    = plugins_url($editor_rel, AIEO_FILE);
        $editor_ver    = file_exists($editor_path) ? filemtime($editor_path) : (defined('AIEO_VERSION') ? AIEO_VERSION : false);
        if (file_exists($editor_path)) {
            wp_register_script(
                $editor_handle,
                $editor_url,
                
                
                
                
                
                
                array('wp-blocks', 'wp-block-editor', 'wp-components', 'wp-element', 'wp-i18n'),
                $editor_ver,
                true
            );

            
            
            
            
            
            
            $options = array();
            foreach (self::list_surfaces() as $s) {
                $options[] = array(
                    'value' => (string) $s['slug'],
                    'label' => sprintf('%s (%s)',
                        $s['label'] !== '' ? $s['label'] : $s['slug'],
                        $s['slug']
                    ),
                    'is_active' => (int) $s['is_active'],
                );
            }
            wp_localize_script($editor_handle, 'aieoPromoSurfaceConfig', array(
                'surfaces' => $options,
            ));
        }

        register_block_type(self::BLOCK_NAME, array(
            'api_version'     => 3,
            'title'           => __('AIEO Promo Surface', 'ai-eshop-optimizer'),
            'category'        => 'theme',
            'icon'            => 'megaphone',
            'description'     => __('Hero carousel + 3 banner row, driven by Promo surfaces config under Campaigns.', 'ai-eshop-optimizer'),
            'keywords'        => array('hero', 'banner', 'carousel', 'promo'),
            'attributes'      => array(
                'surfaceSlug' => array('type' => 'string', 'default' => ''),
                'align'       => array('type' => 'string'),
            ),
            'supports'        => array(
                'align'      => array('wide', 'full'),
                'spacing'    => array('margin' => true, 'padding' => true),
                'typography' => array('fontSize' => true, 'lineHeight' => true),
                'color'      => array('background' => true, 'text' => true),
            ),
            'editor_script'   => $editor_handle,
            'render_callback' => array($this, 'render_block'),
        ));
    }

    
    public function render_block($attrs = array(), $content = '', $block = null) {
        $slug = isset($attrs['surfaceSlug']) ? sanitize_key((string) $attrs['surfaceSlug']) : '';
        if ($slug === '') {
            return is_admin() ? '<div class="aieo-promo-surface aieo-promo-surface--empty">'
                . esc_html__('No surface selected — pick one in the block sidebar.', 'ai-eshop-optimizer')
                . '</div>' : '';
        }

        $surface = self::get_surface_by_slug($slug);
        if (!$surface || (int) $surface['is_active'] !== 1) { return ''; }

        $slots = self::list_slots((int) $surface['surface_id']);
        
        
        $by_position = array();
        foreach ($slots as $slot) {
            if ((int) $slot['is_active'] !== 1) { continue; }
            $p = (int) $slot['position'];
            if (!isset($by_position[$p])) { $by_position[$p] = array(); }
            $by_position[$p][] = $slot;
        }
        if (empty($by_position)) { return ''; }

        $layout = (string) $surface['layout_preset'];
        $hero   = isset($by_position[0]) ? $by_position[0] : array();
        $banners = array();
        for ($i = 1; $i <= 3; $i++) {
            if (isset($by_position[$i]) && !empty($by_position[$i])) {
                $banners[$i] = $by_position[$i][0]; 
            }
        }

        $surface_slug = (string) $surface['slug'];
        
        
        
        
        $align     = isset($attrs['align']) ? sanitize_html_class((string) $attrs['align']) : '';
        $align_cls = ($align === 'full') ? ' alignfull' : (($align === 'wide') ? ' alignwide' : '');
        
        
        
        $base_cls = 'aieo-promo-surface aieo-promo-surface--' . $layout;
        if (function_exists('get_block_wrapper_attributes') && $block instanceof WP_Block) {
            $wrapper_attr = get_block_wrapper_attributes(array('class' => $base_cls, 'data-aieo-promo-surface' => $surface_slug));
        } else {
            $wrapper_attr = 'class="' . esc_attr($base_cls . $align_cls) . '" data-aieo-promo-surface="' . esc_attr($surface_slug) . '"';
        }
        
        
        
        static $css_emitted = false;
        ob_start();
        if (!$css_emitted) {
            $css_emitted = true;
            $this->emit_layout_css();
        }
        ?>
        <div <?php echo $wrapper_attr; ?>>
            <?php if ($layout !== 'banners_only' && !empty($hero)) : ?>
                <div class="aieo-promo-surface__hero">
                    <?php $this->render_hero($hero, $surface_slug); ?>
                </div>
            <?php endif; ?>

            <?php if ($layout !== 'hero_only' && !empty($banners)) : ?>
                <div class="aieo-promo-surface__banners">
                    <?php foreach ($banners as $pos => $slot) : ?>
                        <?php $this->render_banner_slot($slot, $surface_slug, $pos); ?>
                    <?php endforeach; ?>
                </div>
            <?php endif; ?>
        </div>
        <?php
        return (string) ob_get_clean();
    }

    
    public function shortcode($atts = array()) {
        $atts = shortcode_atts(array('slug' => '', 'surface' => ''), $atts, 'aieo_promo_surface');
        $slug = $atts['slug'] !== '' ? $atts['slug'] : $atts['surface'];
        if ($slug === '') { return ''; }
        return $this->render_block(array('surfaceSlug' => (string) $slug));
    }

    
    private function render_hero(array $slots, $surface_slug) {
        $count = count($slots);
        $uid   = 'aieo-promo-hero-' . sanitize_key($surface_slug);
        
        
        $first       = $slots[0];
        $preload_url = $first['desktop_image_id'] ? (string) wp_get_attachment_image_url((int) $first['desktop_image_id'], 'large') : '';
        if ($preload_url !== '') {
            printf('<link rel="preload" as="image" href="%s" media="(min-width:768px)">' . "\n", esc_url($preload_url));
        }
        ?>
        <div id="<?php echo esc_attr($uid); ?>"
             class="aieo-promo-hero"
             data-slide-count="<?php echo (int) $count; ?>">
            <div class="aieo-promo-hero__track">
                <?php foreach ($slots as $i => $slot) : ?>
                    <?php $this->render_slide_or_banner($slot, $surface_slug, 0, $i); ?>
                <?php endforeach; ?>
            </div>
            <?php if ($count > 1) : ?>
                <div class="aieo-promo-hero__bullets" aria-hidden="true"></div>
            <?php endif; ?>
        </div>
        <?php
    }

    private function render_banner_slot(array $slot, $surface_slug, $position) {
        ?>
        <div class="aieo-promo-banner aieo-promo-banner--pos-<?php echo (int) $position; ?>">
            <?php $this->render_slide_or_banner($slot, $surface_slug, (int) $position, 0); ?>
        </div>
        <?php
    }

    
    private function render_slide_or_banner(array $slot, $surface_slug, $position, $index) {
        $slot_id     = (int) $slot['slot_id'];
        $campaign_id = (int) $slot['campaign_id'];
        $eager       = ($position === 0 && $index === 0);

        $alt        = (string) $slot['alt_text'];
        $target_url = (string) $slot['target_url'];
        if ($target_url !== '' && $campaign_id > 0) {
            $target_url = $this->stamp_utm_campaign($target_url, $campaign_id);
        }

        $delay_ms = max(0, (int) $slot['time_delay_ms']);
        $youtube  = trim((string) $slot['youtube_id']);

        $tag_open  = $target_url !== ''
            ? sprintf('<a href="%s" data-aieo-promo-slot="%d" data-aieo-promo-surface="%s" data-aieo-promo-pos="%d">',
                esc_url($target_url), $slot_id, esc_attr($surface_slug), $position)
            : sprintf('<div data-aieo-promo-slot="%d" data-aieo-promo-surface="%s" data-aieo-promo-pos="%d">',
                $slot_id, esc_attr($surface_slug), $position);
        $tag_close = $target_url !== '' ? '</a>' : '</div>';

        echo '<div class="aieo-promo-frame" data-delay="' . (int) $delay_ms . '">';
        echo $tag_open; 

        if ($youtube !== '') {
            
            
            
            
            printf(
                '<iframe class="aieo-promo-youtube" src="https://www.youtube-nocookie.com/embed/%s?modestbranding=1&rel=0" loading="%s" allow="autoplay; encrypted-media" allowfullscreen></iframe>',
                esc_attr($youtube),
                $eager ? 'eager' : 'lazy'
            );
        } else {
            $this->render_picture($slot, $eager, $alt);
        }

        echo $tag_close; 
        echo '</div>';
    }

    
    private function render_picture(array $slot, $eager, $alt) {
        $desktop_id = (int) $slot['desktop_image_id'];
        $mobile_id  = (int) $slot['mobile_image_id'];
        if ($desktop_id <= 0 && $mobile_id <= 0) { return; }

        $desktop = $desktop_id ? wp_get_attachment_image_src($desktop_id, 'full') : null;
        $mobile  = $mobile_id  ? wp_get_attachment_image_src($mobile_id,  'full') : null;
        if (!$desktop && !$mobile) { return; }

        $main = $desktop ?: $mobile;
        $loading = $eager ? 'eager' : 'lazy';
        $fp_attr = $eager ? ' fetchpriority="high"' : '';

        echo '<picture>';
        if ($mobile) {
            printf('<source media="(max-width:767px)" srcset="%s">', esc_url((string) $mobile[0]));
        }
        if ($desktop) {
            printf('<source media="(min-width:768px)" srcset="%s">', esc_url((string) $desktop[0]));
        }
        printf(
            '<img src="%s" alt="%s" width="%d" height="%d" loading="%s" decoding="async"%s>',
            esc_url((string) $main[0]),
            esc_attr($alt),
            (int) $main[1],
            (int) $main[2],
            esc_attr($loading),
            $fp_attr 
        );
        echo '</picture>';
    }

    
    private function emit_layout_css() {
        ?>
        <style id="aieo-promo-surface-css">
        .aieo-promo-surface{display:block;width:100%}
        /* Full-width override: when the block is set to "Full width", force a
           full-bleed that escapes the theme's constrained content container.
           Negative side-margins expand the element to the full viewport
           regardless of the parent's max-width (classic OR block theme). */
        /* width:100vw (not just max-width) is required so the surface fills the
           viewport even when it is NOT a direct child of the constrained root —
           e.g. when the page content is rendered through wp:post-content (a
           programmable front page). Without an explicit width the element keeps
           width:100% of its (root-padding-inset) parent and leaves a side gap. */
        .aieo-promo-surface.alignfull{width:100vw;max-width:100vw;margin-left:calc(50% - 50vw);margin-right:calc(50% - 50vw)}
        .aieo-promo-surface__hero{position:relative}
        /* `touch-action:pan-y` lets the browser keep handling vertical
         * page scroll while we own horizontal swipes — without this,
         * iOS/Android Safari can hijack the swipe and refuse to fire
         * pointermove past the first few pixels. */
        .aieo-promo-hero{position:relative;overflow:hidden;width:100%;touch-action:pan-y;user-select:none}
        .aieo-promo-hero__track{display:flex;transition:transform 600ms ease;will-change:transform}
        .aieo-promo-hero__track.is-grabbing{transition:none;cursor:grabbing}
        .aieo-promo-hero .aieo-promo-frame{flex:0 0 100%;width:100%;position:relative}
        .aieo-promo-frame > a,.aieo-promo-frame > div{display:block;width:100%}
        .aieo-promo-frame picture,.aieo-promo-frame img{display:block;width:100%;height:auto}
        .aieo-promo-frame .aieo-promo-youtube{display:block;width:100%;aspect-ratio:16/9;border:0}
        .aieo-promo-hero__bullets{position:absolute;left:50%;bottom:14px;transform:translateX(-50%);display:flex;gap:8px;z-index:2}
        .aieo-promo-hero__bullet{width:10px;height:10px;border-radius:50%;background:rgba(255,255,255,.55);border:1px solid rgba(0,0,0,.15);cursor:pointer;padding:0;transition:background-color 150ms ease,transform 150ms ease}
        .aieo-promo-hero__bullet.is-active{background:rgba(0,0,0,.7);transform:scale(1.15)}
        .aieo-promo-surface__banners{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;margin-top:16px}
        /* Lock each banner to a uniform 4:3 tile and crop any image
         * inside to fill it. Operators can upload landscape, portrait,
         * or square assets without breaking row alignment — the image
         * is centred and cropped via object-fit:cover instead of
         * stretching the row to its tallest member. */
        .aieo-promo-banner{height:280px;overflow:hidden;border-radius:4px;background:#f6f6f6}
        .aieo-promo-banner > .aieo-promo-frame,
        .aieo-promo-banner > .aieo-promo-frame > a,
        .aieo-promo-banner > .aieo-promo-frame > div{display:block;width:100%;height:100%}
        .aieo-promo-banner picture{display:block;width:100%;height:100%}
        .aieo-promo-banner picture img{width:100%;height:100%;object-fit:cover;object-position:center}
        .aieo-promo-banner .aieo-promo-youtube{width:100%;height:100%;aspect-ratio:auto}
        @media (max-width:767px){
            .aieo-promo-surface__banners{grid-template-columns:1fr}
            /* On mobile the banners stack — reset the desktop fixed height
             * and switch to 16:9 so each banner is wider than tall and the
             * column doesn't become an awkward portrait stack. */
            .aieo-promo-banner{height:auto;aspect-ratio:16/9}
            .aieo-promo-hero__bullets{bottom:8px}
            .aieo-promo-hero__bullet{width:8px;height:8px}
        }
        /* Homepage: the lead hero surface should sit flush under the header.
         * It is NOT :first-child of the constrained .entry-content (this very
         * inline <style> takes that slot), so it picks up the 24px constrained
         * block-gap from `:root :where(.is-layout-constrained) > *`; <main>
         * likewise carries the root-level block-gap. Zero both — scoped to the
         * front page (body.home) so no other page or surface placement loses
         * its spacing. Unlayered + class-specific, so it beats the :where rules. */
        body.home main{margin-block-start:0}
        body.home .aieo-promo-surface{margin-block-start:0}
        </style>
        <?php
    }

    private function stamp_utm_campaign($url, $campaign_id) {
        if (!class_exists('AIEO_DMM_Campaigns')) { return $url; }
        
        $slug = method_exists('AIEO_DMM_Campaigns', 'slug_for_id')
            ? (string) AIEO_DMM_Campaigns::slug_for_id((int) $campaign_id)
            : '';
        if ($slug === '') { $slug = 'cmp' . (int) $campaign_id; }
        return add_query_arg(array('utm_campaign' => $slug, 'utm_source' => 'promo_surface'), $url);
    }

    
    
    
    

    public function maybe_emit_tracker() {
        static $emitted = false;
        if ($emitted || is_admin()) { return; }
        
        
        
        
        $emitted = true;
        $ajax = admin_url('admin-ajax.php');
        ?>
        <script>
        (function(){
            if (!document.querySelector('[data-aieo-promo-surface]')) { return; }
            var ajaxUrl = <?php echo wp_json_encode($ajax); ?>;
            function uuid() {
                return (window.crypto && crypto.randomUUID) ? crypto.randomUUID()
                    : ('p-' + Date.now() + '-' + Math.random().toString(16).slice(2));
            }
            // Telemetry sender — one sendBeacon POST per batch. Kept OUT of
            // the page-load critical chain: impressions are queued and flushed
            // on pagehide / visibilitychange (tab close / navigate), NEVER on
            // first paint or scroll. Synthetic perf tools scroll the whole page
            // (GTmetrix full-page capture, Lighthouse programmatic scroll), so
            // firing impressions on intersection would land these beacons in
            // the measurement window and inflate "fully loaded". Clicks send
            // immediately (the user is leaving the page anyway) and drag any
            // pending impressions along with them.
            function sendEvents(events) {
                if (!events || !events.length) { return; }
                try {
                    var fd = new FormData();
                    fd.set('action', 'aieo_reco_log_batch');
                    fd.set('events', JSON.stringify(events));
                    if (navigator.sendBeacon) { navigator.sendBeacon(ajaxUrl, fd); }
                    else { fetch(ajaxUrl, { method:'POST', credentials:'same-origin', body: fd, keepalive:true }); }
                } catch(e) {}
            }
            function mkEvent(type, slotId, surface) {
                return { type: type, surface: 'promo_' + surface,
                    target_pid: slotId, anchor_pid: 0,
                    position: 0, ts_offset_ms: 0, event_id: uuid() };
            }
            var impQueue = [];
            function flushImpressions() {
                if (impQueue.length) { sendEvents(impQueue.splice(0, impQueue.length)); }
            }
            // Impression: first time a slot enters the viewport — queued, not
            // sent. Cap at 30 to bound payload on very long browsing sessions.
            var seen = new WeakSet();
            var io = new IntersectionObserver(function(entries){
                entries.forEach(function(en){
                    if (!en.isIntersecting) { return; }
                    var el = en.target;
                    if (seen.has(el)) { return; }
                    seen.add(el);
                    var slot = parseInt(el.getAttribute('data-aieo-promo-slot'), 10) || 0;
                    var surf = el.getAttribute('data-aieo-promo-surface') || '';
                    if (slot > 0) {
                        impQueue.push(mkEvent('promo_impression', slot, surf));
                        if (impQueue.length >= 30) { flushImpressions(); }
                    }
                    io.unobserve(el);
                });
            }, { rootMargin: '0px', threshold: 0.5 });
            document.querySelectorAll('[data-aieo-promo-slot]').forEach(function(el){ io.observe(el); });
            // Click: capture before navigation — sent immediately, riding along
            // with any pending impressions in a single beacon.
            document.addEventListener('click', function(ev){
                var el = ev.target.closest('[data-aieo-promo-slot]');
                if (!el) { return; }
                var slot = parseInt(el.getAttribute('data-aieo-promo-slot'), 10) || 0;
                var surf = el.getAttribute('data-aieo-promo-surface') || '';
                if (slot > 0) { impQueue.push(mkEvent('promo_click', slot, surf)); flushImpressions(); }
            }, true);
            // Lifecycle flushes — record impressions on bounce visits without
            // ever firing during the load/scroll measurement window.
            window.addEventListener('pagehide', flushImpressions);
            document.addEventListener('visibilitychange', function(){
                if (document.visibilityState === 'hidden') { flushImpressions(); }
            });

            // Carousel — auto-advance + bullets + pointer-swipe + infinite
            // loop. Cloning the first slide and snapping back without
            // animation when we hit the clone gives a seamless loop.
            // Per-slide delay comes from the slide's data-delay attr.
            function initHero(hero) {
                if (hero.__aieoInit) { return; }
                hero.__aieoInit = true;
                var track = hero.querySelector('.aieo-promo-hero__track');
                var bullets = hero.querySelector('.aieo-promo-hero__bullets');
                if (!track) { return; }
                var slides = Array.prototype.slice.call(track.children);
                if (slides.length < 2) { return; }

                // Clone first slide for seamless loop.
                var clone = slides[0].cloneNode(true);
                // Make clone images eager so the loop-back doesn't show
                // a blank frame on the wraparound.
                clone.querySelectorAll('img').forEach(function(i){ i.loading = 'eager'; });
                // Strip data-aieo-promo-slot from clone so we don't double-count
                // impressions on it (the original already fires).
                clone.querySelectorAll('[data-aieo-promo-slot]').forEach(function(n){
                    n.removeAttribute('data-aieo-promo-slot');
                });
                track.appendChild(clone);
                var realCount = slides.length;
                var current = 0, timer = null;

                // Build bullet dots.
                if (bullets) {
                    for (var i = 0; i < realCount; i++) {
                        var b = document.createElement('button');
                        b.type = 'button';
                        b.className = 'aieo-promo-hero__bullet' + (i === 0 ? ' is-active' : '');
                        b.setAttribute('aria-label', 'Slide ' + (i + 1));
                        b.dataset.idx = i;
                        bullets.appendChild(b);
                    }
                    bullets.addEventListener('click', function(ev){
                        var btn = ev.target.closest('.aieo-promo-hero__bullet');
                        if (!btn) { return; }
                        clearTimeout(timer);
                        goTo(parseInt(btn.dataset.idx, 10) || 0, true);
                        schedule();
                    });
                }

                function update(idx, animate) {
                    track.style.transition = animate ? 'transform 600ms ease' : 'none';
                    track.style.transform = 'translateX(-' + (idx * 100) + '%)';
                    if (bullets) {
                        var active = idx % realCount;
                        bullets.querySelectorAll('.aieo-promo-hero__bullet').forEach(function(b, j){
                            b.classList.toggle('is-active', j === active);
                        });
                    }
                }
                function goTo(i, animate) { current = i; update(current, animate); }
                function schedule() {
                    clearTimeout(timer);
                    var slideIdx = current % realCount;
                    var delay = parseInt(slides[slideIdx] && slides[slideIdx].getAttribute('data-delay'), 10) || 4000;
                    timer = setTimeout(function(){
                        current++;
                        update(current, true);
                    }, delay);
                }

                // Snap-back when we land on the clone, without animation.
                track.addEventListener('transitionend', function(){
                    if (current === realCount) {
                        current = 0;
                        update(current, false);
                    }
                    schedule();
                });

                // Pointer-swipe.
                var down = false, startX = 0;
                hero.addEventListener('pointerdown', function(ev){
                    down = true; startX = ev.pageX;
                    track.classList.add('is-grabbing');
                    clearTimeout(timer);
                });
                hero.addEventListener('pointermove', function(ev){
                    if (!down) { return; }
                    var dx = ev.pageX - startX;
                    track.style.transform = 'translateX(' + (-current * 100 + (dx / hero.clientWidth) * 100) + '%)';
                });
                ['pointerup','pointerleave','pointercancel'].forEach(function(evt){
                    hero.addEventListener(evt, function(ev){
                        if (!down) { return; }
                        down = false;
                        track.classList.remove('is-grabbing');
                        var dx = (ev.pageX || 0) - startX;
                        var thresh = hero.clientWidth * 0.2;
                        if (dx < -thresh) { current++; }
                        else if (dx > thresh) { current = current > 0 ? current - 1 : 0; }
                        update(current, true);
                    });
                });

                // Kick off.
                update(0, false);
                schedule();
            }
            document.querySelectorAll('.aieo-promo-hero').forEach(initHero);
        })();
        </script>
        <?php
    }

    
    
    

    public function render_subtab() {
        $action     = isset($_GET['promo_action']) ? sanitize_key($_GET['promo_action']) : '';
        $surface_id = isset($_GET['surface_id']) ? (int) $_GET['surface_id'] : 0;

        if ($action === 'edit' && $surface_id > 0) {
            $this->render_edit_view($surface_id);
        } elseif ($action === 'new') {
            $this->render_edit_view(0);
        } else {
            $this->render_list_view();
        }
    }

    
    private function render_reports_card() {
        global $wpdb;

        $reco_tbl     = $wpdb->prefix . 'aieo_reco_events';
        $surfaces_tbl = self::tbl_surfaces();
        $slots_tbl    = self::tbl_slots();
        $attr_tbl     = $wpdb->prefix . 'aieo_dmm_offer_attributions';

        $window_days = 30;
        $since       = gmdate('Y-m-d H:i:s', time() - $window_days * DAY_IN_SECONDS);

        
        
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT SUBSTRING(surface, 7) AS slug,
                    SUM(CASE WHEN event_type = 'promo_impression' THEN 1 ELSE 0 END) AS impressions,
                    SUM(CASE WHEN event_type = 'promo_click'      THEN 1 ELSE 0 END) AS clicks
             FROM {$reco_tbl}
             WHERE surface LIKE 'promo\\_%' ESCAPE '\\\\'
               AND event_type IN ('promo_impression','promo_click')
               AND ts >= %s
             GROUP BY surface",
            $since
        ), ARRAY_A);

        $by_slug = array();
        if (is_array($rows)) {
            foreach ($rows as $r) {
                $by_slug[(string) $r['slug']] = array(
                    'impressions' => (int) $r['impressions'],
                    'clicks'      => (int) $r['clicks'],
                );
            }
        }

        
        
        $surfaces = $wpdb->get_results(
            "SELECT s.surface_id, s.slug, s.label, s.is_active,
                    COUNT(sl.slot_id) AS slot_count,
                    SUM(CASE WHEN sl.is_active = 1 THEN 1 ELSE 0 END) AS active_slots
             FROM {$surfaces_tbl} s
             LEFT JOIN {$slots_tbl} sl ON sl.surface_id = s.surface_id
             GROUP BY s.surface_id, s.slug, s.label, s.is_active
             ORDER BY s.label, s.slug",
            ARRAY_A
        ) ?: array();

        $fmt_pct = function ($num, $denom) {
            $denom = (int) $denom;
            return $denom > 0 ? round(($num / $denom) * 100, 1) . '%' : '—';
        };
        ?>
        <div class="aieo-dmm-card" style="margin-top:24px;padding:20px;border:1px solid #c3c4c7;background:#fff;">
            <h3 style="margin-top:0;">
                <?php
                
                echo esc_html(sprintf(__('Promo-surface performance — last %d days', 'ai-eshop-optimizer'), $window_days));
                ?>
            </h3>
            <p class="description" style="margin-top:0;">
                <?php esc_html_e('Impressions fire on first viewport intersection of any slot in a surface; clicks fire before navigation. Both flow into the shared wp_aieo_reco_events table — same plumbing as exit-intent and product-card analytics.', 'ai-eshop-optimizer'); ?>
            </p>
            <table class="widefat striped" style="max-width:880px;">
                <thead>
                    <tr>
                        <th><?php esc_html_e('Surface', 'ai-eshop-optimizer'); ?></th>
                        <th><?php esc_html_e('Status', 'ai-eshop-optimizer'); ?></th>
                        <th style="text-align:right;"><?php esc_html_e('Slots', 'ai-eshop-optimizer'); ?></th>
                        <th style="text-align:right;"><?php esc_html_e('Impressions', 'ai-eshop-optimizer'); ?></th>
                        <th style="text-align:right;"><?php esc_html_e('Clicks', 'ai-eshop-optimizer'); ?></th>
                        <th style="text-align:right;"><?php esc_html_e('CTR', 'ai-eshop-optimizer'); ?></th>
                    </tr>
                </thead>
                <tbody>
                <?php
                $totals = array('slots' => 0, 'active' => 0, 'impressions' => 0, 'clicks' => 0);
                if (empty($surfaces)) :
                ?>
                    <tr><td colspan="6"><em><?php esc_html_e('No surfaces yet. Add one above to start collecting data.', 'ai-eshop-optimizer'); ?></em></td></tr>
                <?php else : foreach ($surfaces as $s) :
                    $slug = (string) $s['slug'];
                    $stats = isset($by_slug[$slug]) ? $by_slug[$slug] : array('impressions' => 0, 'clicks' => 0);
                    $totals['slots']       += (int) $s['slot_count'];
                    $totals['active']      += (int) $s['active_slots'];
                    $totals['impressions'] += (int) $stats['impressions'];
                    $totals['clicks']      += (int) $stats['clicks'];
                ?>
                    <tr>
                        <td>
                            <strong><?php echo esc_html((string) ($s['label'] !== '' ? $s['label'] : $slug)); ?></strong><br>
                            <code style="font-size:11px;color:#646970;"><?php echo esc_html($slug); ?></code>
                        </td>
                        <td><?php echo (int) $s['is_active'] ? '✅' : '—'; ?></td>
                        <td style="text-align:right;">
                            <?php echo (int) $s['active_slots']; ?>
                            <?php if ((int) $s['slot_count'] > (int) $s['active_slots']) : ?>
                                <span style="color:#646970;">/<?php echo (int) $s['slot_count']; ?></span>
                            <?php endif; ?>
                        </td>
                        <td style="text-align:right;"><?php echo (int) $stats['impressions']; ?></td>
                        <td style="text-align:right;"><?php echo (int) $stats['clicks']; ?></td>
                        <td style="text-align:right;"><?php echo esc_html($fmt_pct($stats['clicks'], $stats['impressions'])); ?></td>
                    </tr>
                <?php endforeach; ?>
                    <tr style="font-weight:600;background:#f6f7f7;">
                        <td><?php esc_html_e('Total', 'ai-eshop-optimizer'); ?></td>
                        <td>—</td>
                        <td style="text-align:right;"><?php echo (int) $totals['active']; ?>
                            <?php if ($totals['slots'] > $totals['active']) : ?>
                                <span style="color:#646970;">/<?php echo (int) $totals['slots']; ?></span>
                            <?php endif; ?>
                        </td>
                        <td style="text-align:right;"><?php echo (int) $totals['impressions']; ?></td>
                        <td style="text-align:right;"><?php echo (int) $totals['clicks']; ?></td>
                        <td style="text-align:right;"><?php echo esc_html($fmt_pct($totals['clicks'], $totals['impressions'])); ?></td>
                    </tr>
                <?php endif; ?>
                </tbody>
            </table>
            <p class="description" style="margin-top:12px;">
                <?php esc_html_e('CTR = clicks ÷ impressions. Slots column shows active / total when some slots are disabled. To break clicks down by slot or campaign, query wp_aieo_reco_events directly (target_pid carries the slot_id) or wp_aieo_dmm_offer_attributions filtered by entry_channel = the surface\'s utm slug.', 'ai-eshop-optimizer'); ?>
            </p>
        </div>
        <?php
    }

    private function render_list_view() {
        $surfaces = self::list_surfaces();
        $new_url  = add_query_arg(array(
            'page'        => AIEO_DMM::MENU_SLUG,
            'dmm_tab'     => $this->get_id(),
            'promo_action'=> 'new',
        ), admin_url('admin.php'));
        ?>
        <div class="aieo-dmm-section">
            <h2><?php esc_html_e('Promo surfaces', 'ai-eshop-optimizer'); ?></h2>
            <p class="description"><?php esc_html_e('Operator-curated promo placements (homepage hero + 3 banner row, category top, etc.). On a block theme, drop the “Promo Surface” block onto a template and pick a surface. On a LEGACY (classic) theme, copy the shortcode shown next to each surface below — e.g. [aieo_promo_surface slug="mai-hero"] — into a template (via do_shortcode), a text widget, or a page-builder module. Both paths render identically.', 'ai-eshop-optimizer'); ?></p>
            <p>
                <a href="<?php echo esc_url($new_url); ?>" class="button button-primary"><?php esc_html_e('Add new surface', 'ai-eshop-optimizer'); ?></a>
            </p>
            <table class="widefat striped">
                <thead>
                    <tr>
                        <th><?php esc_html_e('Slug', 'ai-eshop-optimizer'); ?></th>
                        <th><?php esc_html_e('Label', 'ai-eshop-optimizer'); ?></th>
                        <th><?php esc_html_e('Layout', 'ai-eshop-optimizer'); ?></th>
                        <th><?php esc_html_e('Active', 'ai-eshop-optimizer'); ?></th>
                        <th><?php esc_html_e('Updated', 'ai-eshop-optimizer'); ?></th>
                        <th></th>
                    </tr>
                </thead>
                <tbody>
                <?php if (empty($surfaces)) : ?>
                    <tr><td colspan="6"><em><?php esc_html_e('No surfaces yet — click “Add new surface” to create the first one.', 'ai-eshop-optimizer'); ?></em></td></tr>
                <?php else : foreach ($surfaces as $s) :
                    $edit_url = add_query_arg(array(
                        'page' => AIEO_DMM::MENU_SLUG,
                        'dmm_tab' => $this->get_id(),
                        'promo_action' => 'edit',
                        'surface_id' => (int) $s['surface_id'],
                    ), admin_url('admin.php'));
                ?>
                    <tr>
                        <td><code><?php echo esc_html($s['slug']); ?></code><br>
                            <code class="aieo-promo-shortcode" style="font-size:11px;color:#646970;user-select:all;cursor:text;" title="<?php esc_attr_e('Legacy-theme shortcode — click to select, then copy', 'ai-eshop-optimizer'); ?>">[aieo_promo_surface slug="<?php echo esc_attr($s['slug']); ?>"]</code></td>
                        <td><?php echo esc_html($s['label']); ?></td>
                        <td><code><?php echo esc_html($s['layout_preset']); ?></code></td>
                        <td><?php echo (int) $s['is_active'] ? '✅' : '—'; ?></td>
                        <td><?php echo esc_html((string) $s['updated_at']); ?></td>
                        <td><a href="<?php echo esc_url($edit_url); ?>" class="button button-small"><?php esc_html_e('Edit', 'ai-eshop-optimizer'); ?></a></td>
                    </tr>
                <?php endforeach; endif; ?>
                </tbody>
            </table>

            <?php $this->render_reports_card(); ?>
        </div>
        <?php
    }

    private function render_edit_view($surface_id) {
        $surface = $surface_id > 0 ? self::get_surface($surface_id) : null;
        if ($surface_id > 0 && !$surface) {
            echo '<div class="notice notice-error"><p>' . esc_html__('Surface not found.', 'ai-eshop-optimizer') . '</p></div>';
            $this->render_list_view();
            return;
        }
        $slots = $surface_id > 0 ? self::list_slots($surface_id) : array();
        $nonce = wp_create_nonce(AIEO_DMM::NONCE_ACTION);
        $ajax  = admin_url('admin-ajax.php');
        $list_url = add_query_arg(array(
            'page' => AIEO_DMM::MENU_SLUG, 'dmm_tab' => $this->get_id(),
        ), admin_url('admin.php'));

        $defaults = array(
            'surface_id'    => 0,
            'slug'          => '',
            'label'         => '',
            'layout_preset' => 'hero_plus_3banners',
            'is_active'     => 1,
        );
        $s = is_array($surface) ? array_merge($defaults, $surface) : $defaults;

        
        
        global $wpdb;
        $campaigns_tbl = $wpdb->prefix . 'aieo_dmm_campaigns';
        
        $campaigns = $wpdb->get_results("SELECT id, slug, label FROM {$campaigns_tbl} ORDER BY label, slug", ARRAY_A) ?: array();
        ?>
        <div class="aieo-dmm-section">
            <p><a href="<?php echo esc_url($list_url); ?>">← <?php esc_html_e('All surfaces', 'ai-eshop-optimizer'); ?></a></p>
            <h2><?php echo $surface_id ? esc_html__('Edit surface', 'ai-eshop-optimizer') : esc_html__('New surface', 'ai-eshop-optimizer'); ?></h2>

            <form id="aieo-promo-surface-form" class="aieo-dmm-card">
                <input type="hidden" name="surface_id" value="<?php echo (int) $s['surface_id']; ?>">
                <table class="form-table">
                    <tr>
                        <th><?php esc_html_e('Slug', 'ai-eshop-optimizer'); ?></th>
                        <td>
                            <input type="text" name="slug" value="<?php echo esc_attr((string) $s['slug']); ?>" class="regular-text" required>
                            <p class="description"><?php esc_html_e('URL-safe identifier. Used by the block to look this surface up — keep stable.', 'ai-eshop-optimizer'); ?></p>
                        </td>
                    </tr>
                    <tr>
                        <th><?php esc_html_e('Label', 'ai-eshop-optimizer'); ?></th>
                        <td><input type="text" name="label" value="<?php echo esc_attr((string) $s['label']); ?>" class="regular-text"></td>
                    </tr>
                    <tr>
                        <th><?php esc_html_e('Layout', 'ai-eshop-optimizer'); ?></th>
                        <td>
                            <select name="layout_preset">
                                <option value="hero_plus_3banners" <?php selected($s['layout_preset'], 'hero_plus_3banners'); ?>><?php esc_html_e('Hero + 3 banners', 'ai-eshop-optimizer'); ?></option>
                                <option value="hero_only"          <?php selected($s['layout_preset'], 'hero_only'); ?>><?php esc_html_e('Hero only', 'ai-eshop-optimizer'); ?></option>
                                <option value="banners_only"       <?php selected($s['layout_preset'], 'banners_only'); ?>><?php esc_html_e('3 banners only', 'ai-eshop-optimizer'); ?></option>
                            </select>
                        </td>
                    </tr>
                    <tr>
                        <th><?php esc_html_e('Active', 'ai-eshop-optimizer'); ?></th>
                        <td><label><input type="checkbox" name="is_active" value="1" <?php checked((int) $s['is_active'], 1); ?>> <?php esc_html_e('Render on front-end.', 'ai-eshop-optimizer'); ?></label></td>
                    </tr>
                </table>
                <p>
                    <button type="button" class="button button-primary" id="aieo-promo-surface-save"><?php esc_html_e('Save surface', 'ai-eshop-optimizer'); ?></button>
                    <span id="aieo-promo-surface-status" style="margin-left:10px;color:#646970;font-size:13px;"></span>
                </p>
            </form>

            <?php if ($surface_id > 0) : ?>
            <h3 style="margin-top:32px;"><?php esc_html_e('Slots', 'ai-eshop-optimizer'); ?></h3>
            <p class="description">
                <?php esc_html_e('Position 0 = hero carousel (each row is a slide). Positions 1, 2, 3 = the three banner row slots (one slot each, the first active wins).', 'ai-eshop-optimizer'); ?>
            </p>

            <table class="widefat striped" id="aieo-promo-slots-table">
                <thead>
                    <tr>
                        <th style="width:40px;"><?php esc_html_e('☰', 'ai-eshop-optimizer'); ?></th>
                        <th><?php esc_html_e('Pos', 'ai-eshop-optimizer'); ?></th>
                        <th><?php esc_html_e('Type', 'ai-eshop-optimizer'); ?></th>
                        <th><?php esc_html_e('Desktop img id', 'ai-eshop-optimizer'); ?></th>
                        <th><?php esc_html_e('Mobile img id', 'ai-eshop-optimizer'); ?></th>
                        <th><?php esc_html_e('YouTube id', 'ai-eshop-optimizer'); ?></th>
                        <th><?php esc_html_e('Target URL', 'ai-eshop-optimizer'); ?></th>
                        <th><?php esc_html_e('Alt', 'ai-eshop-optimizer'); ?></th>
                        <th><?php esc_html_e('Delay (ms)', 'ai-eshop-optimizer'); ?></th>
                        <th><?php esc_html_e('Campaign', 'ai-eshop-optimizer'); ?></th>
                        <th><?php esc_html_e('Active', 'ai-eshop-optimizer'); ?></th>
                        <th></th>
                    </tr>
                </thead>
                <tbody id="aieo-promo-slots-body">
                <?php
                $rows = $slots;
                $rows[] = array('slot_id' => 0, 'surface_id' => $surface_id, 'position' => 0, 'slot_type' => 'slide',
                    'sort_order' => 0, 'desktop_image_id' => 0, 'mobile_image_id' => 0, 'youtube_id' => '',
                    'target_url' => '', 'alt_text' => '', 'time_delay_ms' => 4000, 'campaign_id' => 0, 'is_active' => 1);
                foreach ($rows as $r) :
                    $is_new = empty($r['slot_id']);
                ?>
                    <tr class="aieo-promo-slot-row" data-slot-id="<?php echo (int) $r['slot_id']; ?>">
                        <td class="aieo-promo-slot-handle" style="cursor:move;color:#999;">⋮⋮</td>
                        <td><input type="number" class="js-slot-position" min="0" max="9" step="1" value="<?php echo (int) $r['position']; ?>" style="width:50px;"></td>
                        <td>
                            <select class="js-slot-type">
                                <option value="slide" <?php selected($r['slot_type'], 'slide'); ?>>slide</option>
                                <option value="banner" <?php selected($r['slot_type'], 'banner'); ?>>banner</option>
                            </select>
                        </td>
                        <td><?php $this->render_media_picker_cell('desktop', (int) $r['desktop_image_id']); ?></td>
                        <td><?php $this->render_media_picker_cell('mobile',  (int) $r['mobile_image_id']);  ?></td>
                        <td><input type="text" class="js-slot-youtube" value="<?php echo esc_attr((string) $r['youtube_id']); ?>" style="width:100px;" placeholder="dQw4w9WgXcQ"></td>
                        <td><input type="url" class="js-slot-url" value="<?php echo esc_attr((string) $r['target_url']); ?>" style="width:200px;" placeholder="https://..."></td>
                        <td><input type="text" class="js-slot-alt" value="<?php echo esc_attr((string) $r['alt_text']); ?>" style="width:120px;"></td>
                        <td><input type="number" class="js-slot-delay" min="0" step="100" value="<?php echo (int) $r['time_delay_ms']; ?>" style="width:80px;"></td>
                        <td>
                            <select class="js-slot-campaign">
                                <option value="0">— none —</option>
                                <?php foreach ($campaigns as $c) : ?>
                                    <option value="<?php echo (int) $c['id']; ?>" <?php selected((int) $r['campaign_id'], (int) $c['id']); ?>>
                                        <?php echo esc_html((string) ($c['label'] ?: $c['slug'])); ?>
                                    </option>
                                <?php endforeach; ?>
                            </select>
                        </td>
                        <td><input type="checkbox" class="js-slot-active" value="1" <?php checked((int) $r['is_active'], 1); ?>></td>
                        <td>
                            <button type="button" class="button button-small js-slot-save"><?php echo $is_new ? esc_html__('Add', 'ai-eshop-optimizer') : esc_html__('Save', 'ai-eshop-optimizer'); ?></button>
                            <?php if (!$is_new) : ?>
                                <button type="button" class="button button-small js-slot-delete" style="color:#b91c1c;">×</button>
                            <?php endif; ?>
                        </td>
                    </tr>
                <?php endforeach; ?>
                </tbody>
            </table>
            <p><span id="aieo-promo-slots-status" style="color:#646970;font-size:13px;"></span></p>
            <?php endif; ?>

            <?php $this->render_admin_js($nonce, $ajax, $surface_id); ?>
        </div>
        <?php
    }

    
    private function render_media_picker_cell($kind, $id) {
        $thumb = $id > 0 ? wp_get_attachment_image_url($id, 'thumbnail') : '';
        $cls   = 'js-slot-' . $kind . '-id';
        ?>
        <div class="aieo-promo-media-picker" data-kind="<?php echo esc_attr($kind); ?>" style="display:flex;align-items:center;gap:6px;">
            <span class="js-slot-thumb" style="display:inline-block;width:48px;height:48px;border:1px solid #c3c4c7;background:#f6f7f7 <?php echo $thumb ? "url('" . esc_url($thumb) . "') center/cover no-repeat" : ''; ?>;border-radius:3px;flex:0 0 48px;"></span>
            <input type="hidden" class="<?php echo esc_attr($cls); ?>" value="<?php echo (int) $id; ?>">
            <span style="display:flex;flex-direction:column;gap:2px;">
                <button type="button" class="button button-small js-slot-pick" data-kind="<?php echo esc_attr($kind); ?>"><?php echo $id > 0 ? esc_html__('Replace', 'ai-eshop-optimizer') : esc_html__('Browse', 'ai-eshop-optimizer'); ?></button>
                <button type="button" class="button-link js-slot-clear" style="font-size:11px;color:#b91c1c;<?php echo $id > 0 ? '' : 'visibility:hidden;'; ?>"><?php esc_html_e('Clear', 'ai-eshop-optimizer'); ?></button>
            </span>
        </div>
        <?php
    }

    private function render_admin_js($nonce, $ajax, $surface_id) {
        ?>
        <script>
        (function(){
            var nonce  = <?php echo wp_json_encode($nonce); ?>;
            var ajax   = <?php echo wp_json_encode($ajax); ?>;
            var surfaceId = <?php echo (int) $surface_id; ?>;

            function post(action, payload, cb){
                var fd = new FormData();
                fd.set('action', action);
                fd.set('nonce', nonce);
                Object.keys(payload).forEach(function(k){ fd.set(k, payload[k]); });
                fetch(ajax, { method:'POST', credentials:'same-origin', body: fd })
                    .then(function(r){ return r.json(); })
                    .then(cb)
                    .catch(function(){ cb && cb({ success:false, data:{ message:'network error' } }); });
            }

            // Surface form save.
            var surfBtn = document.getElementById('aieo-promo-surface-save');
            if (surfBtn) {
                surfBtn.addEventListener('click', function(){
                    var f = document.getElementById('aieo-promo-surface-form');
                    var fd = new FormData(f);
                    var p = {};
                    fd.forEach(function(v,k){ p[k] = v; });
                    if (f.querySelector('[name="is_active"]:not(:checked)')) { p.is_active = ''; }
                    var st = document.getElementById('aieo-promo-surface-status');
                    st.style.color = '#646970'; st.textContent = '<?php echo esc_js(__('Saving…', 'ai-eshop-optimizer')); ?>';
                    post('aieo_promo_save_surface', p, function(j){
                        if (j && j.success) {
                            st.style.color = '#15803d'; st.textContent = j.data.message;
                            if (!surfaceId && j.data.surface_id) {
                                // Reload to expose the slots panel for the new row.
                                var url = new URL(window.location.href);
                                url.searchParams.set('promo_action', 'edit');
                                url.searchParams.set('surface_id', j.data.surface_id);
                                window.location.href = url.toString();
                            }
                        } else {
                            st.style.color = '#b91c1c'; st.textContent = (j && j.data && j.data.message) || 'error';
                        }
                    });
                });
            }

            // Drag-and-drop sort — jQuery UI sortable on the tbody,
            // with the ⋮⋮ cell as the handle. After a drop, send the
            // new sort_order list to the reorder endpoint.
            (function initSortable(){
                if (typeof jQuery === 'undefined' || !jQuery.fn || !jQuery.fn.sortable) {
                    setTimeout(initSortable, 200);
                    return;
                }
                var $body = jQuery('#aieo-promo-slots-body');
                if (!$body.length) { return; }
                $body.sortable({
                    items: '> tr',
                    handle: '.aieo-promo-slot-handle',
                    helper: function(e, tr) {
                        // Preserve cell widths while dragging — without this
                        // jQuery UI shrinks the row to its content width.
                        var $orig = jQuery(tr);
                        var $clone = $orig.clone();
                        $clone.children().each(function(i){
                            jQuery(this).width($orig.children().eq(i).width());
                        });
                        return $clone;
                    },
                    update: function() {
                        var rows = jQuery('#aieo-promo-slots-body > tr').toArray();
                        var order = [];
                        rows.forEach(function(tr, i){
                            var slotId = parseInt(tr.dataset.slotId, 10) || 0;
                            if (slotId > 0) { order.push({ slot_id: slotId, sort_order: i }); }
                        });
                        if (!order.length) { return; }
                        var st = document.getElementById('aieo-promo-slots-status');
                        st.style.color = '#646970'; st.textContent = '<?php echo esc_js(__('Reordering…', 'ai-eshop-optimizer')); ?>';
                        post('aieo_promo_reorder', { order: JSON.stringify(order) }, function(j){
                            if (j && j.success) {
                                st.style.color = '#15803d'; st.textContent = j.data.message;
                            } else {
                                st.style.color = '#b91c1c'; st.textContent = (j && j.data && j.data.message) || 'error';
                            }
                        });
                    }
                });
            })();

            // Media picker — opens wp.media, writes attachment id into
            // the row's hidden input + paints the thumbnail.
            document.addEventListener('click', function(ev){
                var pickBtn  = ev.target.closest('.js-slot-pick');
                var clearBtn = ev.target.closest('.js-slot-clear');
                if (pickBtn) {
                    if (typeof wp === 'undefined' || !wp.media) { return; }
                    var cell = pickBtn.closest('.aieo-promo-media-picker');
                    var kind = pickBtn.dataset.kind || 'desktop';
                    var frame = wp.media({
                        title: kind === 'mobile' ? 'Pick mobile image' : 'Pick desktop image',
                        button: { text: 'Use this image' },
                        library: { type: 'image' },
                        multiple: false
                    });
                    frame.on('select', function(){
                        var att = frame.state().get('selection').first().toJSON();
                        var thumb = (att.sizes && att.sizes.thumbnail) ? att.sizes.thumbnail.url : att.url;
                        cell.querySelector('.js-slot-thumb').style.background =
                            "#f6f7f7 url('" + thumb + "') center/cover no-repeat";
                        var input = cell.querySelector('.js-slot-' + kind + '-id');
                        input.value = att.id;
                        // Hidden inputs don't fire `change` natively, so
                        // dispatch one explicitly — that triggers the
                        // auto-save listener registered above.
                        input.dispatchEvent(new Event('change', { bubbles: true }));
                        pickBtn.textContent = '<?php echo esc_js(__('Replace', 'ai-eshop-optimizer')); ?>';
                        cell.querySelector('.js-slot-clear').style.visibility = 'visible';
                    });
                    frame.open();
                    return;
                }
                if (clearBtn) {
                    var cell2 = clearBtn.closest('.aieo-promo-media-picker');
                    var kind2 = cell2.dataset.kind || 'desktop';
                    cell2.querySelector('.js-slot-thumb').style.background = '#f6f7f7';
                    var input2 = cell2.querySelector('.js-slot-' + kind2 + '-id');
                    input2.value = 0;
                    input2.dispatchEvent(new Event('change', { bubbles: true }));
                    cell2.querySelector('.js-slot-pick').textContent = '<?php echo esc_js(__('Browse', 'ai-eshop-optimizer')); ?>';
                    clearBtn.style.visibility = 'hidden';
                    return;
                }
            });

            // Build the AJAX payload from the current state of a row's
            // inputs. Used by both auto-save (on field change) and the
            // explicit Add button (for the empty trailing row).
            function buildPayload(row) {
                return {
                    slot_id:          row.dataset.slotId || 0,
                    surface_id:       surfaceId,
                    position:         row.querySelector('.js-slot-position').value,
                    slot_type:        row.querySelector('.js-slot-type').value,
                    sort_order:       Array.prototype.indexOf.call(row.parentNode.children, row),
                    desktop_image_id: row.querySelector('.js-slot-desktop-id').value,
                    mobile_image_id:  row.querySelector('.js-slot-mobile-id').value,
                    youtube_id:       row.querySelector('.js-slot-youtube').value,
                    target_url:       row.querySelector('.js-slot-url').value,
                    alt_text:         row.querySelector('.js-slot-alt').value,
                    time_delay_ms:    row.querySelector('.js-slot-delay').value,
                    campaign_id:      row.querySelector('.js-slot-campaign').value,
                    is_active:        row.querySelector('.js-slot-active').checked ? '1' : ''
                };
            }

            function flash(row, color) {
                row.style.transition = 'background-color 200ms ease';
                row.style.backgroundColor = color;
                setTimeout(function(){ row.style.backgroundColor = ''; }, 700);
            }

            function saveRow(row, opts) {
                opts = opts || {};
                var st = document.getElementById('aieo-promo-slots-status');
                if (st) { st.style.color = '#646970'; st.textContent = '<?php echo esc_js(__('Saving…', 'ai-eshop-optimizer')); ?>'; }
                post('aieo_promo_save_slot', buildPayload(row), function(j){
                    if (j && j.success) {
                        flash(row, '#d1fae5');
                        if (st) { st.style.color = '#15803d'; st.textContent = j.data.message; }
                        if (opts.reload) { window.location.reload(); }
                    } else {
                        flash(row, '#fee2e2');
                        if (st) { st.style.color = '#b91c1c'; st.textContent = (j && j.data && j.data.message) || 'error'; }
                    }
                });
            }

            // AUTO-SAVE — any change to an existing slot row's input,
            // select or checkbox commits immediately. The explicit
            // "Add" / "Save" button is kept only for the empty trailing
            // row (creating a brand-new slot needs an explicit step so
            // partial typing doesn't insert empty rows on every blur).
            document.addEventListener('change', function(ev){
                var row = ev.target.closest('.aieo-promo-slot-row');
                if (!row) { return; }
                var slotId = row.dataset.slotId;
                if (!slotId || slotId === '0') { return; } // empty/new row: needs explicit Add
                // Only react to actual editable fields in the row.
                var fields = ['js-slot-position','js-slot-type','js-slot-desktop-id','js-slot-mobile-id',
                              'js-slot-youtube','js-slot-url','js-slot-alt','js-slot-delay',
                              'js-slot-campaign','js-slot-active'];
                var t = ev.target;
                var hit = false;
                for (var i=0; i<fields.length; i++) { if (t.classList.contains(fields[i])) { hit = true; break; } }
                if (!hit) { return; }
                saveRow(row);
            });

            // Click handlers — explicit Add (new row) and Delete.
            document.addEventListener('click', function(ev){
                var row = ev.target.closest('.aieo-promo-slot-row');
                if (!row) { return; }
                if (ev.target.classList.contains('js-slot-save')) {
                    // For new rows we reload after success so the
                    // empty bottom row gets re-rendered with a fresh
                    // slot_id and a new empty trailing row appears.
                    var isNew = !row.dataset.slotId || row.dataset.slotId === '0';
                    saveRow(row, { reload: isNew });
                } else if (ev.target.classList.contains('js-slot-delete')) {
                    if (!confirm('<?php echo esc_js(__('Delete this slot?', 'ai-eshop-optimizer')); ?>')) { return; }
                    post('aieo_promo_delete_slot', { slot_id: row.dataset.slotId }, function(j){
                        if (j && j.success) { row.remove(); }
                    });
                }
            });
        })();
        </script>
        <?php
    }
}
