<?php

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

class AIEO_MOI_Admin {

    private static $instance = null;
    public static function instance() {
        if (self::$instance === null) { self::$instance = new self(); }
        return self::$instance;
    }

    private function __construct() {
        
        add_action('init', array($this, 'register_module'), 5);
        
        add_action('wp_ajax_aieo_moi_save_vendor',        array($this, 'ajax_save_vendor'));
        add_action('wp_ajax_aieo_moi_delete_vendor',      array($this, 'ajax_delete_vendor'));
        add_action('wp_ajax_aieo_moi_rotate_token',       array($this, 'ajax_rotate_token'));
        add_action('wp_ajax_aieo_moi_rederive_token',     array($this, 'ajax_rederive_token_from_rooster'));
        add_action('wp_ajax_aieo_moi_reconcile',          array($this, 'ajax_reconcile'));
        
        
        
        
        add_action('wp_ajax_aieo_moi_reconcile_plan',     array($this, 'ajax_reconcile_plan'));
        add_action('wp_ajax_aieo_moi_reconcile_process',  array($this, 'ajax_reconcile_process'));
        
        
        
        add_action('wp_ajax_aieo_moi_legacy_scan',          array($this, 'ajax_legacy_scan'));
        add_action('wp_ajax_aieo_moi_legacy_migrate_batch', array($this, 'ajax_legacy_migrate_batch'));
    }

    
    public function register_module() {
        if (!class_exists('AIEO_DMM_Module_Base')) { return; }
        if (class_exists('AIEO_DMM_Marketplace_Order_Importer')) { return; }

        
        
        require_once __DIR__ . '/class-aieo-dmm-marketplace-order-importer.php';

        
        
        
        if (method_exists('AIEO_DMM', 'instance')) {
            $dmm = AIEO_DMM::instance();
            if (is_object($dmm) && method_exists($dmm, 'register_module')) {
                $dmm->register_module(new AIEO_DMM_Marketplace_Order_Importer());
            } else {
                
                try {
                    $r = new ReflectionClass($dmm);
                    if ($r->hasProperty('modules')) {
                        $p = $r->getProperty('modules');
                        $p->setAccessible(true);
                        $current = $p->getValue($dmm);
                        if (is_array($current)) {
                            $module = new AIEO_DMM_Marketplace_Order_Importer();
                            $current['marketplace_order_importer'] = $module;
                            $p->setValue($dmm, $current);
                        }
                    }
                    if ($r->hasProperty('subtabs')) {
                        $p = $r->getProperty('subtabs');
                        $p->setAccessible(true);
                        $tabs = $p->getValue($dmm);
                        if (is_array($tabs)) {
                            $tabs[] = array(
                                'id'       => 'marketplace_order_importer',
                                'label'    => __('Marketplace Order Importer', 'ai-eshop-optimizer'),
                                'priority' => 60,
                                
                                
                                'group'    => 'catalog_sync',
                                'section'  => null,
                            );
                            $p->setValue($dmm, $tabs);
                        }
                    }
                } catch (\Throwable $e) {
                    
                    
                }
            }
        }
    }

    public function render_subtab() {
        $section = isset($_GET['moi_section']) ? sanitize_key((string) $_GET['moi_section']) : 'vendors';
        if (!in_array($section, array('vendors', 'log', 'settings'), true)) { $section = 'vendors'; }
        $base_url = admin_url('admin.php?page=' . AIEO_DMM::MENU_SLUG . '&dmm_tab=marketplace_order_importer');

        echo '<div class="aieo-dmm-section">';
        echo '<h2>' . esc_html__('Marketplace Order Importer', 'ai-eshop-optimizer') . '</h2>';
        echo '<p class="description">' . esc_html__('Inbound orders from marketplace vendor APIs. Each vendor pushes a webhook with order data; AIEO normalises it into a WooCommerce order, with vendor-namespaced meta. Accept / Reject / Voucher actions hit the marketplace API.', 'ai-eshop-optimizer') . '</p>';

        echo '<div class="nav-tab-wrapper" style="margin-bottom:14px;">';
        foreach (array(
            'vendors'  => __('Vendors', 'ai-eshop-optimizer'),
            'log'      => __('Inbound Log', 'ai-eshop-optimizer'),
            'settings' => __('Settings', 'ai-eshop-optimizer'),
        ) as $slug => $label) {
            $active = $section === $slug ? ' nav-tab-active' : '';
            $url    = esc_url($base_url . '&moi_section=' . $slug);
            echo '<a href="' . $url . '" class="nav-tab' . $active . '">' . esc_html($label) . '</a>'; 
        }
        echo '</div>';

        if ($section === 'vendors')  { $this->render_vendors(); }
        if ($section === 'log')      { $this->render_log(); }
        if ($section === 'settings') { $this->render_settings(); }

        echo '</div>';
    }

