// Nova Post column + per-row action buttons for Shoptet orders listing page.
//
// The install function runs in page MAIN world (via executeJavaScript or
// chrome.scripting.executeScript). Per-row actions call through
// `window.__np_bridge` which both the Electron preload and the Chrome
// extension content-script wiring must expose with these methods:
//   - createShipment({orderId, orderCode, detailUrl}) → {shipment, labelBase64, error?}
//   - downloadLabel(shipmentId)                       → {labelBase64, error?}
//   - printLabel(labelBase64)                         → {success}
(function (root) {

  // Install function — signature shaped by buildInstallScript below.
  // Inline styles instead of injected CSS so this works in any host
  // without shipping a separate stylesheet.
  function installOrdersListColumn(config) {
    var BTN_BASE = 'display:block;width:100%;margin-top:3px;padding:4px 6px;font:11px -apple-system,sans-serif;border-radius:4px;border:1px solid #e2e8f0;background:#fff;color:#1e293b;cursor:pointer;text-align:center;';
    var BTN_PRIMARY = BTN_BASE + 'background:#e30613;color:#fff;border-color:#e30613;font-weight:600;';
    var BTN_DANGER = BTN_BASE + 'color:#991b1b;border-color:#fecaca;';

    // Req-F-036 i18n — minimal translator over the dict the host pre-resolved
    // for the active locale. Missing key returns the key itself, which makes
    // gaps obvious in dev. config.translations is always present from
    // shoptet-bridge.js (defaults to CS).
    var translations = (config && config.translations) || {};
    function t(key) {
      return Object.prototype.hasOwnProperty.call(translations, key) ? translations[key] : key;
    }

    // NB: Shoptet's "Služby Shoptetu" onboarding promo (a cross-origin Userpilot
    // <iframe id="userpilot-checklist">) is no longer handled here — it cannot be
    // reached from page-JS. BC now hides it via CSS injected per page load (the
    // requests still load normally, so Shoptet sees no anomaly). See
    // main/content-block.js.

    var table = document.querySelector('#orders-listing table.checkbox-table');
    if (!table) return 'no-table';

    // Column headers (idempotent). Two columns side by side:
    //   1. "NP stav"  — read-only display of latest NP webhook event (Req-F-041)
    //   2. "Nova Post" — action buttons (Create / Print / Download)
    // Order: stav first (operator reads), then actions (operator clicks).
    var thead = table.querySelector('thead tr');
    if (thead && !thead.querySelector('.np-col-header')) {
      var th = document.createElement('th');
      th.className = 'np-col-header';
      th.textContent = t('ordersList.col.actions');
      th.style.cssText = 'background:#e30613;color:#fff;font-size:11px;padding:6px 8px;text-align:center;white-space:nowrap;';
      thead.insertBefore(th, thead.lastElementChild);
    }
    if (thead && !thead.querySelector('.np-status-col-header')) {
      var statusTh = document.createElement('th');
      statusTh.className = 'np-status-col-header';
      statusTh.textContent = t('ordersList.col.status');
      statusTh.style.cssText = 'background:#7a050d;color:#fff;font-size:11px;padding:6px 8px;text-align:center;white-space:nowrap;';
      // Insert immediately BEFORE the Nova Post actions header so order is: NP stav | Nova Post.
      var actionsHeader = thead.querySelector('.np-col-header');
      thead.insertBefore(statusTh, actionsHeader);
    }

    var DASHBOARD_URL = 'https://dashboard.biatec.cz/';

    // EU-27 membership for the three-tier customs-document logic on cell
    // rendering. Inline copy of shared/eu.js — orders-list.js runs in the
    // Shoptet admin MAIN world via executeJavaScript, so require() is not
    // available; keep the list small and self-contained.
    // Source of truth: tmp/01 Questions & Answers/nova-post-questions-final.xlsx
    // → "Shipment API Parameters" sheet, legend rows R86-R88.
    //   - lavender (international) = EU + UA + US — Commercial Invoice required
    //   - yellow (non-EU)          = UA + US      — Customs Declaration required
    // Values use truthy `1` (not `true`) — keep the lookup truthy-checked, NOT
    // strict-`=== true` (1 !== true would silently break every classification).
    var EU_SET = { AT:1, BE:1, BG:1, HR:1, CY:1, CZ:1, DK:1, EE:1, FI:1, FR:1,
                   DE:1, GR:1, HU:1, IE:1, IT:1, LV:1, LT:1, LU:1, MT:1, NL:1,
                   PL:1, PT:1, RO:1, SK:1, SI:1, ES:1, SE:1 };
    function isEU(cc) { return !!cc && !!EU_SET[String(cc).trim().toUpperCase()]; }
    var SENDER_CC = (config && config.senderCountryCode) ? String(config.senderCountryCode).trim().toUpperCase() : 'CZ';
    function shipmentClass(recipCC) {
      // .trim() guards against PostgreSQL CHAR(2) padding ("SK " etc.) that
      // pg-node can return depending on driver settings — without it, "SK "
      // would not match "SK" and the helper would mis-classify EU as non-EU.
      if (!recipCC) return null;
      var r = String(recipCC).trim().toUpperCase();
      if (!r) return null;
      if (r === SENDER_CC) return 'domestic';
      if (isEU(SENDER_CC) && isEU(r)) return 'eu';
      return 'non-eu';
    }
    // Build the customs-PDF button (Commercial Invoice) for a tracking
    // shipment. Returns '' for domestic — only label is needed.
    function customsPdfButtons(shipmentId, recipCC) {
      // OPEN-023 variant B (NP 2026-06-01): intra-EU shipments carry no invoice
      // block, so NP generates no Commercial Invoice for them — the invoice
      // button renders only for non-EU (CZ→UA/US). Domestic and intra-EU get
      // the label only.
      // Tic-0012 (Vasyl 2026-06): the NP 'international' doc (International
      // Waybill) is required ONLY for UA→EU — a direction none of our cases
      // cover (our sender is always CZ) — so we never print it. Keep marking
      // (thermal label) + Commercial Invoice only.
      var cls = shipmentClass(recipCC);
      if (cls !== 'non-eu') return '';
      var buttons = '<button class="np-btn-print-invoice" data-shipment-id="' + escape(shipmentId)
        + '" style="' + BTN_BASE + '">' + escape(t('ordersList.btn.printInvoice')) + '</button>';
      return buttons;
    }

    // True when this order row uses the merchant's Nova Post delivery method —
    // either the delivery-type label contains "Nova Post", or the row's shipping
    // id matches the NP branch/address id configured for this e-shop. Single
    // source of truth, used to gate the per-row NP UI AND the bulk selection/
    // count, so orders with another delivery method (e.g. Česká pošta) are never
    // counted or offered for NP shipment creation.
    function rowIsNovaPost(tr) {
      if (!tr) return false;
      var deliveryEl = tr.querySelector('[data-testid="orderRowDeliveryType"]');
      var deliveryType = (deliveryEl ? deliveryEl.textContent : '').trim().toLowerCase();
      if (deliveryType.indexOf('nova post') !== -1) return true;
      if (config.shippingBranchId || config.shippingAddressId) {
        var shippingIdEl = tr.querySelector('input[name^="shippingId"]') || tr.querySelector('[data-shipping-id]');
        var rowShippingId = shippingIdEl ? (shippingIdEl.value || shippingIdEl.getAttribute('data-shipping-id') || '') : '';
        if (rowShippingId && (rowShippingId === config.shippingBranchId || rowShippingId === config.shippingAddressId)) {
          return true;
        }
      }
      return false;
    }

    // Req-F-023 PR1: bulk-create toolbar — a bar above the orders list showing
    // the live selection count and a "Vytvořit NP zásilky" trigger.
    // Picks up Shoptet's native per-row checkboxes (name^="orderId").
    function ensureBulkToolbar() {
      if (document.getElementById('np-bulk-toolbar')) return;
      // Mount in NORMAL FLOW directly above the orders table. form#orders-listing
      // is a child of div.section.section-123 (after the h1 / tabs / filter / the
      // native "FUNKCE" mass-action row), so inserting our bar as its previous
      // sibling lands it right on top of the table.
      //
      // Do NOT mount inside `div.content-header` (the Uložit/Přidat/Nápověda
      // row): that element is position:absolute — Shoptet pins it to the top of
      // the section, OUTSIDE normal flow, ~80px ABOVE the "Přehled objednávek"
      // h1. A full-width bar dropped in there floats over the page and overlaps
      // the title. In-flow placement pushes the table down cleanly, no overlap;
      // and being non-positioned (below) it still paints under the FUNKCE
      // dropdown, so that menu isn't hidden either.
      var listingHost = document.querySelector('#orders-listing');
      if (!listingHost || !listingHost.parentNode) return;
      var bar = document.createElement('div');
      bar.id = 'np-bulk-toolbar';
      // Non-positioned (no position:sticky / no z-index) on purpose:
      // position:sticky creates its OWN stacking context in Chromium/Electron
      // regardless of the z-index value, so a sticky bar at any positive z
      // (was 50, then 5) painted OVER the FUNKCE dropdown. A non-positioned bar
      // lands in the block-flow paint group, BELOW any positioned native
      // dropdown by CSS rule — overlap-proof, no dependency on Shoptet's z-index.
      bar.style.cssText = 'width:100%;box-sizing:border-box;background:#1e293b;color:#fff;padding:8px 12px;display:flex;align-items:center;gap:12px;font:12px -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;border-bottom:2px solid #e30613;margin:6px 0;';
      bar.innerHTML = ''
        + '<span class="np-bulk-count" style="font-weight:600;min-width:140px"></span>'
        + '<button type="button" class="np-bulk-create" disabled '
        + 'style="padding:6px 14px;border-radius:4px;border:0;background:#e30613;color:#fff;font-weight:600;cursor:not-allowed;opacity:0.5;">'
        + escape(t('ordersList.bulk.create')) + '</button>'
        // Tic-0022 (1B): reprint marking labels for already-created NP shipments
        // among the selected orders (secondary action, slate button).
        + '<button type="button" class="np-bulk-print" disabled '
        + 'style="padding:6px 14px;border-radius:4px;border:0;background:#334155;color:#fff;font-weight:600;cursor:not-allowed;opacity:0.5;">'
        + escape(t('ordersList.bulk.printLabels')) + '</button>'
        + '<span class="np-bulk-hint" style="margin-left:auto;color:#94a3b8;font-size:11px">' + escape(t('ordersList.bulk.hint')) + '</span>';
      listingHost.parentNode.insertBefore(bar, listingHost);
      updateBulkToolbar();
    }

    function updateBulkToolbar() {
      var bar = document.getElementById('np-bulk-toolbar');
      if (!bar) return;
      var checked = document.querySelectorAll('#orders-listing input[type="checkbox"][name^="orderId"]:checked');
      // Count ONLY orders whose delivery method is the mapped NP one. Orders on
      // another carrier are ignored, so the count + button state reflect what
      // can actually be created as NP shipments.
      var n = 0;
      // Tic-0022: count selected NP orders that have an ACTIVE (non-cancelled)
      // shipment with a tracking number — those are the ones "Print labels" can
      // reprint. Uses the injected snapshot for responsive button state; the
      // modal re-resolves fresh from the BE before printing.
      var nPrint = 0;
      for (var i = 0; i < checked.length; i++) {
        if (!checked[i].value || !rowIsNovaPost(checked[i].closest('tr'))) continue;
        n++;
        var ex = config.shipmentsByOrderId[checked[i].value];
        // Match the BE "alive" rule (main/shoptet-bridge shipmentIsAlive): has a
        // tracking number and not error / cancelled / returned / refused /
        // utilization. The modal re-resolves fresh before printing.
        if (ex && ex.np_tracking_number && ex.status !== 'error'
            && ['cancelled', 'returned', 'refused', 'utilization'].indexOf(ex.status) === -1) nPrint++;
      }
      var countEl = bar.querySelector('.np-bulk-count');
      if (countEl) {
        countEl.textContent = n === 0
          ? t('ordersList.bulk.zeroSelected')
          : n + ' ' + t('ordersList.bulk.selected');
      }
      var btn = bar.querySelector('.np-bulk-create');
      if (btn) {
        btn.disabled = n === 0;
        btn.style.opacity = n === 0 ? '0.5' : '1';
        btn.style.cursor = n === 0 ? 'not-allowed' : 'pointer';
      }
      var printBtn = bar.querySelector('.np-bulk-print');
      if (printBtn) {
        printBtn.disabled = nPrint === 0;
        printBtn.style.opacity = nPrint === 0 ? '0.5' : '1';
        printBtn.style.cursor = nPrint === 0 ? 'not-allowed' : 'pointer';
      }
    }

    // One delegated change listener for the page lifetime — keeps the count
    // in sync as the operator ticks/unticks rows (including Shoptet's
    // "select-all" header checkbox, which fires change on each row).
    if (!window.__np_bulk_change_bound) {
      window.__np_bulk_change_bound = true;
      document.addEventListener('change', function (e) {
        if (e.target && e.target.matches && e.target.matches('input[type="checkbox"]')) {
          updateBulkToolbar();
        }
      });
    }

    ensureBulkToolbar();

    // Errors from the BE that point the operator at project settings (missing
    // sender phone, API key, etc.). Detected loosely on the persisted message
    // so legacy records — including older shorter wording — still render the
    // dashboard call-to-action.
    function isProjectSettingsError(msg) {
      var s = String(msg || '').toLowerCase();
      return s.indexOf('project settings') !== -1
        || s.indexOf('dashboard.biatec.cz') !== -1
        || s.indexOf('not configured') !== -1;
    }

    function buildCellHtml(order, existing) {
      // NP actions belong only on NP orders: either the delivery method is Nova
      // Post (so a shipment can be created), OR an NP shipment already exists for
      // this order (it was sent via NP → still show manage actions even if the
      // method label no longer matches the NP heuristic). Orders with any other
      // delivery method and no NP shipment get nothing — no NP buttons.
      var hasNpShipment = !!(existing && (existing.np_tracking_number || existing.status === 'error'));
      if (!order.isNovaPost && !hasNpShipment) {
        return '<span style="color:#cbd5e1">—</span>';
      }
      // Terminal-failure states — Req-F-019 "Vytvořit kopii". The original
      // shipment is preserved as audit; clicking the button creates a brand
      // new row + new NP tracking number (BE handles the dedup carve-out).
      // Covers: cancelled (Req-F-018 storno), returned (recipient absent /
      // refused), refused (explicit reject at handover), utilization (NP
      // disposed of the parcel after the holding window).
      var TERMINAL_FAILURES = ['cancelled', 'returned', 'refused', 'utilization'];
      if (existing && TERMINAL_FAILURES.indexOf(existing.status) !== -1) {
        var label = t('status.terminal.' + existing.status);
        return ''
          + '<div style="font-size:10px;color:#64748b;margin-bottom:3px;text-decoration:line-through">' + escape(existing.np_tracking_number || t('ordersList.cell.empty')) + '</div>'
          + '<div style="font-size:10px;color:#64748b;margin-bottom:3px">' + escape(label) + '</div>'
          + '<button class="np-btn-create" data-order-id="' + escape(order.orderId) + '"'
          + ' data-order-code="' + escape(order.orderCode) + '"'
          + ' data-detail-url="' + escape(order.detailUrl || '') + '"'
          + ' data-resend="1"'
          + ' style="' + BTN_PRIMARY + '">' + escape(t('ordersList.btn.resend')) + '</button>';
      }
      if (existing && existing.np_tracking_number && existing.status !== 'error') {
        // Storno button (Req-F-018) only on the active 'created' state — once
        // NP webhook flips us to in_transit / delivered / returned, NP rejects
        // DELETE and the button is hidden.
        var stornoBtn = (existing.status === 'created')
          ? '<button class="np-btn-cancel" data-shipment-id="' + escape(existing.id)
            + '" data-order-id="' + escape(order.orderId) + '" style="' + BTN_DANGER + '">' + escape(t('ordersList.btn.cancel')) + '</button>'
          : '';
        // Three-tier customs-PDF buttons (CI for EU+non-EU intl, customs decl
        // for non-EU only). Persisted recipient_country_code added 2026-05-06
        // (migration 018) — pre-018 rows have null and render no PDF buttons.
        var customsBtns = customsPdfButtons(existing.id, existing.recipient_country_code);
        return ''
          + '<div style="font-size:10px;color:#166534;margin-bottom:3px"><b>' + escape(existing.np_tracking_number) + '</b></div>'
          + '<button class="np-btn-print" data-shipment-id="' + escape(existing.id) + '" style="' + BTN_PRIMARY + '">' + escape(t('ordersList.btn.print')) + '</button>'
          + '<button class="np-btn-download" data-shipment-id="' + escape(existing.id) + '" style="' + BTN_BASE + '">' + escape(t('ordersList.btn.download')) + '</button>'
          + customsBtns
          + stornoBtn;
      }
      if (existing && existing.status === 'error') {
        var raw = existing.error_message || 'Error';
        var settingsLink = isProjectSettingsError(raw)
          ? '<button class="np-btn-open-settings" data-url="' + escape(DASHBOARD_URL)
            + '" style="' + BTN_PRIMARY + '">' + escape(t('ordersList.btn.openSettings')) + '</button>'
          : '';
        return ''
          + '<div style="font-size:10px;color:#991b1b;margin-bottom:3px">' + escape(raw) + '</div>'
          + settingsLink
          + '<button class="np-btn-retry" data-shipment-id="' + escape(existing.id) + '" style="' + BTN_DANGER + '">' + escape(t('ordersList.btn.retry')) + '</button>';
      }
      return ''
        + '<button class="np-btn-create" data-order-id="' + escape(order.orderId) + '"'
        + ' data-order-code="' + escape(order.orderCode) + '"'
        + ' data-detail-url="' + escape(order.detailUrl || '') + '"'
        + ' style="' + BTN_PRIMARY + '">' + escape(t('ordersList.btn.create')) + '</button>';
    }

    function escape(s) {
      return String(s == null ? '' : s).replace(/[&<>"']/g, function (ch) {
        return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[ch];
      });
    }

    // Categorize NP eventType into a display group with color hints. The full
    // mapping with internal status semantics is in
    // backend/src/services/np-event-mapping.js — this is the visual side only.
    function npEventDisplay(eventType, statusCode) {
      if (!eventType && (statusCode == null || statusCode === '')) return null;
      var GREEN = '#166534', RED = '#991b1b', YELLOW = '#92400e', GREY = '#475569';
      // Color is event-type specific (independent of locale); the label
      // comes from the active translation dict via t(). Keep short to fit
      // the column.
      var COLORS = {
        ReadyToShip: GREEN, LoadingCourier: GREEN, Received: GREEN,
        Deleted: GREY,
        Returned: RED, Refused: RED, Utilization: RED,
        MoneyTransfer: YELLOW, MoneyTransferReceived: GREEN,
        Redirecting: YELLOW, Redelivery: YELLOW, UndeliveryReason: YELLOW,
        ChangeTime: GREY,
        ArrivalSC: GREY, TransferToPartner: GREY,
        ArrivalSenderWarehouse: GREY, DepartureSenderWarehouse: GREY,
        InCityRecipient: YELLOW, AwaitingOnDivision: YELLOW
      };
      // Label preference (localized per BC locale via t()):
      //   1) precise per-code label npStatus.<code> — covers intermediate /
      //      customs states that several distinct codes collapse to one event
      //      NAME for, so the operator sees the exact status;
      //   2) the event-name label npEvent.<eventType>;
      //   3) the raw value, so an unmapped future code still shows something.
      var label = null;
      if (statusCode != null && statusCode !== '') {
        var ck = 'npStatus.' + statusCode;
        var cl = t(ck);
        if (cl && cl !== ck) label = cl;
      }
      if (!label && eventType) {
        var ek = 'npEvent.' + eventType;
        var el = t(ek);
        label = (el && el !== ek) ? el : eventType;
      }
      if (!label) label = String(statusCode || '');
      return { label: label, color: COLORS[eventType] || GREY };
    }

    function buildStatusCellHtml(existing) {
      if (!existing) return '<span style="color:#cbd5e1">—</span>';
      if (existing.np_status_event || existing.np_status_code) {
        var info = npEventDisplay(existing.np_status_event, existing.np_status_code);
        var when = '';
        if (existing.np_status_at) {
          // Compact "DD.MM. HH:MM" — Shoptet admin is in cs_CZ locale.
          var d = new Date(existing.np_status_at);
          if (!isNaN(d.getTime())) {
            var pad = function (n) { return n < 10 ? '0' + n : '' + n; };
            when = pad(d.getDate()) + '.' + pad(d.getMonth() + 1) + '. ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
          }
        }
        return '<div style="font-size:11px;color:' + info.color + ';font-weight:600;line-height:1.2">'
          + escape(info.label) + '</div>'
          + (when ? '<div style="font-size:10px;color:#64748b;margin-top:2px">' + escape(when) + '</div>' : '');
      }
      // Terminal local states (storno / return) without an NP event yet: show
      // the terminal label, never "created, awaiting update" — a cancelled
      // shipment is not waiting for anything.
      var TERMINAL = ['cancelled', 'returned', 'refused', 'utilization'];
      if (TERMINAL.indexOf(existing.status) !== -1) {
        return '<div style="font-size:11px;color:#475569;font-weight:600;line-height:1.2">'
          + escape(t('status.terminal.' + existing.status)) + '</div>';
      }
      if (existing.np_tracking_number) {
        return '<div style="font-size:10px;color:#64748b">' + escape(t('ordersList.cell.createdWaitingUpdate')) + '</div>';
      }
      return '<span style="color:#cbd5e1">' + escape(t('ordersList.cell.empty')) + '</span>';
    }

    // Rebuild each row cell (idempotent — replace existing .np-col-cell + .np-status-col-cell).
    var rows = table.querySelectorAll('tbody tr');
    for (var i = 0; i < rows.length; i++) {
      var tr = rows[i];
      var existingCell = tr.querySelector('.np-col-cell');
      if (existingCell) existingCell.remove();
      var existingStatusCell = tr.querySelector('.np-status-col-cell');
      if (existingStatusCell) existingStatusCell.remove();

      var checkbox = tr.querySelector('input[type="checkbox"][name^="orderId"]');
      var codeEl = tr.querySelector('[data-testid="orderCode"]');
      var detailLink = tr.querySelector('a.table__detailLink');

      var statusCell = document.createElement('td');
      statusCell.className = 'np-status-col-cell';
      statusCell.style.cssText = 'padding:4px 6px;vertical-align:top;min-width:90px;max-width:120px;text-align:center;';

      var cell = document.createElement('td');
      cell.className = 'np-col-cell';
      cell.style.cssText = 'padding:4px 6px;vertical-align:top;min-width:120px;max-width:160px;';

      if (!checkbox || !codeEl) {
        cell.innerHTML = '<span style="color:#cbd5e1">—</span>';
        statusCell.innerHTML = '<span style="color:#cbd5e1">—</span>';
      } else {
        var orderId = checkbox.value;
        var orderCode = codeEl.textContent.trim();
        var detailUrl = detailLink ? detailLink.getAttribute('href') : '';

        var isNovaPost = rowIsNovaPost(tr);

        var existing = config.shipmentsByOrderId[orderId];
        cell.innerHTML = buildCellHtml({
          orderId: orderId,
          orderCode: orderCode,
          detailUrl: detailUrl,
          isNovaPost: isNovaPost
        }, existing);
        // The NP status column shows ONLY for orders whose shipment was actually
        // sent via NP — i.e. an NP shipment row with a tracking number exists.
        // Not merely orders whose delivery method looks like NP (that label is a
        // fragile heuristic), and never for other delivery methods.
        var wasSentViaNp = !!(existing && existing.np_tracking_number);
        statusCell.innerHTML = wasSentViaNp ? buildStatusCellHtml(existing) : '<span style="color:#cbd5e1">—</span>';
      }

      // Insertion order matters: the second insertBefore lands BEFORE the
      // first, so insert "actions" (NP) first, then "status" (NP stav) — final
      // order in the row: NP stav | Nova Post | <last column>.
      tr.insertBefore(cell, tr.lastElementChild);
      tr.insertBefore(statusCell, cell);
    }

    // tfoot colspan — widen by 2 for our two new columns.
    var tfootCells = table.querySelectorAll('tfoot td[colspan]');
    for (var f = 0; f < tfootCells.length; f++) {
      var td = tfootCells[f];
      if (td.hasAttribute('data-np-fixed')) continue;
      var current = parseInt(td.getAttribute('colspan'), 10);
      if (current) {
        td.setAttribute('colspan', current + 2);
        td.setAttribute('data-np-fixed', '1');
      }
    }


    // Delegated click handler — attached once per document lifetime.
    if (!window.__np_orders_bound) {
      window.__np_orders_bound = true;
      document.addEventListener('click', onClick);
    }

    // Tic-0022 (1B): "Print labels" modal — reprint marking labels for the
    // selected orders that already have an active NP shipment. Rendered inline in
    // the injected page (no renderer round-trip); shipment state is re-resolved
    // FRESH from the BE (Q3), printing goes per-shipment through the shared
    // reprint path so a single NP failure doesn't abort the batch (Q2).
    function openPrintLabelsModal(orders) {
      var bridge = window.__np_bridge;
      if (!bridge || !bridge.resolveShipmentsForLabels || !bridge.printShipment) {
        alert(t('ordersList.bulk.notAvailable'));
        return;
      }
      var prev = document.getElementById('np-print-labels-modal');
      if (prev) prev.remove();

      var overlay = document.createElement('div');
      overlay.id = 'np-print-labels-modal';
      overlay.style.cssText = 'position:fixed;inset:0;background:rgba(15,23,42,0.55);z-index:2147483000;display:flex;align-items:center;justify-content:center;font:13px -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;';
      overlay.innerHTML = ''
        + '<div style="background:#fff;color:#0f172a;border-radius:10px;width:min(720px,92vw);max-height:85vh;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,0.35);">'
        +   '<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid #e2e8f0;">'
        +     '<strong style="font-size:15px;">' + escape(t('printLabels.title')) + '</strong>'
        +     '<button type="button" class="np-pl-close" aria-label="Close" style="border:0;background:#f1f5f9;color:#475569;width:30px;height:30px;border-radius:50%;font-size:20px;line-height:1;cursor:pointer;">&times;</button>'
        +   '</div>'
        +   '<div class="np-pl-body" style="padding:14px 18px;overflow:auto;">' + escape(t('printLabels.loading')) + '</div>'
        +   '<div style="display:flex;align-items:center;gap:12px;padding:12px 18px;border-top:1px solid #e2e8f0;">'
        +     '<span class="np-pl-msg" style="font-size:12px;color:#64748b;margin-right:auto;"></span>'
        +     '<button type="button" class="np-pl-cancel" style="padding:7px 16px;border-radius:6px;border:1px solid #cbd5e1;background:#fff;color:#334155;font-weight:600;cursor:pointer;">' + escape(t('printLabels.cancel')) + '</button>'
        +     '<button type="button" class="np-pl-print" disabled style="padding:7px 16px;border-radius:6px;border:0;background:#e30613;color:#fff;font-weight:600;cursor:not-allowed;opacity:0.5;">' + escape(t('printLabels.print')) + '</button>'
        +   '</div>'
        + '</div>';
      document.body.appendChild(overlay);

      var bodyEl = overlay.querySelector('.np-pl-body');
      var printEl = overlay.querySelector('.np-pl-print');
      var cancelEl = overlay.querySelector('.np-pl-cancel');
      var msgEl = overlay.querySelector('.np-pl-msg');
      var rows = [];

      function closeModal() { overlay.remove(); }
      overlay.querySelector('.np-pl-close').addEventListener('click', closeModal);
      cancelEl.addEventListener('click', function () { closeModal(); });
      overlay.addEventListener('click', function (ev) { if (ev.target === overlay) closeModal(); });

      function updatePrintBtn() {
        var c = 0;
        for (var r = 0; r < rows.length; r++) { if (rows[r].checkbox && rows[r].checkbox.checked) c++; }
        printEl.disabled = c === 0;
        printEl.style.opacity = c === 0 ? '0.5' : '1';
        printEl.style.cursor = c === 0 ? 'not-allowed' : 'pointer';
        printEl.textContent = t('printLabels.print') + (c ? ' (' + c + ')' : '');
      }

      bridge.resolveShipmentsForLabels(orders.map(function (o) { return o.orderId; })).then(function (res) {
        if (!res || res.error) {
          bodyEl.innerHTML = '<span style="color:#b91c1c;">' + escape(t('printLabels.errorPrefix') + ' ' + ((res && res.error) || '')) + '</span>';
          return;
        }
        var items = res.items || {};
        var html = ''
          + '<table style="width:100%;border-collapse:collapse;font-size:12px;">'
          +   '<thead><tr style="text-align:left;color:#64748b;border-bottom:1px solid #e2e8f0;">'
          +     '<th style="padding:6px 8px;width:28px;"></th>'
          +     '<th style="padding:6px 8px;">' + escape(t('printLabels.colOrder')) + '</th>'
          +     '<th style="padding:6px 8px;">' + escape(t('printLabels.colTracking')) + '</th>'
          +     '<th style="padding:6px 8px;">' + escape(t('printLabels.colCountry')) + '</th>'
          +   '</tr></thead><tbody>';
        for (var i = 0; i < orders.length; i++) {
          var o = orders[i];
          var it = items[o.orderId];
          if (it && it.active) {
            html += '<tr style="border-bottom:1px solid #f1f5f9;">'
              + '<td style="padding:6px 8px;"><input type="checkbox" class="np-pl-cb" data-sid="' + escape(it.shipmentId) + '" checked></td>'
              + '<td style="padding:6px 8px;font-weight:600;">' + escape(o.orderCode) + '</td>'
              + '<td style="padding:6px 8px;font-family:monospace;">' + escape(it.trackingNumber || '') + '</td>'
              + '<td style="padding:6px 8px;">' + escape(it.countryCode || '') + '</td>'
              + '</tr>';
          } else {
            html += '<tr style="border-bottom:1px solid #f1f5f9;color:#94a3b8;">'
              + '<td style="padding:6px 8px;"></td>'
              + '<td style="padding:6px 8px;">' + escape(o.orderCode) + '</td>'
              + '<td style="padding:6px 8px;" colspan="2"><em>' + escape(t('printLabels.noShipment')) + '</em></td>'
              + '</tr>';
          }
        }
        html += '</tbody></table>';
        bodyEl.innerHTML = html;

        var cbs = bodyEl.querySelectorAll('.np-pl-cb');
        for (var c = 0; c < cbs.length; c++) {
          rows.push({ shipmentId: cbs[c].getAttribute('data-sid'), checkbox: cbs[c] });
          cbs[c].addEventListener('change', updatePrintBtn);
        }
        if (!rows.length) msgEl.textContent = t('printLabels.noneToPrint');
        updatePrintBtn();
      }).catch(function (err) {
        bodyEl.innerHTML = '<span style="color:#b91c1c;">' + escape(t('printLabels.errorPrefix') + ' ' + (err && err.message || err)) + '</span>';
      });

      printEl.addEventListener('click', async function () {
        var targets = [];
        for (var r = 0; r < rows.length; r++) {
          if (rows[r].checkbox && rows[r].checkbox.checked) targets.push(rows[r].shipmentId);
        }
        if (!targets.length) return;
        printEl.disabled = true; printEl.style.opacity = '0.5'; printEl.style.cursor = 'not-allowed';
        cancelEl.disabled = true;
        var ok = 0; var failed = 0;
        for (var ti = 0; ti < targets.length; ti++) {
          msgEl.style.color = '#64748b';
          msgEl.textContent = t('printLabels.printing') + ' ' + (ti + 1) + '/' + targets.length;
          try {
            var pr = await bridge.printShipment(targets[ti], { what: 'label' });
            if (pr && pr.error) failed++; else ok++;
          } catch (e) { failed++; }
        }
        if (failed) {
          msgEl.style.color = '#b91c1c';
          msgEl.textContent = t('printLabels.printed') + ' ' + ok + ' · ' + t('printLabels.failed') + ' ' + failed;
        } else {
          msgEl.style.color = '#15803d';
          msgEl.textContent = t('printLabels.printed') + ' ' + ok;
        }
        cancelEl.disabled = false;
        cancelEl.textContent = t('printLabels.close');
      });
    }

    async function onClick(e) {
      var bridge = window.__np_bridge;
      if (!bridge) return;

      // Req-F-023 PR1: bulk-wizard trigger from the sticky toolbar.
      // Collects every checked orderId checkbox under #orders-listing and
      // — crucially — also harvests the orderCode + detailUrl from the same
      // <tr> so the main process can pass real values to
      // createShipmentAndFetchLabel (the single-order flow grabs them from
      // its dataset attributes; we mirror that path).
      var bulkBtn = e.target.closest('.np-bulk-create');
      if (bulkBtn) {
        e.preventDefault();
        if (bulkBtn.disabled) return;
        if (!bridge.bulkWizardStart) {
          alert(t('ordersList.bulk.notAvailable'));
          return;
        }
        var checkedBoxes = document.querySelectorAll('#orders-listing input[type="checkbox"][name^="orderId"]:checked');
        var selected = [];
        for (var bi = 0; bi < checkedBoxes.length; bi++) {
          var cb = checkedBoxes[bi];
          if (!cb.value) continue;
          var trEl = cb.closest('tr');
          // Skip orders on another delivery method — only NP orders can become
          // NP shipments.
          if (!rowIsNovaPost(trEl)) continue;
          var codeEl = trEl ? trEl.querySelector('[data-testid="orderCode"]') : null;
          var linkEl = trEl ? trEl.querySelector('a.table__detailLink') : null;
          selected.push({
            orderId: cb.value,
            orderCode: codeEl ? codeEl.textContent.trim() : '',
            detailUrl: linkEl ? linkEl.getAttribute('href') : ''
          });
        }
        if (!selected.length) return;
        var origLabel = bulkBtn.textContent;
        bulkBtn.disabled = true;
        bulkBtn.textContent = t('ordersList.bulk.starting');
        try {
          var bulkRes = await bridge.bulkWizardStart({
            // Keep selectedOrderIds for backwards compat with PR 1 stubs;
            // selectedOrders is the new richer payload.
            selectedOrderIds: selected.map(function (o) { return o.orderId; }),
            selectedOrders: selected
          });
          if (bulkRes && bulkRes.error) {
            alert(t('ordersList.bulk.notAvailable') + ' (' + bulkRes.error + ')');
          }
        } catch (bulkErr) {
          alert(t('ordersList.bulk.notAvailable') + ' (' + (bulkErr && bulkErr.message || bulkErr) + ')');
        } finally {
          bulkBtn.disabled = false;
          bulkBtn.textContent = origLabel;
          updateBulkToolbar();
        }
        return;
      }

      // Tic-0022 (1B): "Print labels" — reprint marking labels for the selected
      // orders that already have an active NP shipment.
      var printBtn2 = e.target.closest('.np-bulk-print');
      if (printBtn2) {
        e.preventDefault();
        if (printBtn2.disabled) return;
        var pboxes = document.querySelectorAll('#orders-listing input[type="checkbox"][name^="orderId"]:checked');
        var porders = [];
        for (var pi = 0; pi < pboxes.length; pi++) {
          var pcb = pboxes[pi];
          if (!pcb.value) continue;
          var ptr = pcb.closest('tr');
          if (!rowIsNovaPost(ptr)) continue;
          var pcodeEl = ptr ? ptr.querySelector('[data-testid="orderCode"]') : null;
          porders.push({ orderId: pcb.value, orderCode: pcodeEl ? pcodeEl.textContent.trim() : pcb.value });
        }
        if (porders.length) openPrintLabelsModal(porders);
        return;
      }

      var createBtn = e.target.closest('.np-btn-create');
      if (createBtn) {
        e.preventDefault();
        var cell = createBtn.closest('.np-col-cell');
        var originalCreateLabel = createBtn.textContent;
        var customsConfirmed = createBtn.dataset.customsConfirmed === '1';

        // Unified single-shipment routing (shared with the order-detail overlay
        // via bridge.routeShipmentStart): a NON-EU order is handed to the SAME
        // wizard as bulk — for a single order — which collects customs +
        // (non-EU B2B) EORI when missing and auto-skips it when cached
        // (F-016/017). Domestic / EU fall through to the direct 1-click create
        // below. A routing hiccup must never block shipping → fall through.
        if (!customsConfirmed && bridge.routeShipmentStart) {
          createBtn.disabled = true;
          createBtn.textContent = t('ordersList.btn.creating');
          var routed = null;
          try {
            routed = await bridge.routeShipmentStart({
              orderId: createBtn.dataset.orderId,
              orderCode: createBtn.dataset.orderCode,
              detailUrl: createBtn.dataset.detailUrl
            });
          } catch (rErr) { routed = null; }
          createBtn.disabled = false;
          createBtn.textContent = originalCreateLabel;
          if (routed && routed.handledByWizard) return; // non-EU → wizard took over
          // (domestic / EU, or a routing error → fall through to direct create)
        }

        createBtn.disabled = true;
        createBtn.textContent = t('ordersList.btn.creating');
        try {
          var res = await bridge.createShipment({
            orderId: createBtn.dataset.orderId,
            orderCode: createBtn.dataset.orderCode,
            detailUrl: createBtn.dataset.detailUrl,
            // We are on the orders LIST (no order form here), so ask the main
            // process to write the tracking + status back into the Shoptet order
            // after create — parity with the detail overlay + bulk. It navigates
            // to the order detail, saves, and restores this list (which reloads).
            writeBack: true
          });
          if (!res || res.error) throw new Error((res && res.error) || 'Failed');
          var s = res.shipment;
          if (!s) throw new Error('No shipment returned');
          if (s.status === 'error') {
            // Req-F-016/F-017 — cross-border customs cache incomplete. BE
            // surfaces missingCustomsRows[] + defaultHsCode; we hand them
            // off to the wizard which lets the operator fill in nameEng /
            // hsCode / countryOfOrigin per product, PATCHes the customs
            // cache, then re-clicks Create. Falls through to the generic
            // error UI when missingCustomsRows is absent (config / NP errors).
            if (s.missingCustomsRows && s.missingCustomsRows.length && bridge.bulkWizardStart) {
              // Customs/EORI incomplete — hand off to the unified wizard for this
              // single order (collects the data, then re-runs the create).
              bridge.bulkWizardStart({
                selectedOrderIds: [createBtn.dataset.orderId],
                selectedOrders: [{
                  orderId: createBtn.dataset.orderId,
                  orderCode: createBtn.dataset.orderCode,
                  detailUrl: createBtn.dataset.detailUrl
                }],
                origin: 'detail'
              });
              return;
            }
            var rawErr = s.error_message || 'Error';
            var settingsBtn = isProjectSettingsError(rawErr)
              ? '<button class="np-btn-open-settings" data-url="' + escape(DASHBOARD_URL)
                + '" style="' + BTN_PRIMARY + '">' + escape(t('ordersList.btn.openSettings')) + '</button>'
              : '';
            cell.innerHTML = '<div style="font-size:10px;color:#991b1b;margin-bottom:3px">' + escape(rawErr) + '</div>'
              + settingsBtn
              + '<button class="np-btn-retry" data-shipment-id="' + escape(s.id) + '" style="' + BTN_DANGER + '">' + escape(t('ordersList.btn.retry')) + '</button>';
            return;
          }
          // Success: cache label base64 on the cell so "Print" works without
          // another round trip.
          if (res.labelBase64) cell.dataset.labelBase64 = res.labelBase64;
          // Req-F-021/F-022: surface customs PDF buttons for cross-border
          // shipments (detected by presence of cod_currency != null OR
          // Three-tier customs-PDF buttons (per nova-post-questions-final.xlsx
          // → Shipment API Parameters legend R86-R88):
          //   domestic (CZ→CZ): label only;
          //   EU intl (CZ→SK/DE/PL/...):  + Commercial Invoice;
          //   non-EU intl (CZ→UA/US):     + CI + Customs Declaration.
          var customsBtns = customsPdfButtons(s.id, s && s.recipient_country_code);
          // Storno (Req-F-018) — a freshly created shipment is still ReadyToShip,
          // so show the cancel button immediately (parity with buildCellHtml,
          // which only renders it on a later page load). Once a webhook flips the
          // status past 'created' NP rejects DELETE and the page-load render hides it.
          var stornoBtn = (s.status === 'created')
            ? '<button class="np-btn-cancel" data-shipment-id="' + escape(s.id)
              + '" data-order-id="' + escape(createBtn.dataset.orderId) + '" style="' + BTN_DANGER + '">' + escape(t('ordersList.btn.cancel')) + '</button>'
            : '';
          cell.innerHTML = ''
            + '<div style="font-size:10px;color:#166534;margin-bottom:3px"><b>' + escape(s.np_tracking_number || t('ordersList.cell.noTracking')) + '</b></div>'
            + '<button class="np-btn-print-cached" data-shipment-id="' + escape(s.id) + '" style="' + BTN_PRIMARY + '">' + escape(t('ordersList.btn.print')) + '</button>'
            + '<button class="np-btn-download" data-shipment-id="' + escape(s.id) + '" style="' + BTN_BASE + '">' + escape(t('ordersList.btn.download')) + '</button>'
            + customsBtns
            + stornoBtn;
          if (res.labelBase64) cell.querySelector('.np-btn-print-cached').__labelB64 = res.labelBase64;

          // Flip Shoptet's order status dropdown to "Vyřizuje se" (ID -2) and
          // click the save-and-stay button so the change is persisted. Matches
          // what the Chrome extension does after creating a shipment.
          setOrderStatusAndSave(createBtn.dataset.orderId);
        } catch (err) {
          createBtn.disabled = false;
          createBtn.textContent = originalCreateLabel || t('ordersList.btn.create');
          alert(t('ordersList.err.createFailed') + ': ' + err.message);
        }
        return;
      }

      var printCachedBtn = e.target.closest('.np-btn-print-cached');
      if (printCachedBtn) {
        e.preventDefault();
        var b64 = printCachedBtn.__labelB64;
        if (!b64) {
          // Fall back to fetching label by ID.
          printCachedBtn.classList.remove('np-btn-print-cached');
          printCachedBtn.classList.add('np-btn-print');
          printCachedBtn.click();
          return;
        }
        await runPrint(printCachedBtn, b64);
        return;
      }

      var printBtn = e.target.closest('.np-btn-print');
      if (printBtn) {
        e.preventDefault();
        var shipmentId = printBtn.dataset.shipmentId;
        printBtn.disabled = true;
        printBtn.textContent = t('ordersList.btn.fetchingLabel');
        try {
          var res = await bridge.downloadLabel(shipmentId);
          if (!res || res.error) throw new Error((res && res.error) || 'Failed');
          await runPrint(printBtn, res.labelBase64);
        } catch (err) {
          printBtn.disabled = false;
          printBtn.textContent = t('ordersList.btn.print');
          alert(t('ordersList.err.printFailed') + ': ' + err.message);
        }
        return;
      }

      var dlBtn = e.target.closest('.np-btn-download');
      if (dlBtn) {
        e.preventDefault();
        var shipmentId2 = dlBtn.dataset.shipmentId;
        dlBtn.disabled = true;
        dlBtn.textContent = t('ordersList.btn.downloading');
        try {
          var res2 = await bridge.downloadLabel(shipmentId2);
          if (!res2 || res2.error) throw new Error((res2 && res2.error) || 'Failed');
          var a = document.createElement('a');
          a.href = 'data:application/pdf;base64,' + res2.labelBase64;
          a.download = 'label-' + shipmentId2 + '.pdf';
          a.click();
          dlBtn.disabled = false;
          dlBtn.textContent = t('ordersList.btn.download');
        } catch (err) {
          dlBtn.disabled = false;
          dlBtn.textContent = t('ordersList.btn.download');
          alert(t('ordersList.err.downloadFailed') + ': ' + err.message);
        }
        return;
      }

      var settingsOpenBtn = e.target.closest('.np-btn-open-settings');
      if (settingsOpenBtn) {
        e.preventDefault();
        var url = settingsOpenBtn.dataset.url || DASHBOARD_URL;
        if (bridge.openExternal) bridge.openExternal(url);
        else window.open(url, '_blank');
        return;
      }

      var retryBtn = e.target.closest('.np-btn-retry');
      if (retryBtn) {
        e.preventDefault();
        // Treat retry as a fresh create attempt: re-use parent row's data if we still have it.
        var cell2 = retryBtn.closest('.np-col-cell');
        var tr2 = cell2 && cell2.parentElement;
        var cb = tr2 ? tr2.querySelector('input[type="checkbox"][name^="orderId"]') : null;
        var codeEl2 = tr2 ? tr2.querySelector('[data-testid="orderCode"]') : null;
        var link = tr2 ? tr2.querySelector('a.table__detailLink') : null;
        if (!cb || !codeEl2) { alert(t('ordersList.err.rowDataUnavailable')); return; }
        cell2.innerHTML = '<button class="np-btn-create" data-order-id="' + escape(cb.value) + '"'
          + ' data-order-code="' + escape(codeEl2.textContent.trim()) + '"'
          + ' data-detail-url="' + escape(link ? link.getAttribute('href') : '') + '"'
          + ' style="' + BTN_PRIMARY + '">' + escape(t('ordersList.btn.create')) + '</button>';
        cell2.querySelector('.np-btn-create').click();
        return;
      }

      // Storno (Req-F-018) — cancel an active shipment. Confirms with operator,
      // calls BE which calls NP DELETE; on success flips the cell to the
      // cancelled state and reverts Shoptet order status to "Vyřizuje se" (-2).
      // Req-F-021 — print the Commercial Invoice. (The International Waybill
      // 'international' doc is never printed on our side — UA→EU only, Tic-0012.)
      var invBtn = e.target.closest('.np-btn-print-invoice');
      var pdfBtn = invBtn;
      if (pdfBtn) {
        e.preventDefault();
        // Print the customs PDF to the A4 printer (role 'a4'), mirroring the
        // order-detail + bulk customs print — NOT a browser download. Falls back
        // to download only if the print bridge is unavailable (older build).
        var sid = pdfBtn.dataset.shipmentId;
        var kind = 'invoice';
        pdfBtn.disabled = true;
        var orig = pdfBtn.textContent;
        if (bridge.printCustomsPdf) {
          pdfBtn.textContent = t('ordersList.btn.printing');
          try {
            var pr = await bridge.printCustomsPdf(sid, kind);
            if (!pr || pr.error || pr.success === false) throw new Error((pr && (pr.error || pr.failureReason)) || 'Failed');
            pdfBtn.textContent = t('ordersList.btn.sent');
            setTimeout(function () { pdfBtn.disabled = false; pdfBtn.textContent = orig; }, 1500);
          } catch (pdfErr) {
            pdfBtn.disabled = false;
            pdfBtn.textContent = orig;
            alert(t('ordersList.btn.printInvoice') + ': ' + pdfErr.message);
          }
          return;
        }
        if (!bridge.downloadCustomsPdf) { pdfBtn.disabled = false; pdfBtn.textContent = orig; alert(t('ordersList.err.cancelNotSupported')); return; }
        pdfBtn.textContent = t('ordersList.btn.fetchingLabel');
        try {
          var pdfRes = await bridge.downloadCustomsPdf(sid, kind);
          if (!pdfRes || pdfRes.error) throw new Error((pdfRes && pdfRes.error) || 'Failed');
          var a = document.createElement('a');
          a.href = 'data:application/pdf;base64,' + pdfRes.pdfBase64;
          a.download = 'invoice-' + sid + '.pdf';
          a.click();
        } catch (pdfErr) {
          alert(t('ordersList.btn.printInvoice') + ': ' + pdfErr.message);
        } finally {
          pdfBtn.disabled = false;
          pdfBtn.textContent = orig;
        }
        return;
      }

      var cancelBtn = e.target.closest('.np-btn-cancel');
      if (cancelBtn) {
        e.preventDefault();
        if (!bridge.cancelShipment) { alert(t('ordersList.err.cancelNotSupported')); return; }
        if (!window.confirm(t('ordersList.confirm.cancel'))) return;
        var cellC = cancelBtn.closest('.np-col-cell');
        var trackingEl = cellC && cellC.querySelector('div b');
        var tracking = trackingEl ? trackingEl.textContent : t('ordersList.cell.empty');
        var orderIdC = cancelBtn.dataset.orderId;
        cancelBtn.disabled = true;
        cancelBtn.textContent = t('ordersList.btn.cancelling');
        try {
          var resC = await bridge.cancelShipment(cancelBtn.dataset.shipmentId);
          if (!resC || resC.error) throw new Error((resC && resC.error) || t('ordersList.err.cancelFailed'));
          cellC.innerHTML = ''
            + '<div style="font-size:10px;color:#64748b;margin-bottom:3px;text-decoration:line-through">' + escape(tracking) + '</div>'
            + '<div style="font-size:10px;color:#64748b">' + escape(t('status.terminal.cancelled')) + '</div>';
          // Revert Shoptet order status to "Vyřizuje se" (system default -2)
          // — silently no-op if the dropdown doesn't have that code (always
          // does, it's a system default). No fallback needed.
          if (orderIdC) setOrderStatusWithFallback(orderIdC, -2, 'no-change');
        } catch (errC) {
          cancelBtn.disabled = false;
          cancelBtn.textContent = t('ordersList.btn.cancel');
          alert(t('ordersList.err.cancelFailed') + ': ' + errC.message);
        }
        return;
      }
    }

    // Flip the Shoptet order-status dropdown to a target code and click
    // Save-and-stay, IF the dropdown actually has that <option>. Used both at
    // shipment-create time (target = 32 'Balení a Expedice', fallback 'no-change')
    // and from the NP webhook event sync (Req-F-041, target varies per event).
    //
    // primaryCode    — string|number, e.g. 32, -3, '-2'
    // fallbackCode   — 'no-change' (silent no-op) | another code | null (treat
    //                  same as 'no-change')
    // Returns: { ok, applied?: code, reason?: 'select-not-found'|'primary-missing'|'both-missing' }
    //
    // SILENT no-op semantics: when primary missing AND fallback === 'no-change',
    // we do NOT show any UI nudge. The 'NP status' column tells operator the
    // truth; admin's workflow MUST NOT be interrupted (per Req-F-041 design).
    function setOrderStatusWithFallback(orderId, primaryCode, fallbackCode) {
      var sel = document.querySelector('select[name="statusId[' + orderId + ']"]');
      if (!sel) return { ok: false, reason: 'select-not-found' };

      function hasOption(code) {
        var v = String(code);
        for (var i = 0; i < sel.options.length; i++) {
          if (sel.options[i].value === v) return true;
        }
        return false;
      }

      var target;
      if (hasOption(primaryCode)) {
        target = String(primaryCode);
      } else if (fallbackCode == null || fallbackCode === 'no-change') {
        return { ok: true, applied: null, reason: 'primary-missing' };
      } else if (hasOption(fallbackCode)) {
        target = String(fallbackCode);
      } else {
        return { ok: false, reason: 'both-missing' };
      }

      sel.value = target;
      sel.dispatchEvent(new Event('change', { bubbles: true }));
      var saveBtn = document.querySelector('a.submit-js[rel="saveAndStay"], a.submit-js[data-testid="buttonSaveAndStay"]');
      if (saveBtn) {
        saveBtn.click();
        // After Shoptet's AJAX save, the table may re-render and strip our
        // column. Ask the host to re-install shortly after.
        if (window.__np_bridge && window.__np_bridge.refreshUi) {
          setTimeout(function () { window.__np_bridge.refreshUi(); }, 1800);
        }
      }
      return { ok: true, applied: target };
    }

    // Backwards-compatible wrapper. Original behavior: order goes to 'Vyřizuje
    // se' (-2). Per the new global mapping, after shipment create we move to
    // 'Balení a Expedice' (32) when available, else stay (no-change). The
    // status dropdown WILL contain -2 (system default), but we no longer want
    // to overwrite the merchant's current state to -2 — keeping it as the
    // ultimate fallback would be misleading because -2 is just the default.
    function setOrderStatusAndSave(orderId) {
      return setOrderStatusWithFallback(orderId, 32, 'no-change');
    }

    async function runPrint(btn, labelBase64) {
      btn.disabled = true;
      btn.textContent = t('ordersList.btn.printing');
      try {
        var res = await window.__np_bridge.printLabel(labelBase64);
        if (!res || res.success === false) {
          throw new Error((res && (res.failureReason || res.error)) || 'Printing failed');
        }
        btn.textContent = t('ordersList.btn.sent');
        setTimeout(function () { btn.textContent = t('ordersList.btn.printAgain'); btn.disabled = false; }, 1500);
      } catch (err) {
        btn.disabled = false;
        btn.textContent = t('ordersList.btn.print');
        alert(t('ordersList.err.printFailed') + ': ' + (err && err.message || err));
      }
    }

    return 'ok';
  }

  // Remove everything installOrdersListColumn injected into the Shoptet orders
  // list — the NP column headers/cells, the bulk toolbar, any open customs
  // wizard. Runs in page MAIN world on BC logout so the buttons disappear
  // immediately, without forcing a page reload.
  //
  // We deliberately leave the document-level delegated click/change listeners
  // (guarded by window.__np_orders_bound / __np_bulk_change_bound) bound: they
  // dispatch on NP elements that no longer exist (harmless no-ops), and keeping
  // the guard flags set means a later re-inject after re-login won't double-bind.
  function uninstallOrdersListColumn() {
    var selectors = [
      '.np-col-header', '.np-status-col-header',
      '.np-col-cell', '.np-status-col-cell',
      '#np-bulk-toolbar', '#np-customs-wizard-overlay'
    ];
    for (var i = 0; i < selectors.length; i++) {
      var nodes = document.querySelectorAll(selectors[i]);
      for (var j = 0; j < nodes.length; j++) {
        var n = nodes[j];
        if (n && n.parentNode) n.parentNode.removeChild(n);
      }
    }
    return 'ok';
  }

  function buildInstallScript(config) {
    return '(' + installOrdersListColumn.toString() + ')(' + JSON.stringify(config || {}) + ')';
  }

  function buildUninstallScript() {
    return '(' + uninstallOrdersListColumn.toString() + ')()';
  }

  var api = {
    installOrdersListColumn: installOrdersListColumn,
    buildInstallScript: buildInstallScript,
    uninstallOrdersListColumn: uninstallOrdersListColumn,
    buildUninstallScript: buildUninstallScript
  };
  if (typeof module !== 'undefined' && module.exports) module.exports = api;
  else { root.NP = root.NP || {}; root.NP.shoptetOrdersList = api; }
})(typeof self !== 'undefined' ? self : this);