    private function render_vendors() {
        $vendors = AIEO_MOI_Vendor_Manager::list_all();
        $providers = AIEO_MOI_Provider_Registry::all();
        $nonce  = wp_create_nonce('aieo_moi_admin');
        $ajax   = admin_url('admin-ajax.php');
        $statuses = function_exists('wc_get_order_statuses') ? wc_get_order_statuses() : array();

        echo '<table class="wp-list-table widefat fixed striped">';
        echo '<thead><tr>';
        echo '<th>' . esc_html__('Vendor', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('Provider', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('Webhook URL', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('Default status', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('SKU strategy', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('Enabled', 'ai-eshop-optimizer') . '</th>';
        echo '<th></th>';
        echo '</tr></thead><tbody>';
        if (empty($vendors)) {
            echo '<tr><td colspan="7"><em>' . esc_html__('No vendors yet. Add one below.', 'ai-eshop-optimizer') . '</em></td></tr>';
        } else {
            foreach ($vendors as $v) {
                $hook = esc_url(AIEO_MOI_Vendor_Manager::webhook_url($v));
                $provider = AIEO_MOI_Provider_Registry::get((string) $v['provider']);
                $provider_label = $provider ? $provider->label() : (string) $v['provider'];
                echo '<tr>';
                echo '<td><strong>' . esc_html((string) $v['label']) . '</strong><br><code>' . esc_html((string) $v['slug']) . '</code></td>';
                echo '<td>' . esc_html($provider_label) . '</td>';
                echo '<td><input type="text" readonly value="' . $hook . '" class="large-text" onclick="this.select();" style="font-family:monospace;font-size:11px;">'; 
                echo ' <button type="button" class="button" data-aieo-moi-rotate="' . esc_attr((int) $v['id']) . '">' . esc_html__('Rotate token', 'ai-eshop-optimizer') . '</button>';
                echo '</td>';
                echo '<td>' . esc_html((string) $v['default_order_status']) . '</td>';
                echo '<td>' . esc_html((string) $v['sku_strategy']) . '</td>';
                echo '<td>' . ($v['enabled'] ? '✓' : '—') . '</td>';
                echo '<td><button type="button" class="button" data-aieo-moi-edit="' . esc_attr((int) $v['id']) . '">' . esc_html__('Edit', 'ai-eshop-optimizer') . '</button> ';
                echo '<button type="button" class="button" data-aieo-moi-delete="' . esc_attr((int) $v['id']) . '">' . esc_html__('Delete', 'ai-eshop-optimizer') . '</button></td>';
                echo '</tr>';
            }
        }
        echo '</tbody></table>';

        
        
        
        
        
        
        
        
        $rooster_connected = ((string) get_option('aieo_rooster_api_key', '')) !== '';
        if (!empty($vendors)) {
            foreach ($vendors as $v) {
                $provider = AIEO_MOI_Provider_Registry::get((string) $v['provider']);
                $url      = AIEO_MOI_Vendor_Manager::webhook_url($v);
                $vendor_id = (int) $v['id'];
                $has_steps = $provider && method_exists($provider, 'webhook_setup_steps');
                if (!$has_steps) { continue; }
                $cfg = (array) $provider->webhook_setup_steps($url);
                if (($cfg['mode'] ?? '') !== 'webhook') { continue; }

                
                
                
                $token       = (string) $v['webhook_token'];
                $is_derived  = (strlen($token) === 20 && ctype_xdigit($token));

                echo '<div class="aieo-dmm-card" style="margin-top:18px;padding:16px 18px;">';
                echo '<h3 style="margin-top:0;">' . sprintf(
                    
                    esc_html__('Setup help — %s', 'ai-eshop-optimizer'),
                    esc_html((string) $v['label'])
                ) . '</h3>';

                
                echo '<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:8px 0 12px;">';
                echo '<input type="text" readonly value="' . esc_attr($url) . '" id="aieo-moi-setup-url-' . (int) $vendor_id . '" class="large-text" onclick="this.select();" style="font-family:monospace;font-size:12px;flex:1;min-width:380px;">';
                echo '<button type="button" class="button button-primary" data-aieo-moi-copy="' . (int) $vendor_id . '">' . esc_html__('Copy URL', 'ai-eshop-optimizer') . '</button>';
                if ($rooster_connected) {
                    echo '<button type="button" class="button" data-aieo-moi-rederive="' . (int) $vendor_id . '" title="' . esc_attr__('Re-derive the URL\'s token from your Rooster API key (deterministic across installs).', 'ai-eshop-optimizer') . '">' . esc_html__('Use Rooster-derived token', 'ai-eshop-optimizer') . '</button>';
                }
                echo '<span id="aieo-moi-setup-status-' . (int) $vendor_id . '" style="font-size:13px;color:#1f7a1f;"></span>';
                echo '</div>';

                if ($is_derived) {
                    echo '<p style="margin:0 0 12px;font-size:12px;color:#646970;">'
                        . '<span class="dashicons dashicons-yes" style="color:#1f7a1f;"></span> '
                        . esc_html__('Token derived from your Rooster API key — same URL would regenerate if you ever lost this row.', 'ai-eshop-optimizer')
                        . '</p>';
                }

                echo '<h4 style="margin:14px 0 6px;">' . esc_html__('Where to paste this', 'ai-eshop-optimizer') . '</h4>';
                $panel_url   = isset($cfg['panel_url'])   ? (string) $cfg['panel_url']   : '';
                $panel_label = isset($cfg['panel_label']) ? (string) $cfg['panel_label'] : '';
                if ($panel_url !== '') {
                    echo '<p style="margin:0 0 8px;">'
                        . '<a href="' . esc_url($panel_url) . '" target="_blank" rel="noopener noreferrer">'
                        . esc_html($panel_label !== '' ? $panel_label : $panel_url)
                        . ' <span class="dashicons dashicons-external" style="font-size:14px;vertical-align:middle;"></span>'
                        . '</a></p>';
                }
                if (!empty($cfg['field_label'])) {
                    echo '<p style="margin:0 0 10px;font-size:13px;color:#646970;">'
                        . sprintf(
                            
                            esc_html__('Look for the field labelled %s.', 'ai-eshop-optimizer'),
                            '<code>' . esc_html((string) $cfg['field_label']) . '</code>'
                        )
                        . '</p>'; 
                }

                if (!empty($cfg['steps']) && is_array($cfg['steps'])) {
                    echo '<ol style="margin:6px 0 10px 20px;line-height:1.7;">';
                    foreach ($cfg['steps'] as $step) {
                        echo '<li>' . esc_html((string) $step) . '</li>';
                    }
                    echo '</ol>';
                }
                if (!empty($cfg['extra_notes']) && is_array($cfg['extra_notes'])) {
                    foreach ($cfg['extra_notes'] as $note) {
                        echo '<p style="margin:6px 0;padding:8px 10px;background:#fff8e1;border-left:3px solid #f0b849;border-radius:3px;font-size:12px;line-height:1.6;">'
                            . esc_html((string) $note)
                            . '</p>';
                    }
                }

                echo '</div>'; 
            }

            
            echo '<script>(function(){
                var ajaxUrl = "' . esc_js($ajax) . '";
                var nonce   = "' . esc_js($nonce) . '";
                var i18nCopied  = "' . esc_js(__('Copied to clipboard.', 'ai-eshop-optimizer')) . '";
                var i18nFailed  = "' . esc_js(__('Copy failed — select and copy manually.', 'ai-eshop-optimizer')) . '";
                var i18nDerived = "' . esc_js(__('Token re-derived. Paste the new URL into the marketplace panel.', 'ai-eshop-optimizer')) . '";
                var i18nNetErr  = "' . esc_js(__('Network error.', 'ai-eshop-optimizer')) . '";

                function flash(el, msg, ok) {
                    if (!el) { return; }
                    el.style.color = ok ? "#1f7a1f" : "#b91c1c";
                    el.textContent = msg;
                    setTimeout(function(){ el.textContent = ""; }, 4000);
                }

                document.addEventListener("click", function(ev){
                    var btn = ev.target.closest("[data-aieo-moi-copy]");
                    if (btn) {
                        var id  = btn.getAttribute("data-aieo-moi-copy");
                        var inp = document.getElementById("aieo-moi-setup-url-" + id);
                        var st  = document.getElementById("aieo-moi-setup-status-" + id);
                        if (!inp) { return; }
                        inp.select();
                        var ok = false;
                        try { ok = document.execCommand("copy"); } catch (e) {}
                        if (!ok && navigator.clipboard) {
                            navigator.clipboard.writeText(inp.value).then(
                                function(){ flash(st, i18nCopied, true); },
                                function(){ flash(st, i18nFailed, false); }
                            );
                        } else {
                            flash(st, ok ? i18nCopied : i18nFailed, ok);
                        }
                        return;
                    }
                    var rd = ev.target.closest("[data-aieo-moi-rederive]");
                    if (rd) {
                        var id = rd.getAttribute("data-aieo-moi-rederive");
                        var st = document.getElementById("aieo-moi-setup-status-" + id);
                        var inp = document.getElementById("aieo-moi-setup-url-" + id);
                        rd.disabled = true;
                        var fd = new FormData();
                        fd.set("action", "aieo_moi_rederive_token");
                        fd.set("nonce",  nonce);
                        fd.set("id",     id);
                        fetch(ajaxUrl, { method: "POST", credentials: "same-origin", body: fd })
                            .then(function(r){ return r.json(); })
                            .then(function(j){
                                if (j && j.success && j.data && j.data.webhook_url) {
                                    if (inp) { inp.value = j.data.webhook_url; }
                                    flash(st, i18nDerived, true);
                                } else {
                                    flash(st, (j && j.data && j.data.message) ? j.data.message : i18nNetErr, false);
                                }
                            })
                            .catch(function(){ flash(st, i18nNetErr, false); })
                            .finally(function(){ rd.disabled = false; });
                        return;
                    }
                });
            })();</script>';
        }

        
        if (!empty($vendors)) {
            $today = current_time('Y-m-d');
            $week_ago = gmdate('Y-m-d', strtotime('-7 days', strtotime($today)));
            echo '<div class="aieo-dmm-card" style="margin-top:18px;">';
            echo '<h3>' . esc_html__('Reconcile (one-off backfill)', 'ai-eshop-optimizer') . '</h3>';
            echo '<p class="description">' . esc_html__('Pull every order the marketplace shows in the chosen window and import any that the normal webhook/poll flow missed. Already-imported orders are detected via vendor + remote ID and only have empty metadata gaps filled — never status, never customer-facing emails. Newly-discovered orders are flagged with “Imported via: reconcile” so you can spot them on the orders list.', 'ai-eshop-optimizer') . '</p>';
            echo '<table class="form-table"><tr>';
            echo '<th>' . esc_html__('Vendor', 'ai-eshop-optimizer') . '</th>';
            echo '<td><select id="aieo-moi-reconcile-vendor">';
            foreach ($vendors as $v) {
                echo '<option value="' . esc_attr((int) $v['id']) . '">' . esc_html($v['label']) . ' (' . esc_html($v['provider']) . ')</option>';
            }
            echo '</select></td></tr>';
            echo '<tr><th>' . esc_html__('From', 'ai-eshop-optimizer') . '</th><td><input type="date" id="aieo-moi-reconcile-from" value="' . esc_attr($week_ago) . '"></td></tr>';
            echo '<tr><th>' . esc_html__('To', 'ai-eshop-optimizer') . '</th><td><input type="date" id="aieo-moi-reconcile-to" value="' . esc_attr($today) . '"></td></tr>';
            echo '</table>';
            echo '<p>';
            echo '<button type="button" class="button button-primary" id="aieo-moi-reconcile-run">' . esc_html__('Run reconcile', 'ai-eshop-optimizer') . '</button>';
            echo ' <span id="aieo-moi-reconcile-status" style="margin-left:10px;font-size:13px;color:#646970;"></span>';
            echo '</p>';
            echo '<div id="aieo-moi-reconcile-result" style="display:none;margin-top:8px;padding:10px 12px;background:#f0f6fc;border-left:3px solid #2271b1;border-radius:3px;font-size:13px;line-height:1.7;"></div>';
            echo '</div>';

            
            echo '<script>(function(){
                var btn  = document.getElementById("aieo-moi-reconcile-run");
                var sel  = document.getElementById("aieo-moi-reconcile-vendor");
                var from = document.getElementById("aieo-moi-reconcile-from");
                var to   = document.getElementById("aieo-moi-reconcile-to");
                var st   = document.getElementById("aieo-moi-reconcile-status");
                var res  = document.getElementById("aieo-moi-reconcile-result");
                // Chunked-progress reconcile.
                //
                // Phase 1 — PLAN: server lists every remote_order_id in
                //   the date window using ONLY the cheap list endpoint
                //   (no per-id GETs). Returns the count + array of ids,
                //   typically in <5 seconds for windows under 5000 orders.
                //
                // Phase 2 — PROCESS: browser iterates the id list in
                //   batches of BATCH_SIZE, posting each batch to the
                //   server which then does the per-id GET + upsert. The
                //   browser keeps a running total and updates the
                //   progress bar after every batch finishes.
                //
                // Why two phases: a single AJAX call covering thousands
                //   of orders blows past the PHP-FPM max_execution_time +
                //   nginx proxy_read_timeout (network-error symptom from
                //   the 2023->2026 reconcile attempt). Plan is bounded
                //   (list-only); process is bounded per-batch (10 ids =
                //   ~3 seconds), so neither side ever crosses a timeout.
                var BATCH_SIZE = 10;
                var ajaxUrl    = "' . esc_js($ajax) . '";
                var nonce      = "' . esc_js($nonce) . '";
                var i18n = {
                    pickDates:      "' . esc_js(__('Pick both From and To dates.', 'ai-eshop-optimizer')) . '",
                    fromBeforeTo:   "' . esc_js(__('From must be on or before To.', 'ai-eshop-optimizer')) . '",
                    planning:       "' . esc_js(__('Listing orders in window…', 'ai-eshop-optimizer')) . '",
                    nothingFound:   "' . esc_js(__('No orders in window.', 'ai-eshop-optimizer')) . '",
                    foundN:         "' . esc_js(  __('Found %d orders. Processing…', 'ai-eshop-optimizer')) . '",
                    progressFmt:    "' . esc_js(  __('Processed %1$d of %2$d…', 'ai-eshop-optimizer')) . '",
                    done:           "' . esc_js(__('Done.', 'ai-eshop-optimizer')) . '",
                    networkErr:     "' . esc_js(__('Network error.', 'ai-eshop-optimizer')) . '",
                    planFailed:     "' . esc_js(__('Plan failed.', 'ai-eshop-optimizer')) . '",
                    fetched:        "' . esc_js(__('fetched', 'ai-eshop-optimizer')) . '",
                    created:        "' . esc_js(__('newly imported (flagged as reconciled)', 'ai-eshop-optimizer')) . '",
                    filled:         "' . esc_js(__('existing orders with metadata gaps filled', 'ai-eshop-optimizer')) . '",
                    inSync:         "' . esc_js(__('already in sync', 'ai-eshop-optimizer')) . '",
                    errors:         "' . esc_js(__('errors', 'ai-eshop-optimizer')) . '"
                };

                btn.addEventListener("click", function(){
                    if (!from.value || !to.value) {
                        st.style.color="#b91c1c"; st.textContent = i18n.pickDates; return;
                    }
                    if (from.value > to.value) {
                        st.style.color="#b91c1c"; st.textContent = i18n.fromBeforeTo; return;
                    }
                    btn.disabled = true;
                    res.style.display = "none";
                    res.innerHTML = "";

                    // Render an inline progress bar inside the result div.
                    var bar     = document.createElement("div");
                    bar.style.cssText = "margin-top:8px;height:8px;background:#e5e7eb;border-radius:4px;overflow:hidden;";
                    var barFill = document.createElement("div");
                    barFill.style.cssText = "height:100%;width:0%;background:#2271b1;transition:width 200ms ease;";
                    bar.appendChild(barFill);

                    // ---- Interleaved plan + process loop ----
                    //
                    // Each tick:
                    //   1. fetch ONE list-page (bounded ~3s)
                    //   2. process its 50 ids in batches of 10 (bounded
                    //      per-batch, so each batch stays inside the
                    //      proxy_read_timeout)
                    //   3. if has_more, increment page and repeat
                    //
                    // No single AJAX call ever covers more than one list
                    // page or one process batch, so wide windows
                    // (multi-year) no longer trip nginx/PHP-FPM timeouts.
                    var totals = { created: 0, metadata_filled: 0, already_present: 0, errors: 0, errs: [], processedSoFar: 0, totalSeen: 0 };
                    var page    = 1;
                    var perPage = 50;
                    var label   = "";
                    var firstPlanRendered = false;

                    function planPage(p) {
                        var planFd = new FormData();
                        planFd.set("action",   "aieo_moi_reconcile_plan");
                        planFd.set("nonce",    nonce);
                        planFd.set("vendor_id", sel.value);
                        planFd.set("from",      from.value);
                        planFd.set("to",        to.value);
                        planFd.set("page",      String(p));
                        planFd.set("per_page",  String(perPage));

                        st.textContent = i18n.planning + " (page " + p + ")";

                        fetch(ajaxUrl, {method:"POST", credentials:"same-origin", body: planFd})
                            .then(function(r){return r.json();})
                            .then(function(j){
                                if (!j || !j.success) {
                                    btn.disabled = false;
                                    st.style.color = "#b91c1c";
                                    st.textContent = (j && j.data && j.data.message) || i18n.planFailed;
                                    return;
                                }
                                var d = j.data || {};
                                label = label || (d.vendor_label || "");
                                var ids = (d.ids || []);
                                var total = (d.total || 0)|0;
                                var hasMore = !!d.has_more;

                                if (!firstPlanRendered) {
                                    firstPlanRendered = true;
                                    if (total === 0 && ids.length === 0) {
                                        btn.disabled = false;
                                        st.style.color = "#646970";
                                        st.textContent = i18n.nothingFound;
                                        return;
                                    }
                                    res.style.display = "block";
                                    res.innerHTML = "";
                                    var headLine = document.createElement("div");
                                    headLine.innerHTML = "<strong>" + label + "</strong> · " + i18n.foundN.replace("%d", total || ids.length);
                                    res.appendChild(headLine);
                                    res.appendChild(bar);
                                    totals.totalSeen = total || ids.length;
                                }

                                if (ids.length === 0 && !hasMore) {
                                    finalize();
                                    return;
                                }

                                processBatch(ids, 0, function(){
                                    if (hasMore) {
                                        // Yield to browser, then fetch next page.
                                        setTimeout(function(){ planPage(p + 1); }, 0);
                                    } else {
                                        finalize();
                                    }
                                });
                            })
                            .catch(function(){
                                btn.disabled = false;
                                st.style.color = "#b91c1c";
                                st.textContent = i18n.networkErr + " (page " + p + ")";
                            });
                    }

                    function processBatch(ids, idx, doneCb) {
                        if (idx >= ids.length) { doneCb(); return; }
                        var batch = ids.slice(idx, idx + BATCH_SIZE);
                        var procFd = new FormData();
                        procFd.set("action",    "aieo_moi_reconcile_process");
                        procFd.set("nonce",     nonce);
                        procFd.set("vendor_id", sel.value);
                        batch.forEach(function(rid){ procFd.append("ids[]", rid); });

                        fetch(ajaxUrl, {method:"POST", credentials:"same-origin", body: procFd})
                            .then(function(r){return r.json();})
                            .then(function(jj){
                                if (jj && jj.success) {
                                    var d = jj.data || {};
                                    totals.created         += (d.created|0);
                                    totals.metadata_filled += (d.metadata_filled|0);
                                    totals.already_present += (d.already_present|0);
                                    totals.errors          += (d.errors|0);
                                    if (d.error_messages && d.error_messages.length) {
                                        totals.errs = totals.errs.concat(d.error_messages).slice(0, 10);
                                    }
                                } else {
                                    totals.errors += batch.length;
                                    if (jj && jj.data && jj.data.message) {
                                        totals.errs.push(jj.data.message);
                                    }
                                }
                                totals.processedSoFar += batch.length;
                                var t = totals.totalSeen || totals.processedSoFar;
                                var pct = Math.min(100, Math.round(100 * totals.processedSoFar / Math.max(1, t)));
                                barFill.style.width = pct + "%";
                                st.textContent = i18n.progressFmt.replace("%1$d", totals.processedSoFar).replace("%2$d", t);
                                setTimeout(function(){ processBatch(ids, idx + batch.length, doneCb); }, 0);
                            })
                            .catch(function(){
                                totals.errors += batch.length;
                                totals.errs.push(i18n.networkErr);
                                totals.processedSoFar += batch.length;
                                var t = totals.totalSeen || totals.processedSoFar;
                                var pct = Math.min(100, Math.round(100 * totals.processedSoFar / Math.max(1, t)));
                                barFill.style.width = pct + "%";
                                st.textContent = i18n.progressFmt.replace("%1$d", totals.processedSoFar).replace("%2$d", t);
                                setTimeout(function(){ processBatch(ids, idx + batch.length, doneCb); }, 0);
                            });
                    }

                    function finalize() {
                        btn.disabled = false;
                        st.style.color = "#15803d";
                        st.textContent = i18n.done;
                        var summary = document.createElement("div");
                        summary.style.marginTop = "10px";
                        summary.innerHTML =
                            "<strong>" + totals.processedSoFar + "</strong> " + i18n.fetched + " · " +
                            "<strong style=\"color:#15803d\">" + totals.created + "</strong> " + i18n.created + " · " +
                            "<strong>" + totals.metadata_filled + "</strong> " + i18n.filled + " · " +
                            "<strong>" + totals.already_present + "</strong> " + i18n.inSync + " · " +
                            "<strong style=\"color:#b91c1c\">" + totals.errors + "</strong> " + i18n.errors +
                            (totals.errs.length ? "<br><small>" + totals.errs.slice(0,5).join("<br>") + "</small>" : "");
                        res.appendChild(summary);
                    }

                    planPage(1);
                });
            })();</script>';
        }
        

        
        
        
        
        
        $skroutz_vendors = array_values(array_filter($vendors, function ($v) {
            return (string) $v['provider'] === 'skroutz';
        }));
        if (!empty($skroutz_vendors)) {
            echo '<div class="aieo-dmm-card" style="margin-top:18px;">';
            echo '<h3>' . esc_html__('Import legacy Skroutz orders', 'ai-eshop-optimizer') . '</h3>';
            echo '<p class="description">' . esc_html__('One-shot tool to absorb orders created by the older `skroutz-marketplace-xml-for-woocommerce` plugin into the Marketplace Order Importer. WooCommerce status is never changed; customer emails are suppressed; legacy meta keys are left in place (so you can keep the old plugin installed during the cutover). Already-migrated orders are skipped automatically — re-running is safe.', 'ai-eshop-optimizer') . '</p>';
            echo '<table class="form-table"><tr>';
            echo '<th>' . esc_html__('Target Skroutz vendor', 'ai-eshop-optimizer') . '</th>';
            echo '<td><select id="aieo-moi-legacy-vendor">';
            foreach ($skroutz_vendors as $v) {
                echo '<option value="' . esc_attr((int) $v['id']) . '">' . esc_html($v['label']) . '</option>';
            }
            echo '</select></td></tr>';
            echo '</table>';
            echo '<p>';
            echo '<button type="button" class="button" id="aieo-moi-legacy-scan">' . esc_html__('Scan legacy orders', 'ai-eshop-optimizer') . '</button> ';
            echo '<button type="button" class="button button-primary" id="aieo-moi-legacy-run" disabled>' . esc_html__('Migrate now', 'ai-eshop-optimizer') . '</button>';
            echo ' <span id="aieo-moi-legacy-status" style="margin-left:10px;font-size:13px;color:#646970;"></span>';
            echo '</p>';
            echo '<div id="aieo-moi-legacy-result" style="display:none;margin-top:8px;padding:10px 12px;background:#f0f6fc;border-left:3px solid #2271b1;border-radius:3px;font-size:13px;line-height:1.7;"></div>';
            echo '</div>';

            
            
            
            echo '<script>(function(){
                var scanBtn = document.getElementById("aieo-moi-legacy-scan");
                var runBtn  = document.getElementById("aieo-moi-legacy-run");
                var sel     = document.getElementById("aieo-moi-legacy-vendor");
                var st      = document.getElementById("aieo-moi-legacy-status");
                var res     = document.getElementById("aieo-moi-legacy-result");
                var ajaxUrl = "' . esc_js($ajax) . '";
                var nonce   = "' . esc_js($nonce) . '";
                var i18n = {
                    scanning:   "' . esc_js(__('Scanning legacy orders…', 'ai-eshop-optimizer')) . '",
                    scanDone:   "' . esc_js(  __('Found %d legacy order(s) to migrate.', 'ai-eshop-optimizer')) . '",
                    nothing:    "' . esc_js(__('No legacy orders left to migrate.', 'ai-eshop-optimizer')) . '",
                    running:    "' . esc_js(  __('Migrated %d so far…', 'ai-eshop-optimizer')) . '",
                    done:       "' . esc_js(__('Done.', 'ai-eshop-optimizer')) . '",
                    networkErr: "' . esc_js(__('Network error.', 'ai-eshop-optimizer')) . '",
                    failed:     "' . esc_js(__('Failed.', 'ai-eshop-optimizer')) . '",
                    migrated:   "' . esc_js(__('migrated', 'ai-eshop-optimizer')) . '",
                    relinked:   "' . esc_js(__('already linked (refreshed)', 'ai-eshop-optimizer')) . '",
                    skipped:    "' . esc_js(__('skipped — no remote code', 'ai-eshop-optimizer')) . '",
                    errors:     "' . esc_js(__('errors', 'ai-eshop-optimizer')) . '"
                };

                scanBtn.addEventListener("click", function(){
                    scanBtn.disabled = true;
                    runBtn.disabled  = true;
                    st.style.color = "#646970";
                    st.textContent = i18n.scanning;
                    res.style.display = "none";

                    var fd = new FormData();
                    fd.set("action",    "aieo_moi_legacy_scan");
                    fd.set("nonce",     nonce);
                    fd.set("vendor_id", sel.value);
                    fetch(ajaxUrl, {method:"POST", credentials:"same-origin", body: fd})
                        .then(function(r){return r.json();})
                        .then(function(j){
                            scanBtn.disabled = false;
                            if (!j || !j.success) {
                                st.style.color = "#b91c1c";
                                st.textContent = (j && j.data && j.data.message) || i18n.failed;
                                return;
                            }
                            var total = (j.data && j.data.total)|0;
                            if (total === 0) {
                                st.style.color = "#646970";
                                st.textContent = i18n.nothing;
                                runBtn.disabled = true;
                                return;
                            }
                            st.style.color = "#15803d";
                            st.textContent = i18n.scanDone.replace("%d", total);
                            runBtn.disabled = false;
                            runBtn.setAttribute("data-total", String(total));
                        })
                        .catch(function(){
                            scanBtn.disabled = false;
                            st.style.color = "#b91c1c";
                            st.textContent = i18n.networkErr;
                        });
                });

                runBtn.addEventListener("click", function(){
                    runBtn.disabled = true;
                    scanBtn.disabled = true;
                    res.style.display = "block";
                    res.innerHTML = "";
                    var bar = document.createElement("div");
                    bar.style.cssText = "margin-top:8px;height:8px;background:#e5e7eb;border-radius:4px;overflow:hidden;";
                    var barFill = document.createElement("div");
                    barFill.style.cssText = "height:100%;width:0%;background:#2271b1;transition:width 200ms ease;";
                    bar.appendChild(barFill);
                    res.appendChild(bar);

                    var totals = { migrated:0, already_linked:0, skipped_no_code:0, failed:0, errs:[], processed:0 };
                    var totalSeen = parseInt(runBtn.getAttribute("data-total") || "0", 10);

                    function tick() {
                        var fd = new FormData();
                        fd.set("action",    "aieo_moi_legacy_migrate_batch");
                        fd.set("nonce",     nonce);
                        fd.set("vendor_id", sel.value);
                        fetch(ajaxUrl, {method:"POST", credentials:"same-origin", body: fd})
                            .then(function(r){return r.json();})
                            .then(function(j){
                                if (!j || !j.success) {
                                    runBtn.disabled = false;
                                    scanBtn.disabled = false;
                                    st.style.color = "#b91c1c";
                                    st.textContent = (j && j.data && j.data.message) || i18n.failed;
                                    return;
                                }
                                var d = j.data || {};
                                totals.migrated         += (d.migrated|0);
                                totals.already_linked   += (d.already_linked|0);
                                totals.skipped_no_code  += (d.skipped_no_code|0);
                                totals.failed           += (d.failed|0);
                                totals.processed        += (d.processed|0);
                                if (d.error_messages && d.error_messages.length) {
                                    totals.errs = totals.errs.concat(d.error_messages).slice(0, 10);
                                }
                                var doneSoFar = totals.migrated + totals.already_linked + totals.skipped_no_code + totals.failed;
                                var pct = totalSeen > 0 ? Math.min(100, Math.round(100 * doneSoFar / totalSeen)) : 0;
                                barFill.style.width = pct + "%";
                                st.textContent = i18n.running.replace("%d", doneSoFar);

                                if (d.has_more) {
                                    setTimeout(tick, 0);
                                } else {
                                    finalize();
                                }
                            })
                            .catch(function(){
                                runBtn.disabled = false;
                                scanBtn.disabled = false;
                                st.style.color = "#b91c1c";
                                st.textContent = i18n.networkErr;
                            });
                    }

                    function finalize() {
                        runBtn.disabled = false;
                        scanBtn.disabled = false;
                        barFill.style.width = "100%";
                        st.style.color = "#15803d";
                        st.textContent = i18n.done;
                        var summary = document.createElement("div");
                        summary.style.marginTop = "10px";
                        summary.innerHTML =
                            "<strong style=\"color:#15803d\">" + totals.migrated + "</strong> " + i18n.migrated + " · " +
                            "<strong>" + totals.already_linked + "</strong> " + i18n.relinked + " · " +
                            "<strong style=\"color:#92400e\">" + totals.skipped_no_code + "</strong> " + i18n.skipped + " · " +
                            "<strong style=\"color:#b91c1c\">" + totals.failed + "</strong> " + i18n.errors +
                            (totals.errs.length ? "<br><small>" + totals.errs.slice(0,5).join("<br>") + "</small>" : "");
                        res.appendChild(summary);
                    }

                    tick();
                });
            })();</script>';
        }
        

        
        
        
        
        
        
        
        
        $vendor_map = array();
        foreach ($vendors as $v) {
            $vid = (int) $v['id'];
            $entry = array(
                'id'                   => $vid,
                'slug'                 => (string) $v['slug'],
                'label'                => (string) $v['label'],
                'provider'             => (string) $v['provider'],
                'default_order_status' => (string) $v['default_order_status'],
                'sku_strategy'         => (string) $v['sku_strategy'],
                'sku_meta_key'         => (string) ($v['sku_meta_key'] ?? ''),
                'enabled'              => (bool) $v['enabled'],
                'cred'                 => array(),
            );
            $p = AIEO_MOI_Provider_Registry::get((string) $v['provider']);
            if ($p) {
                $db_creds = is_array($v['credentials'] ?? null) ? $v['credentials'] : array();
                foreach ($p->credential_fields() as $f) {
                    if (($f['type'] ?? 'text') === 'password') { continue; }
                    $fname = (string) $f['name'];
                    if (isset($db_creds[$fname])) {
                        $entry['cred'][$fname] = (string) $db_creds[$fname];
                    }
                }
            }
            $vendor_map[$vid] = $entry;
        }
        echo '<script>window.aieoMoiVendors = ' . wp_json_encode($vendor_map) . ';</script>';

        
        echo '<div class="aieo-dmm-card" style="margin-top:18px;">';
        echo '<h3 id="aieo-moi-form-title">' . esc_html__('Add a vendor', 'ai-eshop-optimizer') . '</h3>';
        echo '<form id="aieo-moi-vendor-form" class="aieo-dmm-form">';
        echo '<input type="hidden" name="id" value="">';
        echo '<table class="form-table">';
        echo '<tr><th>' . esc_html__('Slug', 'ai-eshop-optimizer') . '</th><td><input type="text" name="slug" required pattern="[a-z0-9-_]+" class="regular-text" placeholder="skroutz-main"><p class="description">' . esc_html__('Lowercase, no spaces. Used as the vendor identifier in webhook URLs and meta.', 'ai-eshop-optimizer') . '</p></td></tr>';
        echo '<tr><th>' . esc_html__('Display name', 'ai-eshop-optimizer') . '</th><td><input type="text" name="label" required class="regular-text" placeholder="' . esc_attr__('Skroutz (main account)', 'ai-eshop-optimizer') . '"></td></tr>';
        echo '<tr><th>' . esc_html__('Provider', 'ai-eshop-optimizer') . '</th><td><select name="provider" id="aieo-moi-provider">';
        foreach ($providers as $p) {
            echo '<option value="' . esc_attr($p->slug()) . '">' . esc_html($p->label()) . '</option>';
        }
        echo '</select></td></tr>';
        
        
        
        
        
        foreach ($providers as $p) {
            if (method_exists($p, 'info_text')) {
                $info = (string) $p->info_text();
                if ($info !== '') {
                    echo '<tr class="aieo-moi-cred-row" data-provider="' . esc_attr($p->slug()) . '">';
                    echo '<th></th><td>';
                    echo '<div class="aieo-moi-info" style="background:#eff6ff;border-left:3px solid #2271b1;padding:10px 12px;border-radius:3px;font-size:12px;line-height:1.7;color:#1f2937;">'
                       . wp_kses_post($info)
                       . '</div>';
                    echo '</td></tr>';
                }
            }
            foreach ($p->credential_fields() as $f) {
                $fname = 'cred__' . $p->slug() . '__' . $f['name'];
                echo '<tr class="aieo-moi-cred-row" data-provider="' . esc_attr($p->slug()) . '">';
                echo '<th>' . esc_html((string) $f['label']) . '</th><td>';
                $type = ($f['type'] ?? 'text') === 'password' ? 'password' : 'text';
                echo '<input type="' . esc_attr($type) . '" name="' . esc_attr($fname) . '" class="regular-text" autocomplete="off"' . (!empty($f['required']) ? ' required' : '') . '>';
                if (!empty($f['description'])) {
                    echo '<p class="description">' . esc_html((string) $f['description']) . '</p>';
                }
                echo '</td></tr>';
            }
        }
        echo '<tr><th>' . esc_html__('Default order status', 'ai-eshop-optimizer') . '</th><td><select name="default_order_status">';
        foreach ($statuses as $key => $label) {
            echo '<option value="' . esc_attr($key) . '" ' . selected($key, 'wc-on-hold', false) . '>' . esc_html($label) . '</option>';
        }
        echo '</select></td></tr>';
        echo '<tr><th>' . esc_html__('SKU lookup strategy', 'ai-eshop-optimizer') . '</th><td><select name="sku_strategy">';
        foreach (array(
            'sku'          => __('SKU (recommended)', 'ai-eshop-optimizer'),
            'id'           => __('Product post ID', 'ai-eshop-optimizer'),
            'custom_field' => __('Custom postmeta key', 'ai-eshop-optimizer'),
        ) as $val => $label) {
            echo '<option value="' . esc_attr($val) . '">' . esc_html($label) . '</option>';
        }
        echo '</select></td></tr>';
        echo '<tr><th>' . esc_html__('Custom field key', 'ai-eshop-optimizer') . '</th><td><input type="text" name="sku_meta_key" class="regular-text" placeholder="_global_unique_id"><p class="description">' . esc_html__('Only used when SKU strategy is "Custom postmeta key". Match the key used by the marketplace.', 'ai-eshop-optimizer') . '</p></td></tr>';
        echo '<tr><th>' . esc_html__('Enabled', 'ai-eshop-optimizer') . '</th><td><label><input type="checkbox" name="enabled" value="1" checked></label></td></tr>';
        echo '</table>';
        echo '<p><button type="button" class="button button-primary" id="aieo-moi-save">' . esc_html__('Save vendor', 'ai-eshop-optimizer') . '</button>';
        echo ' <span id="aieo-moi-status" style="margin-left:10px;color:#646970;font-size:13px;"></span></p>';
        echo '</form>';
        echo '</div>';

        echo '<script>(function(){
            var form = document.getElementById("aieo-moi-vendor-form");
            var providerSel = document.getElementById("aieo-moi-provider");
            var status = document.getElementById("aieo-moi-status");
            var saveBtn = document.getElementById("aieo-moi-save");
            function syncCredRows(){
                var slug = providerSel.value;
                document.querySelectorAll(".aieo-moi-cred-row").forEach(function(r){
                    r.style.display = r.getAttribute("data-provider") === slug ? "" : "none";
                });
            }
            providerSel.addEventListener("change", syncCredRows);
            syncCredRows();
            saveBtn.addEventListener("click", function(){
                var fd = new FormData(form);
                fd.set("action", "aieo_moi_save_vendor");
                fd.set("nonce", "' . esc_js($nonce) . '");
                status.style.color = "#646970";
                status.textContent = "' . esc_js(__('Saving…', 'ai-eshop-optimizer')) . '";
                fetch("' . esc_js($ajax) . '", {method:"POST", credentials:"same-origin", body:fd})
                    .then(function(r){return r.json();}).then(function(j){
                        if (j && j.success) {
                            status.style.color = "#15803d";
                            status.textContent = j.data.message;
                            setTimeout(function(){ window.location.reload(); }, 700);
                        } else {
                            status.style.color = "#b91c1c";
                            status.textContent = (j && j.data && j.data.message) || "Save failed.";
                        }
                    });
            });
            document.querySelectorAll("[data-aieo-moi-delete]").forEach(function(btn){
                btn.addEventListener("click", function(){
                    if (!confirm("' . esc_js(__('Delete this vendor?', 'ai-eshop-optimizer')) . '")) return;
                    var fd = new FormData();
                    fd.set("action", "aieo_moi_delete_vendor");
                    fd.set("nonce", "' . esc_js($nonce) . '");
                    fd.set("id", btn.getAttribute("data-aieo-moi-delete"));
                    fetch("' . esc_js($ajax) . '", {method:"POST", credentials:"same-origin", body:fd})
                        .then(function(r){return r.json();})
                        .then(function(j){
                            // Previously this just reloaded regardless of
                            // whether the AJAX call actually succeeded —
                            // when the nonce had expired or a JSON parse
                            // error happened, the silent reload made the
                            // vendor look "still there", reading as a bug.
                            if (j && j.success) {
                                window.location.reload();
                            } else {
                                alert((j && j.data && j.data.message) || "' . esc_js(__('Delete failed.', 'ai-eshop-optimizer')) . '");
                            }
                        })
                        .catch(function(e){
                            alert("' . esc_js(__('Network error while deleting vendor.', 'ai-eshop-optimizer')) . '");
                        });
                });
            });
            document.querySelectorAll("[data-aieo-moi-rotate]").forEach(function(btn){
                btn.addEventListener("click", function(){
                    if (!confirm("' . esc_js(__('Rotate the webhook token? The marketplace will need the new URL.', 'ai-eshop-optimizer')) . '")) return;
                    var fd = new FormData();
                    fd.set("action", "aieo_moi_rotate_token");
                    fd.set("nonce", "' . esc_js($nonce) . '");
                    fd.set("id", btn.getAttribute("data-aieo-moi-rotate"));
                    fetch("' . esc_js($ajax) . '", {method:"POST", credentials:"same-origin", body:fd})
                        .then(function(r){return r.json();})
                        .then(function(j){
                            if (j && j.success) {
                                window.location.reload();
                            } else {
                                alert((j && j.data && j.data.message) || "' . esc_js(__('Token rotation failed.', 'ai-eshop-optimizer')) . '");
                            }
                        })
                        .catch(function(e){
                            alert("' . esc_js(__('Network error while rotating token.', 'ai-eshop-optimizer')) . '");
                        });
                });
            });

            // Edit button — populate the Add/Edit form with the chosen
            // vendor row\'s saved data. Reads from window.aieoMoiVendors
            // (built server-side; passwords intentionally absent — left
            // blank means "keep current"). On click, fills every
            // non-credential field, switches the credential rows to
            // match the vendor\'s provider, populates non-password
            // credential inputs (e.g., username), leaves password
            // inputs blank with a "(unchanged)" placeholder, retitles
            // the form, and scrolls it into view.
            document.querySelectorAll("[data-aieo-moi-edit]").forEach(function(btn){
                btn.addEventListener("click", function(){
                    var id = btn.getAttribute("data-aieo-moi-edit");
                    var data = (window.aieoMoiVendors || {})[id];
                    if (!data) {
                        if (status) {
                            status.style.color = "#b91c1c";
                            status.textContent = "' . esc_js(__('Vendor data missing — reload the page.', 'ai-eshop-optimizer')) . '";
                        }
                        return;
                    }
                    var setField = function(name, value, isCheckbox){
                        var el = form.querySelector("[name=\"" + name + "\"]");
                        if (!el) { return; }
                        if (isCheckbox) { el.checked = !!value; }
                        else { el.value = value === null || value === undefined ? "" : value; }
                    };
                    setField("id",                   data.id);
                    setField("slug",                 data.slug);
                    setField("label",                data.label);
                    setField("default_order_status", data.default_order_status);
                    setField("sku_strategy",         data.sku_strategy);
                    setField("sku_meta_key",         data.sku_meta_key);
                    setField("enabled",              data.enabled, true);
                    if (providerSel) {
                        providerSel.value = data.provider || "";
                        syncCredRows();
                    }
                    // Non-password credential fields.
                    var creds = data.cred || {};
                    Object.keys(creds).forEach(function(k){
                        var inp = form.querySelector("[name=\"cred__" + data.provider + "__" + k + "\"]");
                        if (inp) { inp.value = creds[k] || ""; }
                    });
                    // Password fields: keep blank, hint that they\'re preserved.
                    form.querySelectorAll(".aieo-moi-cred-row[data-provider=\"" + data.provider + "\"] input[type=password]").forEach(function(p){
                        p.value = "";
                        p.placeholder = "' . esc_js(__('(leave blank to keep the current value)', 'ai-eshop-optimizer')) . '";
                        p.required = false;
                    });
                    // Retitle + scroll to form.
                    var title = document.getElementById("aieo-moi-form-title");
                    if (title) {
                        title.textContent = "' . esc_js(__('Edit vendor:', 'ai-eshop-optimizer')) . ' " + (data.label || data.slug || "#" + id);
                    }
                    if (status) {
                        status.style.color = "#646970";
                        status.textContent = "";
                    }
                    form.scrollIntoView({behavior: "smooth", block: "start"});
                });
            });
        })();</script>';
    }

    private function render_log() {
        $rows = AIEO_MOI_Inbound_Log::recent(200);
        $vendors_by_id = array();
        foreach (AIEO_MOI_Vendor_Manager::list_all() as $v) { $vendors_by_id[(int) $v['id']] = $v; }

        echo '<table class="wp-list-table widefat fixed striped">';
        echo '<thead><tr>';
        echo '<th style="width:160px;">' . esc_html__('Received', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('Vendor', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('Event', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('Remote ID', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('Status', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('WC order', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('Error', 'ai-eshop-optimizer') . '</th>';
        echo '</tr></thead><tbody>';
        if (empty($rows)) {
            echo '<tr><td colspan="7"><em>' . esc_html__('No events yet.', 'ai-eshop-optimizer') . '</em></td></tr>';
        } else {
            foreach ($rows as $r) {
                $vendor = isset($vendors_by_id[(int) $r['vendor_id']]) ? $vendors_by_id[(int) $r['vendor_id']] : null;
                $vendor_label = $vendor ? $vendor['label'] : '—';
                $status = (string) $r['status'];
                $color  = '#374151';
                if (in_array($status, array('rejected_auth','rejected_signature','failed','parse_failed'), true)) { $color = '#b91c1c'; }
                if (in_array($status, array('processed','accepted'), true)) { $color = '#15803d'; }
                echo '<tr>';
                echo '<td>' . esc_html((string) $r['received_at']) . '</td>';
                echo '<td>' . esc_html($vendor_label) . '</td>';
                echo '<td><code>' . esc_html((string) $r['event_type']) . '</code></td>';
                echo '<td>' . esc_html((string) $r['remote_order_id']) . '</td>';
                echo '<td style="color:' . esc_attr($color) . ';font-weight:600;">' . esc_html($status) . '</td>';
                echo '<td>';
                if (!empty($r['wc_order_id'])) {
                    echo '<a href="' . esc_url(admin_url('post.php?post=' . (int) $r['wc_order_id'] . '&action=edit')) . '">#' . (int) $r['wc_order_id'] . '</a>';
                } else { echo '—'; }
                echo '</td>';
                echo '<td><small>' . esc_html((string) ($r['error_text'] ?? '')) . '</small></td>';
                echo '</tr>';
            }
        }
        echo '</tbody></table>';
        echo '<p class="description" style="margin-top:8px;">' . esc_html__('Last 200 events. Older entries are pruned automatically (30-day retention).', 'ai-eshop-optimizer') . '</p>';
    }

    private function render_settings() {
        echo '<div class="aieo-dmm-card">';
        echo '<p>' . esc_html__('Global settings for the importer. Per-vendor credentials live on each vendor row in the Vendors tab.', 'ai-eshop-optimizer') . '</p>';
        echo '<p><strong>' . esc_html__('Webhook endpoint pattern:', 'ai-eshop-optimizer') . '</strong> <code>' . esc_html(rest_url('moi/v1/webhook/{vendor-slug}?token={webhook-token}')) . '</code></p>';
        echo '<p><strong>' . esc_html__('Admin REST routes:', 'ai-eshop-optimizer') . '</strong></p>';
        echo '<ul style="margin-left:20px;list-style:disc;">';
        echo '<li><code>POST /moi/v1/orders/{wc_order_id}/accept</code></li>';
        echo '<li><code>POST /moi/v1/orders/{wc_order_id}/reject</code></li>';
        echo '<li><code>POST /moi/v1/orders/{wc_order_id}/voucher</code></li>';
        echo '</ul>';
        echo '<p><strong>' . esc_html__('Order meta keys (vendor-namespaced):', 'ai-eshop-optimizer') . '</strong></p>';
        echo '<pre style="background:#f3f4f6;padding:10px;border-radius:4px;font-size:12px;line-height:1.6;">_aieo_moi_vendor_id          (int)
_aieo_moi_vendor_slug        (string)
_aieo_moi_provider           (string, e.g. "skroutz")
_aieo_moi_remote_order_id    (string)
_aieo_moi_remote_code        (string, human-readable)
_aieo_moi_state              (string, provider-specific state)
_aieo_moi_voucher_url        (URL, printable courier label)
_aieo_moi_courier            (string, carrier name)
_aieo_moi_tracking_codes     (JSON array)
_aieo_moi_invoice_data       (JSON)
_aieo_moi_gift_wrap          ("yes" | "no")</pre>';
        echo '</div>';
    }

    public function ajax_save_vendor() {
        check_ajax_referer('aieo_moi_admin', 'nonce');
        if (!current_user_can('manage_woocommerce')) { wp_send_json_error(array('message' => 'forbidden')); }

        $provider_slug = isset($_POST['provider']) ? sanitize_key(wp_unslash($_POST['provider'])) : '';
        $provider = AIEO_MOI_Provider_Registry::get($provider_slug);
        if (!$provider) {
            wp_send_json_error(array('message' => __('Unknown provider.', 'ai-eshop-optimizer')));
        }

        
        
        
        
        
        
        
        
        
        
        
        $id = isset($_POST['id']) ? (int) $_POST['id'] : 0;
        $existing_creds = array();
        if ($id > 0) {
            foreach (AIEO_MOI_Vendor_Manager::list_all() as $existing_v) {
                if ((int) $existing_v['id'] === $id && is_array($existing_v['credentials'] ?? null)) {
                    $existing_creds = $existing_v['credentials'];
                    break;
                }
            }
        }

        
        $credentials      = array();
        $known_field_keys = array();
        foreach ($provider->credential_fields() as $f) {
            $fname              = (string) $f['name'];
            $known_field_keys[] = $fname;
            $key                = 'cred__' . $provider_slug . '__' . $fname;
            $submitted          = isset($_POST[$key]) ? sanitize_text_field(wp_unslash((string) $_POST[$key])) : '';
            $is_password        = ($f['type'] ?? 'text') === 'password';

            
            if ($submitted === '' && $id > 0 && $is_password && !empty($existing_creds[$fname])) {
                $credentials[$fname] = (string) $existing_creds[$fname];
                continue;
            }
            if ($submitted !== '') {
                $credentials[$fname] = $submitted;
            }
        }
        
        foreach ($existing_creds as $k => $val) {
            if (!in_array($k, $known_field_keys, true)) {
                $credentials[$k] = $val;
            }
        }

        if (!$provider->validate_credentials($credentials)) {
            wp_send_json_error(array('message' => __('Invalid credentials. Double-check the API token / required fields.', 'ai-eshop-optimizer')));
        }

        $result = AIEO_MOI_Vendor_Manager::save(array(
            'id'                   => $id,
            'slug'                 => isset($_POST['slug']) ? sanitize_key(wp_unslash($_POST['slug'])) : '',
            'provider'             => $provider_slug,
            'label'                => isset($_POST['label']) ? sanitize_text_field(wp_unslash($_POST['label'])) : '',
            'credentials'          => $credentials,
            'default_order_status' => isset($_POST['default_order_status']) ? sanitize_key(wp_unslash($_POST['default_order_status'])) : 'wc-on-hold',
            'sku_strategy'         => isset($_POST['sku_strategy']) ? sanitize_key(wp_unslash($_POST['sku_strategy'])) : 'sku',
            'sku_meta_key'         => isset($_POST['sku_meta_key']) ? sanitize_text_field(wp_unslash($_POST['sku_meta_key'])) : '',
            'enabled'              => !empty($_POST['enabled']),
        ));
        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
        }
        wp_send_json_success(array(
            'id'      => (int) $result,
            'message' => __('Vendor saved.', 'ai-eshop-optimizer'),
        ));
    }

    public function ajax_delete_vendor() {
        check_ajax_referer('aieo_moi_admin', 'nonce');
        if (!current_user_can('manage_woocommerce')) {
            wp_send_json_error(array('message' => __('Permission denied.', 'ai-eshop-optimizer')));
        }
        $id = isset($_POST['id']) ? (int) $_POST['id'] : 0;
        if ($id <= 0) {
            wp_send_json_error(array('message' => __('Bad vendor id.', 'ai-eshop-optimizer')));
        }
        $result = AIEO_MOI_Vendor_Manager::delete($id);
        if (empty($result['vendor_deleted'])) {
            wp_send_json_error(array('message' => __('Vendor row not deleted (already gone?).', 'ai-eshop-optimizer')));
        }
        wp_send_json_success(array(
            'message'        => sprintf(
                
                __('Vendor deleted. Cascaded %1$d order-link rows and %2$d inbound-log rows.', 'ai-eshop-optimizer'),
                (int) $result['links_deleted'],
                (int) $result['log_deleted']
            ),
            'links_deleted'  => (int) $result['links_deleted'],
            'log_deleted'    => (int) $result['log_deleted'],
        ));
    }

    public function ajax_rotate_token() {
        check_ajax_referer('aieo_moi_admin', 'nonce');
        if (!current_user_can('manage_woocommerce')) { wp_send_json_error(array('message' => 'forbidden')); }
        $id = isset($_POST['id']) ? (int) $_POST['id'] : 0;
        if ($id > 0) {
            $token = AIEO_MOI_Vendor_Manager::rotate_webhook_token($id);
            $vendor = AIEO_MOI_Vendor_Manager::get($id);
            wp_send_json_success(array(
                'webhook_token' => $token,
                'webhook_url'   => $vendor ? AIEO_MOI_Vendor_Manager::webhook_url($vendor) : '',
            ));
        }
        wp_send_json_error(array('message' => 'bad_id'));
    }

    
    public function ajax_rederive_token_from_rooster() {
        check_ajax_referer('aieo_moi_admin', 'nonce');
        if (!current_user_can('manage_woocommerce')) { wp_send_json_error(array('message' => 'forbidden')); }
        $id = isset($_POST['id']) ? (int) $_POST['id'] : 0;
        if ($id <= 0) { wp_send_json_error(array('message' => 'bad_id')); }
        $rooster_key = (string) get_option('aieo_rooster_api_key', '');
        if ($rooster_key === '') {
            wp_send_json_error(array('message' => __('Connect Rooster first (Pro Panel → Rooster Content Connector). Without it the token can\'t be derived.', 'ai-eshop-optimizer')));
        }
        $token  = AIEO_MOI_Vendor_Manager::rotate_webhook_token_from_rooster($id);
        $vendor = AIEO_MOI_Vendor_Manager::get($id);
        wp_send_json_success(array(
            'webhook_token' => $token,
            'webhook_url'   => $vendor ? AIEO_MOI_Vendor_Manager::webhook_url($vendor) : '',
        ));
    }

    
    private static function suppress_wc_customer_emails() {
        $emails = array(
            
            'customer_completed_order',
            'customer_invoice',
            'customer_note',
            'customer_on_hold_order',
            'customer_processing_order',
            'customer_refunded_order',
            'customer_reset_password',
            'customer_new_account',
            'customer_partially_refunded_order',
            'customer_failed_order',
            
            
            'new_order',
            'cancelled_order',
            'failed_order',
        );
        foreach ($emails as $key) {
            add_filter("woocommerce_email_enabled_{$key}", '__return_false', PHP_INT_MAX);
        }
        
        
        
        
        add_filter('woocommerce_email_recipient_customer_completed_order', '__return_empty_string', PHP_INT_MAX);
        
        
        
        add_filter('pre_wp_mail', '__return_false', PHP_INT_MAX);
    }

    
    public function ajax_reconcile() {
        check_ajax_referer('aieo_moi_admin', 'nonce');
        if (!current_user_can('manage_woocommerce')) {
            wp_send_json_error(array('message' => __('Permission denied.', 'ai-eshop-optimizer')));
        }

        
        
        
        self::suppress_wc_customer_emails();

        $vendor_id = isset($_POST['vendor_id']) ? (int) $_POST['vendor_id'] : 0;
        $from      = isset($_POST['from']) ? sanitize_text_field(wp_unslash((string) $_POST['from'])) : '';
        $to        = isset($_POST['to'])   ? sanitize_text_field(wp_unslash((string) $_POST['to']))   : '';
        if ($vendor_id <= 0 || $from === '' || $to === '') {
            wp_send_json_error(array('message' => __('vendor_id, from and to are required.', 'ai-eshop-optimizer')));
        }

        $vendor = AIEO_MOI_Vendor_Manager::get($vendor_id);
        if (!$vendor) {
            wp_send_json_error(array('message' => __('Vendor not found.', 'ai-eshop-optimizer')));
        }
        $provider = AIEO_MOI_Provider_Registry::get((string) $vendor['provider']);
        if (!$provider) {
            wp_send_json_error(array('message' => __('Provider not registered.', 'ai-eshop-optimizer')));
        }

        
        
        $tz = wp_timezone();
        try {
            $start = new DateTime($from . ' 00:00:00', $tz);
            $end   = new DateTime($to   . ' 23:59:59', $tz);
        } catch (\Throwable $e) {
            wp_send_json_error(array('message' => __('Invalid date format.', 'ai-eshop-optimizer')));
        }
        if ($end < $start) {
            wp_send_json_error(array('message' => __('From must be on or before To.', 'ai-eshop-optimizer')));
        }
        $since_ts = (int) $start->getTimestamp();
        $until_ts = (int) $end->getTimestamp();

        
        
        @set_time_limit(300); 

        $counters = array(
            'fetched'         => 0,
            'created'         => 0,
            'metadata_filled' => 0,
            'already_present' => 0,
            'errors'          => 0,
            'error_messages'  => array(),
        );

        try {
            $orders = $provider->backfill_orders($vendor, $since_ts, $until_ts);
        } catch (\Throwable $e) {
            wp_send_json_error(array('message' => sprintf(
                
                __('Provider backfill failed: %s', 'ai-eshop-optimizer'),
                $e->getMessage()
            )));
        }
        if (!is_array($orders)) { $orders = array(); }
        $counters['fetched'] = count($orders);

        foreach ($orders as $canonical) {
            if (!is_array($canonical) || empty($canonical['remote_order_id'])) {
                $counters['errors']++;
                continue;
            }
            $result = AIEO_MOI_Order_Mapper::upsert_from_canonical($vendor, $canonical, 'reconcile');

            $log_status = 'processed';
            $err_msg    = null;
            if (isset($result['error'])) {
                $counters['errors']++;
                $err_msg = is_wp_error($result['error']) ? $result['error']->get_error_message() : (string) $result['error'];
                if (count($counters['error_messages']) < 10) {
                    $counters['error_messages'][] = (string) $canonical['remote_order_id'] . ': ' . $err_msg;
                }
                $log_status = 'failed';
            } elseif (!empty($result['created'])) {
                $counters['created']++;
            } elseif (!empty($result['touched'])) {
                $counters['metadata_filled']++;
            } else {
                $counters['already_present']++;
            }

            AIEO_MOI_Inbound_Log::record(array(
                'vendor_id'       => (int) $vendor['id'],
                'event_type'      => 'reconcile',
                'remote_order_id' => (string) $canonical['remote_order_id'],
                'status'          => $log_status,
                'error_text'      => $err_msg,
                'wc_order_id'     => isset($result['wc_order_id']) ? (int) $result['wc_order_id'] : null,
            ));
        }

        wp_send_json_success(array(
            'vendor_label'     => (string) $vendor['label'],
            'fetched'          => (int) $counters['fetched'],
            'created'          => (int) $counters['created'],
            'metadata_filled'  => (int) $counters['metadata_filled'],
            'already_present'  => (int) $counters['already_present'],
            'errors'           => (int) $counters['errors'],
            'error_messages'   => $counters['error_messages'],
            'window' => array(
                'from_ts' => $since_ts,
                'to_ts'   => $until_ts,
            ),
        ));
    }

    
    public function ajax_reconcile_plan() {
        check_ajax_referer('aieo_moi_admin', 'nonce');
        if (!current_user_can('manage_woocommerce')) {
            wp_send_json_error(array('message' => __('Permission denied.', 'ai-eshop-optimizer')));
        }

        $vendor_id = isset($_POST['vendor_id']) ? (int) $_POST['vendor_id'] : 0;
        $from      = isset($_POST['from']) ? sanitize_text_field(wp_unslash((string) $_POST['from'])) : '';
        $to        = isset($_POST['to'])   ? sanitize_text_field(wp_unslash((string) $_POST['to']))   : '';
        $page      = isset($_POST['page'])     ? max(1, (int) $_POST['page'])     : 1;
        $per_page  = isset($_POST['per_page']) ? max(1, min(50, (int) $_POST['per_page'])) : 50;

        if ($vendor_id <= 0 || $from === '' || $to === '') {
            wp_send_json_error(array('message' => __('vendor_id, from and to are required.', 'ai-eshop-optimizer')));
        }
        $vendor = AIEO_MOI_Vendor_Manager::get($vendor_id);
        if (!$vendor) { wp_send_json_error(array('message' => __('Vendor not found.', 'ai-eshop-optimizer'))); }
        $provider = AIEO_MOI_Provider_Registry::get((string) $vendor['provider']);
        if (!$provider) { wp_send_json_error(array('message' => __('Provider not registered.', 'ai-eshop-optimizer'))); }

        
        
        
        
        $supports_paged = method_exists($provider, 'list_window_page') && method_exists($provider, 'fetch_one_canonical');
        if (!$supports_paged && !method_exists($provider, 'list_window_summaries')) {
            wp_send_json_error(array(
                'message'  => __('This provider does not yet support chunked reconcile.', 'ai-eshop-optimizer'),
                'fallback' => 'legacy',
            ));
        }

        $tz = wp_timezone();
        try {
            $start = new DateTime($from . ' 00:00:00', $tz);
            $end   = new DateTime($to   . ' 23:59:59', $tz);
        } catch (\Throwable $e) {
            wp_send_json_error(array('message' => __('Invalid date format.', 'ai-eshop-optimizer')));
        }
        if ($end < $start) {
            wp_send_json_error(array('message' => __('From must be on or before To.', 'ai-eshop-optimizer')));
        }

        @set_time_limit(60); 

        $since_ts = (int) $start->getTimestamp();
        $until_ts = (int) $end->getTimestamp();

        if ($supports_paged) {
            try {
                $page_data = $provider->list_window_page($vendor, $since_ts, $until_ts, $page, $per_page);
            } catch (\Throwable $e) {
                wp_send_json_error(array('message' => sprintf(
                    
                    __('Provider list failed: %s', 'ai-eshop-optimizer'),
                    $e->getMessage()
                )));
            }
            if (is_wp_error($page_data)) {
                wp_send_json_error(array('message' => $page_data->get_error_message()));
            }
            if (!is_array($page_data)) {
                wp_send_json_error(array('message' => __('Provider returned no data.', 'ai-eshop-optimizer')));
            }
            wp_send_json_success(array(
                'vendor_id'    => (int) $vendor['id'],
                'vendor_label' => (string) $vendor['label'],
                'page'         => (int) ($page_data['page']     ?? $page),
                'per_page'     => (int) ($page_data['per_page'] ?? $per_page),
                'total'        => (int) ($page_data['total']    ?? 0),
                'has_more'     => !empty($page_data['has_more']),
                'ids'          => isset($page_data['ids']) && is_array($page_data['ids']) ? array_values($page_data['ids']) : array(),
                'paged'        => true,
            ));
        }

        
        try {
            $summaries = $provider->list_window_summaries($vendor, $since_ts, $until_ts);
        } catch (\Throwable $e) {
            wp_send_json_error(array('message' => sprintf(
                
                __('Provider list failed: %s', 'ai-eshop-optimizer'),
                $e->getMessage()
            )));
        }
        if (!is_array($summaries)) { $summaries = array(); }
        $ids = array();
        foreach ($summaries as $s) {
            if (is_array($s) && !empty($s['id'])) { $ids[] = (string) $s['id']; }
        }
        wp_send_json_success(array(
            'vendor_id'    => (int) $vendor['id'],
            'vendor_label' => (string) $vendor['label'],
            'count'        => count($ids),
            'ids'          => $ids,
            'has_more'     => false,
            'paged'        => false,
        ));
    }

    
    public function ajax_reconcile_process() {
        check_ajax_referer('aieo_moi_admin', 'nonce');
        if (!current_user_can('manage_woocommerce')) {
            wp_send_json_error(array('message' => __('Permission denied.', 'ai-eshop-optimizer')));
        }

        
        
        
        
        
        self::suppress_wc_customer_emails();

        $vendor_id = isset($_POST['vendor_id']) ? (int) $_POST['vendor_id'] : 0;
        
        
        
        $ids_raw = isset($_POST['ids']) ? (array) wp_unslash($_POST['ids']) : array();
        $ids_raw = array_map('sanitize_text_field', $ids_raw);
        
        
        $ids = array();
        foreach ($ids_raw as $raw) {
            $clean = preg_replace('/[^A-Za-z0-9\-_:.]/', '', (string) $raw);
            if ($clean !== '') { $ids[] = $clean; }
        }
        $ids = array_values(array_unique($ids));

        if ($vendor_id <= 0 || empty($ids)) {
            wp_send_json_error(array('message' => __('vendor_id and ids are required.', 'ai-eshop-optimizer')));
        }

        $vendor = AIEO_MOI_Vendor_Manager::get($vendor_id);
        if (!$vendor) { wp_send_json_error(array('message' => __('Vendor not found.', 'ai-eshop-optimizer'))); }
        $provider = AIEO_MOI_Provider_Registry::get((string) $vendor['provider']);
        if (!$provider) { wp_send_json_error(array('message' => __('Provider not registered.', 'ai-eshop-optimizer'))); }
        if (!method_exists($provider, 'fetch_one_canonical')) {
            wp_send_json_error(array('message' => __('Provider does not support chunked reconcile.', 'ai-eshop-optimizer')));
        }

        @set_time_limit(120); 

        $counters = array(
            'created'         => 0,
            'metadata_filled' => 0,
            'already_present' => 0,
            'errors'          => 0,
            'error_messages'  => array(),
            'processed'       => array(), 
        );

        foreach ($ids as $remote_id) {
            try {
                $canonical = $provider->fetch_one_canonical($vendor, $remote_id);
            } catch (\Throwable $e) {
                $counters['errors']++;
                if (count($counters['error_messages']) < 10) {
                    $counters['error_messages'][] = $remote_id . ': ' . $e->getMessage();
                }
                continue;
            }
            if (!is_array($canonical) || empty($canonical['remote_order_id'])) {
                $counters['errors']++;
                continue;
            }

            try {
                $result = AIEO_MOI_Order_Mapper::upsert_from_canonical($vendor, $canonical, 'reconcile');
            } catch (\Throwable $e) {
                $counters['errors']++;
                if (count($counters['error_messages']) < 10) {
                    $counters['error_messages'][] = $remote_id . ': ' . $e->getMessage();
                }
                continue;
            }

            $log_status = 'processed';
            $err_msg    = null;
            $outcome    = 'unknown';
            if (isset($result['error'])) {
                $counters['errors']++;
                $err_msg = is_wp_error($result['error']) ? $result['error']->get_error_message() : (string) $result['error'];
                if (count($counters['error_messages']) < 10) {
                    $counters['error_messages'][] = $remote_id . ': ' . $err_msg;
                }
                $log_status = 'failed';
                $outcome    = 'error';
            } elseif (!empty($result['created'])) {
                $counters['created']++;
                $outcome = 'created';
            } elseif (!empty($result['touched'])) {
                $counters['metadata_filled']++;
                $outcome = 'metadata_filled';
            } else {
                $counters['already_present']++;
                $outcome = 'already_present';
            }

            AIEO_MOI_Inbound_Log::record(array(
                'vendor_id'       => (int) $vendor['id'],
                'event_type'      => 'reconcile',
                'remote_order_id' => $remote_id,
                'status'          => $log_status,
                'error_text'      => $err_msg,
                'wc_order_id'     => isset($result['wc_order_id']) ? (int) $result['wc_order_id'] : null,
            ));

            $counters['processed'][] = array(
                'id'          => $remote_id,
                'wc_order_id' => isset($result['wc_order_id']) ? (int) $result['wc_order_id'] : 0,
                'outcome'     => $outcome,
            );
        }

        wp_send_json_success($counters);
    }

    
    public function ajax_legacy_scan() {
        check_ajax_referer('aieo_moi_admin', 'nonce');
        if (!current_user_can('manage_woocommerce')) {
            wp_send_json_error(array('message' => __('Permission denied.', 'ai-eshop-optimizer')));
        }
        $vendor_id = isset($_POST['vendor_id']) ? (int) $_POST['vendor_id'] : 0;
        $vendor    = $vendor_id > 0 ? AIEO_MOI_Vendor_Manager::get($vendor_id) : null;
        if (!$vendor) { wp_send_json_error(array('message' => __('Vendor not found.', 'ai-eshop-optimizer'))); }
        if ((string) $vendor['provider'] !== 'skroutz') {
            wp_send_json_error(array('message' => __('Legacy migration only applies to Skroutz vendors.', 'ai-eshop-optimizer')));
        }

        $total = AIEO_MOI_Legacy_Skroutz_Migrator::count_candidates($vendor_id);
        wp_send_json_success(array(
            'total'        => (int) $total,
            'vendor_label' => (string) $vendor['label'],
        ));
    }

    
    public function ajax_legacy_migrate_batch() {
        check_ajax_referer('aieo_moi_admin', 'nonce');
        if (!current_user_can('manage_woocommerce')) {
            wp_send_json_error(array('message' => __('Permission denied.', 'ai-eshop-optimizer')));
        }
        
        
        
        
        self::suppress_wc_customer_emails();

        $vendor_id = isset($_POST['vendor_id']) ? (int) $_POST['vendor_id'] : 0;
        $vendor    = $vendor_id > 0 ? AIEO_MOI_Vendor_Manager::get($vendor_id) : null;
        if (!$vendor) { wp_send_json_error(array('message' => __('Vendor not found.', 'ai-eshop-optimizer'))); }
        if ((string) $vendor['provider'] !== 'skroutz') {
            wp_send_json_error(array('message' => __('Legacy migration only applies to Skroutz vendors.', 'ai-eshop-optimizer')));
        }

        $batch_size = 25;
        @set_time_limit(120); 

        
        
        
        $ids = AIEO_MOI_Legacy_Skroutz_Migrator::find_candidate_ids($vendor_id, 0, $batch_size);

        $counters = array(
            'migrated'         => 0,
            'skipped_no_code'  => 0,
            'already_linked'   => 0,
            'failed'           => 0,
            'error_messages'   => array(),
        );

        foreach ($ids as $wc_order_id) {
            try {
                $result = AIEO_MOI_Legacy_Skroutz_Migrator::migrate_one($wc_order_id, $vendor);
            } catch (\Throwable $e) {
                $counters['failed']++;
                if (count($counters['error_messages']) < 10) {
                    $counters['error_messages'][] = '#' . $wc_order_id . ': ' . $e->getMessage();
                }
                continue;
            }
            switch ($result['status']) {
                case 'migrated':               $counters['migrated']++;        break;
                case 'skipped_already_linked': $counters['already_linked']++;  break;
                case 'skipped_no_code':        $counters['skipped_no_code']++; break;
                case 'failed':
                default:
                    $counters['failed']++;
                    if (!empty($result['error']) && count($counters['error_messages']) < 10) {
                        $counters['error_messages'][] = '#' . $wc_order_id . ': ' . $result['error'];
                    }
                    break;
            }
        }

        
        
        
        
        
        
        
        
        
        $progress_made = ($counters['migrated'] + $counters['already_linked'] + $counters['skipped_no_code']) > 0;
        $has_more      = count($ids) === $batch_size && $progress_made;
        $remaining     = AIEO_MOI_Legacy_Skroutz_Migrator::count_candidates($vendor_id);

        wp_send_json_success(array(
            'batch_size'      => $batch_size,
            'processed'       => count($ids),
            'remaining'       => (int) $remaining,
            'has_more'        => (bool) $has_more,
            'migrated'        => (int) $counters['migrated'],
            'already_linked'  => (int) $counters['already_linked'],
            'skipped_no_code' => (int) $counters['skipped_no_code'],
            'failed'          => (int) $counters['failed'],
            'error_messages'  => $counters['error_messages'],
        ));
    }
}
