// Overlay UI injected into Shoptet orders-detail pages.
// Builds a floating card with "Create Nova Post shipment" action + result area.
//
// Exposed as a source string that both the Chrome extension and Electron app
// can inject via their own mechanisms (chrome.scripting.executeScript in MAIN
// world, or webContents.executeJavaScript). The overlay calls back into the
// host via `window.__np_bridge` (Electron, exposed through contextBridge) or
// `chrome.runtime.sendMessage` (extension). Each host is responsible for
// providing that bridge.
(function (root) {
  // Source of the function the overlay will define on the page. Written as a
  // normal function so escaping stays readable; .toString() + `(fn)(args)` is
  // used to build the injection source.
  function installOverlay(orderId, orderCode, detailUrl, opts) {
    opts = opts || {};
    if (document.getElementById('np-overlay')) return 'already';

    // Req-F-036 i18n: the bridge pre-resolves the active-locale dict (keyed off
    // the Shoptet admin's <html lang>) and passes it in via opts.translations.
    // t(key) returns the localized string, or the key itself when missing so
    // gaps are obvious in dev. Most strings in this file are still hardcoded
    // pending full F-036 coverage; the cancel-shipment flow is wired through t().
    var translations = opts.translations || {};
    function t(key) {
      return Object.prototype.hasOwnProperty.call(translations, key) ? translations[key] : key;
    }

    // Our modal <select>s inherit the Shoptet page font (font:inherit), which
    // carries a tight line-height that clips the selected text/diacritics (e.g.
    // "Slovakia (SK)", "Stornováno") — Samuel 2026-06-19. Force a roomy
    // line-height + auto height on the picker/cancel selects, with !important so
    // it wins over both the inline style and any Shoptet select CSS.
    if (!document.getElementById('np-overlay-style')) {
      var npStyle = document.createElement('style');
      npStyle.id = 'np-overlay-style';
      npStyle.textContent = '#np-picker-modal select,#np-cancel-modal select{'
        + 'line-height:1.6!important;height:auto!important;box-sizing:border-box!important}';
      document.head.appendChild(npStyle);
    }

    // BE error messages may include a dashboard URL the operator needs to
    // visit (e.g. "Sender phone not configured. Open https://… → Edit project").
    // Convert any http(s) URL into a clickable link that opens in the system
    // browser via the Electron bridge (Chrome extension users get a normal
    // target=_blank link, which is also fine).
    function escapeText(s) {
      return String(s == null ? '' : s)
        .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    }
    // Remove the Nova Post branch marker from a remark, preserving customer text.
    // The marker is self-identifying — "Nova Post: … [NP:id]" — so we excise ONLY
    // it (even when the customer wrote on the same line). The leading \n? also
    // drops the marker's own-line newline so no blank line is left behind. A
    // second pass clears any stray bare [NP:id] left by an older widget / other
    // tool.
    function stripNpRemark(s) {
      return String(s == null ? '' : s)
        .replace(/\n?Nova Post:[^[\]]*\[NP:[^\]]+\]/gi, '')
        .replace(/\n?\[NP:[^\]]+\]/g, '')
        .trim();
    }
    function linkifyError(s) {
      var safe = escapeText(s);
      return safe.replace(/(https?:\/\/[^\s<>"]+)/g, function (url) {
        return '<a href="' + url + '" data-np-external="1" '
          + 'style="color:#fff;text-decoration:underline;font-weight:600">' + url + '</a>';
      });
    }
    function bindExternalLinks(container) {
      var links = container.querySelectorAll('a[data-np-external="1"]');
      for (var i = 0; i < links.length; i++) {
        links[i].addEventListener('click', function (ev) {
          var bridge = window.__np_bridge;
          if (bridge && bridge.openExternal) {
            ev.preventDefault();
            bridge.openExternal(this.getAttribute('href'));
          }
          // No bridge → fall through to default (opens in current tab; our
          // extension users still get the URL, just not in the system browser).
        });
      }
      // Settings button — same external-open path, plus an explicit fallback
      // so older bridges without openExternal still drop the user somewhere.
      var settingsBtns = container.querySelectorAll('.np-overlay-open-settings');
      for (var j = 0; j < settingsBtns.length; j++) {
        settingsBtns[j].addEventListener('click', function (ev) {
          ev.preventDefault();
          var url = this.getAttribute('data-url') || 'https://dashboard.biatec.cz/';
          var bridge = window.__np_bridge;
          if (bridge && bridge.openExternal) bridge.openExternal(url);
          else window.open(url, '_blank');
        });
      }
    }

    var DASHBOARD_URL = 'https://dashboard.biatec.cz/';
    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 buildSettingsButtonHtml(rawMsg) {
      if (!isProjectSettingsError(rawMsg)) return '';
      return '<button class="np-overlay-open-settings" data-url="' + DASHBOARD_URL + '"'
        + ' style="margin-top:8px;width:100%;padding:8px;background:#1e293b;color:#fff;border:none;border-radius:6px;font:inherit;cursor:pointer;font-weight:600">'
        + 'Open project settings ↗</button>';
    }

    // Append NP buttons directly into Shoptet's action-buttons container
    // (`#document-update > fieldset > div.content-header > div`, also
    // selectable as `.content-buttons`). Sits inline with ULOZIT / SMS /
    // EXPORT / etc. so spacing inherits Shoptet's own gap/wrap rules and
    // the < > order-nav arrows below stay positioned correctly.
    var createBtn = document.createElement('button');
    createBtn.id = 'np-create-btn';
    createBtn.type = 'button';
    createBtn.className = 'btn btn-sm';
    createBtn.style.cssText = 'background:#e30613;color:#fff;border-color:#e30613';
    createBtn.textContent = t('orderDetail.section.create');

    // Req-F-026 use case (2): operator overrides recipient branch before
    // shipping. Click → branch picker modal; selected branch is written via
    // the Úprava kontaktu modal (canonical store), with a fallback to
    // shopRemark.
    var changeBranchBtn = document.createElement('button');
    changeBranchBtn.id = 'np-change-branch-btn';
    changeBranchBtn.type = 'button';
    changeBranchBtn.className = 'btn btn-sm btn-secondary';
    changeBranchBtn.textContent = t('orderDetail.btn.changeBranch');

    // (b) Tic-0032 — optional manual parcel dimensions + weight. Lets the operator
    // pass the REAL box for THIS shipment (skips automatic sizing). Empty = auto.
    // Stored in the closure; applied on the next "Create" click. cm / kg.
    var manualDims = null; // { l, w, h, wt } — any subset of positive numbers
    var dimsBtn = document.createElement('button');
    dimsBtn.id = 'np-dims-btn';
    dimsBtn.type = 'button';
    dimsBtn.className = 'btn btn-sm btn-secondary';
    dimsBtn.style.cssText = 'margin-left:6px';
    function renderDimsBtn() {
      dimsBtn.textContent = '📦 ' + t('orderDetail.dims.btn') + (manualDims ? ' ✓' : '');
      dimsBtn.style.borderColor = manualDims ? '#16a34a' : '';
      dimsBtn.style.color = manualDims ? '#166534' : '';
    }
    renderDimsBtn();

    // Req-F-018 storno — when this order already has an active NP shipment
    // (passed in via opts.existingShipment from the bridge), offer "Cancel
    // shipment" on the detail page (where the tracking field + status select
    // both live) instead of "Create".
    var existingShipment = opts.existingShipment || null;
    var TERMINAL = ['cancelled', 'returned', 'refused', 'utilization', 'delivered'];
    var cancellable = !!(existingShipment && existingShipment.np_tracking_number
      && TERMINAL.indexOf(existingShipment.status) === -1);
    var cancelBtn = document.createElement('button');
    cancelBtn.id = 'np-cancel-btn';
    cancelBtn.type = 'button';
    cancelBtn.className = 'btn btn-sm';
    cancelBtn.style.cssText = 'background:#b91c1c;color:#fff;border-color:#b91c1c';
    cancelBtn.textContent = t('orderDetail.btn.cancel');

    // Re-print an existing shipment from the detail page. Two separate buttons
    // so the operator can route the thermal label (10×10) and the A4 customs
    // papers to two DIFFERENT printers — each prints on its own click to its own
    // saved printer (asked once per role). The customs button shows only for
    // cross-border shipments (where customs papers exist).
    var crossBorder = !!opts.crossBorder;
    var printLabelBtn = document.createElement('button');
    printLabelBtn.id = 'np-print-label-btn';
    printLabelBtn.type = 'button';
    printLabelBtn.className = 'btn btn-sm btn-secondary';
    printLabelBtn.style.cssText = 'margin-right:6px';
    printLabelBtn.textContent = t('orderDetail.btn.printLabel');

    var printCustomsBtn = document.createElement('button');
    printCustomsBtn.id = 'np-print-customs-btn';
    printCustomsBtn.type = 'button';
    printCustomsBtn.className = 'btn btn-sm btn-secondary';
    printCustomsBtn.style.cssText = 'margin-right:6px';
    // Cross-border A4 print = Commercial Invoice only. The NP 'international'
    // doc (International Waybill) is required ONLY for UA→EU — a direction none
    // of our cases cover (sender is always CZ) — so we never print it
    // (Tic-0012, Vasyl 2026-06). Keep marking (thermal label) + invoice.
    printCustomsBtn.textContent = t('orderDetail.btn.printInvoice');

    // Result message — hidden by default, shown only when a click produces
    // status to convey. Inserted as a dedicated row before #order-header
    // so it doesn't add empty space when there's nothing to display.
    var result = document.createElement('div');
    result.id = 'np-result';
    result.style.cssText = 'display:none;margin:16px 0 6px;padding:8px 12px;font-size:12px;border:1px solid #fecaca;background:#fef2f2;border-radius:6px;color:#1e293b';
    var origInnerHtmlSet = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML').set;
    Object.defineProperty(result, 'innerHTML', {
      configurable: true,
      get: function () { return result.firstChild ? result.firstChild.parentNode.innerHTML : ''; },
      set: function (html) {
        origInnerHtmlSet.call(result, html);
        result.style.display = html ? 'block' : 'none';
      }
    });

    var actionRow = document.querySelector('#document-update > fieldset > div.content-header > div')
      || document.querySelector('.content-buttons')
      || (document.querySelector('[data-testid="buttonSaveAndStay"]') || {}).parentElement
      || document.querySelector('.content-header');
    if (actionRow) {
      if (cancellable) {
        actionRow.appendChild(printLabelBtn);
        if (crossBorder) actionRow.appendChild(printCustomsBtn);
        actionRow.appendChild(cancelBtn);
      } else {
        actionRow.appendChild(createBtn);
        actionRow.appendChild(changeBranchBtn);
        actionRow.appendChild(dimsBtn);
      }
    }
    // Adding NP buttons grows the action row's height (flex-wrap to a 2nd
    // line). The < > order-nav arrows in #order-header are positioned for
    // the original single-line action bar height, so they overlap our
    // wrapped row. Push #order-header down by an extra delta that adapts
    // to whether Shoptet's action bar is in normal or floating-on-scroll
    // mode (.content-header-floating class — different CSS, different
    // height). MutationObserver re-applies the delta when the class flips.
    var orderHeader = document.getElementById('order-header');
    var contentHeader = document.querySelector('.content-header');
    if (orderHeader) {
      var baseMt = parseInt(window.getComputedStyle(orderHeader).marginTop, 10) || 0;
      function applyOrderHeaderMargin() {
        var floating = contentHeader && contentHeader.classList.contains('content-header-floating');
        // 10 px when normal, 50 px when floating — sametic.cz pilot
        // 2026-05-08: floating bar's larger height + extra padding shrinks
        // the visible gap below it; bigger delta restores parity.
        orderHeader.style.marginTop = (baseMt + (floating ? 50 : 10)) + 'px';
      }
      applyOrderHeaderMargin();
      if (contentHeader && typeof MutationObserver !== 'undefined') {
        var obs = new MutationObserver(applyOrderHeaderMargin);
        obs.observe(contentHeader, { attributes: true, attributeFilter: ['class'] });
        // Stash the observer + element so uninstall (BC logout) can stop
        // re-applying the push-down and restore the original margin.
        window.__npOrderHeaderObs = obs;
      }
      window.__npOrderHeaderEl = orderHeader;
    }

    // Result row before #order-header (hidden until something fills it).
    if (orderHeader) {
      orderHeader.insertAdjacentElement('beforebegin', result);
    } else if (actionRow) {
      actionRow.insertAdjacentElement('afterend', result);
    } else {
      document.body.insertBefore(result, document.body.firstChild);
    }

    // Marker so the existing `if (document.getElementById('np-overlay'))`
    // re-injection guard at the top of installOverlay still works after we
    // dropped the wrapper card.
    var marker = document.createElement('div');
    marker.id = 'np-overlay';
    marker.style.display = 'none';
    document.body.appendChild(marker);

    var btn = createBtn;

    // Tic-0444 — Nova Post shipment info inside the order-detail "Zásilky"
    // (Shipments) tab. Shoptet's native Shipments tab is empty for NP orders
    // ("Žádné položky"), so the operator saw no shipment summary there. We
    // inject — into that tab panel, reached by clicking "Zásilky" — the
    // tracking number as a link to the NP tracking page, the current status,
    // and the print/cancel actions; and, when nothing has shipped yet, a
    // Create button. Adapts to domestic vs cross-border (customs A4 button
    // only for non-EU, same gate as the toolbar). Wired to the SAME handlers
    // (wirePrintButton / openCancelDialog / createBtn) so there is no second
    // copy of the create/print/cancel logic to keep in sync. Deliberately NOT
    // a standalone always-visible block — that duplicated the toolbar/general
    // info; the tab is the shipment's natural home (Samuel 2026-07-06).

    // Public NP parcel-tracking page — the number is a path segment
    // (…/tracking/<number>), confirmed by NP. Merchant is CZ → cs-cz locale.
    // Centralised here so the format lives in one place.
    function npTrackingUrl(num) {
      return 'https://novapost.com/cs-cz/tracking/' + encodeURIComponent(num);
    }

    // Localised status label + colour for the section badge. Ported from the
    // orders-list NP-status column (shared/shoptet/orders-list.js) so the
    // detail page and the list agree. Prefers the precise per-code label, then
    // the event name, then a terminal/awaiting fallback.
    function npStatusInfo(s) {
      var GREEN = '#166534', RED = '#991b1b', YELLOW = '#92400e', GREY = '#475569';
      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
      };
      var ev = s.np_status_event, code = s.np_status_code;
      if (ev || (code != null && code !== '')) {
        var label = null;
        if (code != null && code !== '') { var ck = 'npStatus.' + code; var cl = t(ck); if (cl && cl !== ck) label = cl; }
        if (!label && ev) { var ek = 'npEvent.' + ev; var el = t(ek); label = (el && el !== ek) ? el : ev; }
        if (!label) label = String(code || '');
        return { label: label, color: COLORS[ev] || GREY };
      }
      var TERMINAL_LOCAL = ['cancelled', 'returned', 'refused', 'utilization'];
      if (TERMINAL_LOCAL.indexOf(s.status) !== -1) return { label: t('status.terminal.' + s.status), color: GREY };
      if (s.np_tracking_number) return { label: t('ordersList.cell.createdWaitingUpdate'), color: '#64748b' };
      return { label: '', color: GREY };
    }

    var hasShipment = !!(existingShipment && existingShipment.np_tracking_number
      && existingShipment.status !== 'error');

    // Section action buttons (own copies; the toolbar keeps its own set).
    var secPrintLabelBtn = document.createElement('button');
    secPrintLabelBtn.id = 'np-sec-print-label';
    secPrintLabelBtn.type = 'button';
    secPrintLabelBtn.className = 'btn btn-sm btn-secondary';
    secPrintLabelBtn.textContent = '🖨 ' + t('orderDetail.btn.printLabel');

    var secPrintInvoiceBtn = document.createElement('button');
    secPrintInvoiceBtn.id = 'np-sec-print-invoice';
    secPrintInvoiceBtn.type = 'button';
    secPrintInvoiceBtn.className = 'btn btn-sm btn-secondary';
    secPrintInvoiceBtn.textContent = '🧾 ' + t('orderDetail.btn.printInvoice');

    var secCancelBtn = document.createElement('button');
    secCancelBtn.id = 'np-sec-cancel';
    secCancelBtn.type = 'button';
    secCancelBtn.className = 'btn btn-sm';
    secCancelBtn.style.cssText = 'background:#b91c1c;color:#fff;border-color:#b91c1c';
    secCancelBtn.textContent = '🗙 ' + t('orderDetail.btn.cancel');

    var secCreateBtn = document.createElement('button');
    secCreateBtn.id = 'np-sec-create';
    secCreateBtn.type = 'button';
    secCreateBtn.className = 'btn btn-sm';
    secCreateBtn.style.cssText = 'background:#e30613;color:#fff;border-color:#e30613';
    secCreateBtn.textContent = t('orderDetail.section.create');

    var section = document.createElement('div');
    section.id = 'np-section';
    // Lives inside the Shoptet tab panel, so no card border/background — just
    // padding matching the panel's own content rhythm.
    section.style.cssText = 'padding:12px 4px 6px;font:13px -apple-system,BlinkMacSystemFont,sans-serif;color:#1e293b';
    var secHead = document.createElement('div');
    secHead.style.cssText = 'font-weight:700;color:#e30613;font-size:14px;margin-bottom:10px';
    secHead.textContent = t('orderDetail.section.title');
    section.appendChild(secHead);
    var secBody = document.createElement('div');
    section.appendChild(secBody);

    if (hasShipment) {
      var st = npStatusInfo(existingShipment);
      var trackUrl = npTrackingUrl(existingShipment.np_tracking_number);
      var info = document.createElement('div');
      info.style.cssText = 'display:flex;flex-wrap:wrap;align-items:center;gap:6px 22px;margin-bottom:12px';
      info.innerHTML =
        '<div><span style="color:#64748b">' + escapeText(t('orderDetail.section.trackingLabel')) + ': </span>'
        + '<a href="' + escapeText(trackUrl) + '" data-np-external="1" title="' + escapeText(t('orderDetail.section.trackHint')) + '"'
        + ' style="color:#e30613;font-weight:700;text-decoration:underline">' + escapeText(existingShipment.np_tracking_number) + '</a></div>'
        + (st.label
          ? '<div><span style="color:#64748b">' + escapeText(t('orderDetail.section.statusLabel')) + ': </span>'
            + '<span style="font-weight:700;color:' + st.color + '">' + escapeText(st.label) + '</span></div>'
          : '');
      secBody.appendChild(info);
      bindExternalLinks(info);

      // A cancelled shipment is DELETED in Nova Post, so its label/invoice no
      // longer exist there — printing 404s ("shipments not found"). Show the
      // tracking + status (informational) but drop the print buttons and offer
      // re-shipping (Create) instead. Other states (in transit / delivered /
      // returned) keep the shipment on NP's side, so printing still works.
      var shipmentDeleted = existingShipment.status === 'cancelled';
      var secBtnRow = document.createElement('div');
      secBtnRow.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px';
      if (!shipmentDeleted) {
        secBtnRow.appendChild(secPrintLabelBtn);
        if (crossBorder) secBtnRow.appendChild(secPrintInvoiceBtn);
        wirePrintButton(secPrintLabelBtn, 'label');
        wirePrintButton(secPrintInvoiceBtn, 'customs');
      }
      if (cancellable) {
        secBtnRow.appendChild(secCancelBtn);
        secCancelBtn.addEventListener('click', openCancelDialog);
      }
      if (shipmentDeleted) {
        secBtnRow.appendChild(secCreateBtn);
        secCreateBtn.addEventListener('click', function () { createBtn.click(); });
      }
      secBody.appendChild(secBtnRow);

      // Status timeline (Tic-0444) — the full dated history, fetched LIVE from
      // Nova Post on demand (bridge.refreshShipment → BE /shipments/:id/refresh
      // → NP FullTracking). Lazy: one NP round-trip only when the operator asks,
      // and only for shipments still present at NP (a cancelled one is deleted
      // there, so tracking 404s — skip it).
      if (!shipmentDeleted) {
        var tlWrap = document.createElement('div');
        tlWrap.style.cssText = 'margin-top:16px';
        var tlBtn = document.createElement('button');
        tlBtn.type = 'button';
        tlBtn.className = 'btn btn-sm btn-secondary';
        tlBtn.textContent = '📜 ' + t('orderDetail.timeline.load');
        var tlBox = document.createElement('div');
        tlBox.style.cssText = 'margin-top:10px';
        tlWrap.appendChild(tlBtn);
        tlWrap.appendChild(tlBox);
        secBody.appendChild(tlWrap);

        function renderTimeline(box, entries) {
          if (!entries || !entries.length) {
            box.innerHTML = '<div style="color:#64748b;font-size:12px">' + escapeText(t('orderDetail.timeline.empty')) + '</div>';
            return;
          }
          var sorted = entries.slice().sort(function (a, b) { return (Date.parse(b.date) || 0) - (Date.parse(a.date) || 0); });
          var pad = function (n) { return n < 10 ? '0' + n : '' + n; };
          var html = '<div style="font-weight:600;margin-bottom:8px">' + escapeText(t('orderDetail.timeline.title')) + '</div>';
          html += '<ul style="list-style:none;margin:0;padding:0 0 0 4px;border-left:2px solid #e2e8f0">';
          for (var i = 0; i < sorted.length; i++) {
            var e = sorted[i];
            var d = e.date ? new Date(e.date) : null;
            var when = (d && !isNaN(d.getTime()))
              ? (pad(d.getDate()) + '.' + pad(d.getMonth() + 1) + '.' + d.getFullYear() + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()))
              : '';
            var ck = 'npStatus.' + e.code, lbl = t(ck);
            if (!lbl || lbl === ck) lbl = e.code_name || ('#' + e.code);
            var place = e.settlement ? (' · ' + e.settlement) : '';
            html += '<li style="position:relative;padding:5px 0 5px 16px">'
              + '<span style="position:absolute;left:-5px;top:10px;width:8px;height:8px;border-radius:50%;background:#e30613"></span>'
              + '<span style="color:#64748b;font-size:11px">' + escapeText(when) + '</span> '
              + '<span style="font-weight:600">' + escapeText(lbl) + '</span>'
              + '<span style="color:#64748b;font-size:11px">' + escapeText(place) + '</span>'
              + '</li>';
          }
          html += '</ul>';
          box.innerHTML = html;
        }

        var tlLoaded = false;
        tlBtn.addEventListener('click', async function () {
          if (tlLoaded) { // toggle: collapse
            tlBox.innerHTML = '';
            tlLoaded = false;
            tlBtn.textContent = '📜 ' + t('orderDetail.timeline.load');
            return;
          }
          tlBtn.disabled = true;
          tlBtn.textContent = t('orderDetail.timeline.loading');
          try {
            var r = await (window.__np_bridge || {}).refreshShipment(existingShipment.id);
            if (r && r.error) throw new Error(r.error);
            var hist = (r && r.tracking && r.tracking.history_tracking) || [];
            renderTimeline(tlBox, hist);
            tlLoaded = true;
            tlBtn.textContent = '📜 ' + t('orderDetail.timeline.hide');
          } catch (err) {
            tlBox.innerHTML = '<div style="color:#991b1b;font-size:12px">'
              + escapeText(t('orderDetail.timeline.error')) + ' ' + escapeText(err && err.message ? err.message : err) + '</div>';
            tlBtn.textContent = '📜 ' + t('orderDetail.timeline.load');
          }
          tlBtn.disabled = false;
        });
      }
    } else {
      var empty = document.createElement('div');
      empty.style.cssText = 'display:flex;flex-wrap:wrap;align-items:center;gap:14px';
      var emptyMsg = document.createElement('span');
      emptyMsg.style.color = '#64748b';
      emptyMsg.textContent = t('orderDetail.section.empty');
      empty.appendChild(emptyMsg);
      empty.appendChild(secCreateBtn);
      secBody.appendChild(empty);
      // Delegate to the canonical create flow wired on the toolbar button
      // (same routing: EU/domestic direct-create, non-EU → bulk wizard).
      secCreateBtn.addEventListener('click', function () { createBtn.click(); });
    }

    // Resolve the native "Zásilky" (Shipments) tab panel. Shoptet order detail
    // uses jQuery UI tabs: each tab <a> carries the panel id in its href hash
    // and the parent <li role="tab"> in aria-controls (e.g. #t7 → <div id="t7">).
    // Match the tab by its localized label so we don't hard-code the id (jQuery
    // UI numbers panels per page, so t7 isn't stable across shops).
    function findShipmentsPanel() {
      var anchors = document.querySelectorAll('a.ui-tabs-anchor, [role="tablist"] a, .ui-tabs-nav a');
      // Localised "Shipments" tab labels (Shoptet gives the tabs no stable id /
      // data-testid — only the translated text differs, so we match on it):
      // CZ/SK zásilky/zásielky, EN shipments, DE sendungen, PL przesyłki,
      // UK відправлення, HU küldemények. For an admin language NOT in this list
      // the tab isn't found and mountShipmentsInfo() falls back to a standalone
      // block under the order header (see below), so tracking always shows.
      var LABELS = ['zásilky', 'zasilky', 'zásielky', 'zasielky', 'shipments', 'sendungen', 'przesyłki', 'przesylki', 'відправлення', 'küldemények'];
      for (var i = 0; i < anchors.length; i++) {
        var txt = (anchors[i].textContent || '').replace(/\s+/g, ' ').trim().toLowerCase();
        if (LABELS.indexOf(txt) === -1) continue;
        var li = anchors[i].closest ? anchors[i].closest('[role="tab"]') : null;
        var pid = (li && li.getAttribute('aria-controls'))
          || ((anchors[i].getAttribute('href') || '').split('#')[1] || '');
        if (pid) { var el = document.getElementById(pid); if (el) return el; }
      }
      return null;
    }
    function mountShipmentsInfo() {
      if (document.getElementById('np-section') && section.parentNode) return true;
      var panel = findShipmentsPanel();
      if (!panel) return false;
      // Hide Shoptet's native "Žádné položky" placeholder — it refers to
      // Shoptet's own shipments feature (which we don't populate) and would
      // otherwise sit under our NP info and contradict it.
      var emptyP = panel.querySelector('.table__empty');
      if (emptyP) emptyP.style.display = 'none';
      panel.insertBefore(section, panel.firstChild);
      return true;
    }
    // Language-agnostic fallback: when the Shipments tab can't be matched by label
    // (an admin language we don't map — Shoptet gives the tabs no stable id), drop
    // the NP section as a standalone card right under the order header instead, so
    // the tracking/status is ALWAYS visible regardless of admin language (Samuel
    // 2026-07-07). #order-header is a stable, language-independent id.
    function mountShipmentsInfoFallback() {
      if (document.getElementById('np-section') && section.parentNode) return true;
      var anchor = document.getElementById('order-header')
        || document.querySelector('#document-update > fieldset')
        || document.querySelector('#document-update');
      if (!anchor) return false;
      // Standalone → give it a light card so it doesn't look unmoored (in the tab
      // it inherits the panel's padding, so no border is set there).
      section.style.border = '1px solid #e2e8f0';
      section.style.borderRadius = '8px';
      section.style.padding = '12px 16px';
      section.style.margin = '14px 0';
      section.style.background = '#fff';
      anchor.insertAdjacentElement('afterend', section);
      return true;
    }
    // jQuery UI tabs are initialized on page load, so the panel usually exists by
    // the time we inject; retry briefly in case our inject wins the race. If the
    // tabs are present but our label list didn't match (unknown admin language),
    // stop early and use the under-header fallback rather than waiting the full
    // timeout.
    if (!mountShipmentsInfo()) {
      var mountTries = 0;
      var mountIv = setInterval(function () {
        mountTries++;
        if (mountShipmentsInfo()) { clearInterval(mountIv); return; }
        var tabsReady = !!document.querySelector('.ui-tabs-nav a, [role="tablist"] a, a.ui-tabs-anchor');
        if ((tabsReady && mountTries >= 3) || mountTries >= 20) {
          clearInterval(mountIv);
          mountShipmentsInfoFallback();
        }
      }, 150);
    }

    // Allowed delivery countries (admin-configured, from /admin/countries/,
    // captured at onboarding). [{iso,id}] — iso gates which countries the picker
    // offers; id is Shoptet's #deliveryCountryId value for the write-back. Empty
    // (older onboarding) → fall back to the default country list, no write-back.
    var deliveryCountries = (opts.deliveryCountries || []).filter(function (c) { return c && c.iso; });
    var ccIdByIso = {};
    deliveryCountries.forEach(function (c) { ccIdByIso[String(c.iso).toUpperCase()] = c.id; });
    var CC_LABELS = {
      CZ: 'Czechia (CZ)', SK: 'Slovakia (SK)', UA: 'Ukraine (UA)', PL: 'Poland (PL)',
      HU: 'Hungary (HU)', DE: 'Germany (DE)', AT: 'Austria (AT)', RO: 'Romania (RO)'
    };
    // Label an ISO2 delivery country as "Name (CODE)". The curated CC_LABELS win;
    // for any other country the e-shop enables (Tic-0051: Italy showed as a bare
    // "IT"), derive the name from Intl.DisplayNames so it reads "Italy (IT)" —
    // no hand-maintained per-country list. Falls back to the bare code if Intl
    // is unavailable or the code is unknown.
    var __ccRegionNames = (function () {
      try { return (typeof Intl !== 'undefined' && Intl.DisplayNames) ? new Intl.DisplayNames(['en'], { type: 'region' }) : null; }
      catch (e) { return null; }
    })();
    function ccLabel(code) {
      if (CC_LABELS[code]) return CC_LABELS[code];
      if (__ccRegionNames) {
        try { var nm = __ccRegionNames.of(code); if (nm && nm !== code) return nm + ' (' + code + ')'; }
        catch (e) { /* unknown code → bare */ }
      }
      return code;
    }
    // Tic-0048: the country selector is a CUSTOM div-based dropdown, NOT a native
    // <select>. On Linux/Windows a native <select> inside the Shoptet
    // WebContentsView opens a native OS popup whose teardown breaks mouse-input
    // routing for the whole window — the BC chrome (left menu, breadcrumbs) goes
    // dead to clicks until relaunch (the picker-freeze the close-path nudges never
    // fixed, because the trigger is the <select>, not closing the modal). A DOM
    // dropdown invokes no native popup, so the freeze cannot occur. macOS was
    // unaffected by the native popup but uses the same code path.
    function pickerCountryOptionsHtml() {
      var list = deliveryCountries.length
        ? deliveryCountries.map(function (c) { return String(c.iso).toUpperCase(); })
        : ['CZ', 'SK', 'UA', 'PL', 'HU', 'DE', 'AT'];
      var html = '<div class="np-cc-opt" data-value="" style="padding:8px 10px;cursor:pointer">Auto (sender country)</div>';
      for (var i = 0; i < list.length; i++) {
        html += '<div class="np-cc-opt" data-value="' + escapeText(list[i]) + '" style="padding:8px 10px;cursor:pointer">'
          + escapeText(ccLabel(list[i])) + '</div>';
      }
      return html;
    }

    // Req-F-026 branch picker: lazily-created modal, search by city / name,
    // PostBranch only (matches widget v0.11 + NP /shipments validation).
    var pickerModal = null;
    var pickerDebounce = null;
    function openBranchPicker(onSelect) {
      if (!pickerModal) {
        pickerModal = document.createElement('div');
        pickerModal.id = 'np-picker-modal';
        pickerModal.style.cssText = [
          'position:fixed','inset:0','background:rgba(0,0,0,.45)','z-index:2147483647',
          'display:flex','align-items:center','justify-content:center','padding:24px',
          'font:13px -apple-system,BlinkMacSystemFont,sans-serif'
        ].join(';');
        pickerModal.innerHTML =
          '<div style="background:#fff;border-radius:10px;width:100%;max-width:780px;max-height:90vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 12px 30px rgba(0,0,0,.2)">'
          + '<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid #e2e8f0">'
          + '<strong style="color:#e30613">Select Nova Post branch</strong>'
          + '<button id="np-picker-close" type="button" style="background:none;border:none;font-size:22px;line-height:1;color:#64748b;cursor:pointer">&times;</button>'
          + '</div>'
          + '<div style="display:flex;gap:8px;padding:12px 18px">'
          + '<input id="np-picker-search" type="text" placeholder="City / branch name (Bratislava, Praha, ...)" style="flex:1;border:1px solid #cbd5e1;border-radius:6px;padding:8px 10px;font:inherit">'
          // Custom (non-native) country dropdown — see pickerCountryOptionsHtml (Tic-0048).
          // A hidden input holds the ISO value so all existing `.value` reads/writes
          // keep working; the button shows the label, the menu is a plain div list.
          + '<div id="np-picker-country-wrap" style="position:relative;min-width:170px">'
          + '<input id="np-picker-country" type="hidden" value="">'
          + '<button id="np-picker-country-btn" type="button" style="width:100%;text-align:left;border:1px solid #cbd5e1;border-radius:6px;padding:8px 28px 8px 10px;font:inherit;background:#fff;cursor:pointer">'
          + '<span id="np-picker-country-label">Auto (sender country)</span>'
          + '<span style="position:absolute;right:10px;top:50%;transform:translateY(-50%);color:#64748b;pointer-events:none">&#9662;</span>'
          + '</button>'
          + '<div id="np-picker-country-menu" style="display:none;position:absolute;top:calc(100% + 2px);left:0;right:0;max-height:240px;overflow-y:auto;background:#fff;border:1px solid #cbd5e1;border-radius:6px;box-shadow:0 6px 18px rgba(0,0,0,.15);z-index:10">'
          + pickerCountryOptionsHtml()
          + '</div>'
          + '</div>'
          + '</div>'
          + '<div id="np-picker-status" style="padding:0 18px 8px;font-size:11px;color:#64748b">&nbsp;</div>'
          + '<div style="flex:1;overflow-y:auto;border-top:1px solid #e2e8f0;min-height:300px">'
          + '<table style="width:100%;border-collapse:collapse;font-size:12px">'
          + '<thead style="background:#f8fafc;position:sticky;top:0">'
          + '<tr><th style="text-align:left;padding:8px 12px;font-weight:600">ID</th>'
          + '<th style="text-align:left;padding:8px 12px;font-weight:600">Name</th>'
          + '<th style="text-align:left;padding:8px 12px;font-weight:600">City</th>'
          + '<th style="text-align:left;padding:8px 12px;font-weight:600">Address</th>'
          + '<th></th></tr></thead>'
          + '<tbody id="np-picker-rows"></tbody>'
          + '</table>'
          + '</div>'
          + '</div>';
        document.body.appendChild(pickerModal);
        // A click on the dim backdrop must NOT close the picker: the operator
        // types a city/branch search here and a stray click just outside the
        // card used to wipe it (reported on the order-detail "Change NP branch").
        // Close only via the explicit × button or Esc.
        pickerModal.addEventListener('keydown', function (ev) {
          if (ev.key === 'Escape') closePicker();
        });
        pickerModal.querySelector('#np-picker-close').addEventListener('click', closePicker);
        // Each keystroke (debounced) re-queries NP server-side via textSearch —
        // the real free-text filter over branch name/number + city.
        pickerModal.querySelector('#np-picker-search').addEventListener('input', runPickerSearchDebounced);
        // Custom country dropdown: toggle the div menu, pick an option → set the
        // hidden value + label and re-run the search (keeping the search text).
        // No native <select>, so no native OS popup → no Linux/Win input freeze.
        var ccBtn = pickerModal.querySelector('#np-picker-country-btn');
        var ccMenu = pickerModal.querySelector('#np-picker-country-menu');
        ccBtn.addEventListener('click', function (ev) {
          ev.stopPropagation();
          ccMenu.style.display = ccMenu.style.display === 'none' ? 'block' : 'none';
        });
        ccMenu.addEventListener('click', function (ev) {
          var opt = ev.target && ev.target.closest && ev.target.closest('.np-cc-opt');
          if (!opt) return;
          ccMenu.style.display = 'none';
          setPickerCountry(opt.getAttribute('data-value') || '');
          runPickerSearch();
        });
        ccMenu.addEventListener('mouseover', function (ev) {
          var opt = ev.target && ev.target.closest && ev.target.closest('.np-cc-opt');
          if (opt) opt.style.background = '#f8fafc';
        });
        ccMenu.addEventListener('mouseout', function (ev) {
          var opt = ev.target && ev.target.closest && ev.target.closest('.np-cc-opt');
          if (opt) opt.style.background = '';
        });
        // Click anywhere else in the modal closes an open menu.
        pickerModal.addEventListener('click', function (ev) {
          if (!ev.target.closest || !ev.target.closest('#np-picker-country-wrap')) ccMenu.style.display = 'none';
        });
      }
      pickerModal.style.display = 'flex';
      pickerModal._onSelect = onSelect;
      pickerModal.querySelector('#np-picker-search').value = '';
      // Keep prior rows during initial load — replaced wholesale on response.
      pickerModal.querySelector('#np-picker-status').textContent = '';
      // Auto-detect destination country from the Shoptet order page so the
      // picker defaults to the recipient's country (operator changing a
      // recipient branch almost always wants the SAME country, not sender's).
      var detected = (function () {
        var t = (document.body && document.body.textContent || '').toLowerCase();
        if (/\bslovensk[oáeu]\b|\bslovak/.test(t)) return 'SK';
        if (/\bukrajin[aey]\b|\bukrain/.test(t)) return 'UA';
        if (/\bpolsk[oáuy]\b|\bpoland/.test(t)) return 'PL';
        if (/\bmaďarsk[oáu]\b|\bhungary|\bmagyar/.test(t)) return 'HU';
        if (/\bnemeck[oáu]\b|\bněmeck[oáu]\b|\bgerman|\bdeutsch/.test(t)) return 'DE';
        if (/\brakousk[oáu]\b|\brakúsk[oáu]\b|\bösterr/.test(t)) return 'AT';
        return '';
      })();
      // Tic-0051: when the recipient country can't be detected, the results are
      // still filtered by the SENDER country (the bridge default), so pre-select
      // that concrete country in the dropdown instead of the abstract "Auto
      // (sender country)" — it should visibly match the filter that's applied.
      var senderCC = String(opts.senderCountryCode || '').toUpperCase();
      setPickerCountry(detected || senderCC || '');
      setTimeout(function () { pickerModal.querySelector('#np-picker-search').focus(); }, 50);
      runPickerSearch();
    }
    // Set the custom country dropdown's value: writes the hidden input (all
    // `.value` readers keep working) and syncs the visible button label.
    function setPickerCountry(val) {
      if (!pickerModal) return;
      var v = val || '';
      pickerModal.querySelector('#np-picker-country').value = v;
      var opt = pickerModal.querySelector('.np-cc-opt[data-value="' + v + '"]');
      pickerModal.querySelector('#np-picker-country-label').textContent =
        opt ? opt.textContent : (v ? ccLabel(v) : 'Auto (sender country)');
    }
    function closePicker() {
      if (!pickerModal) return;
      // Blur whatever inside the modal still holds focus (the search input grabs
      // it on open) BEFORE hiding. Otherwise focus is left on a display:none
      // element and the WebContentsView swallows the next click while it
      // re-homes focus — the intermittent "eshop stops reacting to clicks after
      // closing with ×" report. Hand focus back to the page explicitly.
      try {
        var act = document.activeElement;
        if (act && pickerModal.contains(act) && act.blur) act.blur();
      } catch (_) { /* noop */ }
      pickerModal.style.display = 'none';
      try { if (document.body && document.body.focus) document.body.focus(); } catch (_) { /* noop */ }
      // Tic-0048: the blur/refocus above (v0.4.62) was not enough on
      // Linux/Windows — the Shoptet WebContentsView keeps painting but stops
      // routing clicks after this in-view overlay closes (the left menu /
      // breadcrumbs go dead until the app is relaunched, most reliably after
      // switching the picker country to a foreign one and searching). Ask main
      // to nudge the view's bounds, which re-arms input routing (same proven
      // fix the sender-branch takeover gets on close). Best-effort.
      try {
        if (window.__np_bridge && window.__np_bridge.nudgeView) window.__np_bridge.nudgeView();
      } catch (_) { /* noop */ }
    }
    var pickerRequestSeq = 0;
    var pickerSearchTimer = null;

    // Debounced keystroke handler — coalesce fast typing into ONE server query.
    function runPickerSearchDebounced() {
      if (pickerSearchTimer) clearTimeout(pickerSearchTimer);
      pickerSearchTimer = setTimeout(runPickerSearch, 250);
    }

    // City of a division. NP /divisions sends NO cityName — the city lives in
    // settlement.name (Latin, "city Kyiv"), fullAddress.settlement (Cyrillic,
    // "місто Київ") and displayAddress. Return the Cyrillic form for display,
    // falling back to the Latin transliteration.
    function pickerCityOf(it) {
      return (it.fullAddress && it.fullAddress.settlement)
        || (it.settlement && it.settlement.name)
        || it.cityName
        || '';
    }
    // Formatted street address for the address column.
    function pickerAddrOf(it) {
      if (it.fullAddress) {
        return [it.fullAddress.street, it.fullAddress.building, it.fullAddress.zipcode, it.fullAddress.settlement]
          .filter(Boolean).join(', ');
      }
      return it.displayAddress || it.address || it.addressDescription || '';
    }

    // Paint a server-filtered list of divisions into the table. NP already did
    // the filtering (textSearch), so this is a plain render — no client filter.
    function renderPickerRows(items) {
      if (!pickerModal) return;
      var rowsEl = pickerModal.querySelector('#np-picker-rows');
      var html = '';
      for (var i = 0; i < items.length; i++) {
        var it = items[i];
        var addr = pickerAddrOf(it);
        html += '<tr class="np-picker-row" data-id="' + escapeText(it.id) + '"'
          + ' data-name="' + escapeText(it.name || '') + '"'
          + ' data-addr="' + escapeText(addr) + '"'
          + ' style="border-top:1px solid #e2e8f0;cursor:pointer">'
          + '<td style="padding:6px 12px;font-family:monospace;color:#64748b">' + escapeText(it.id) + '</td>'
          + '<td style="padding:6px 12px">' + escapeText(it.name || '') + '</td>'
          + '<td style="padding:6px 12px">' + escapeText(pickerCityOf(it)) + '</td>'
          + '<td style="padding:6px 12px;color:#64748b">' + escapeText(addr) + '</td>'
          + '<td style="padding:6px 12px;text-align:right;color:#e30613">Use →</td>'
          + '</tr>';
      }
      rowsEl.innerHTML = html;
      // Delegate row events to the tbody ONCE (survives innerHTML replacement).
      if (!rowsEl._npDelegated) {
        rowsEl._npDelegated = true;
        rowsEl.addEventListener('mouseover', function (e) {
          var tr = e.target && e.target.closest && e.target.closest('.np-picker-row');
          if (tr) tr.style.background = '#f8fafc';
        });
        rowsEl.addEventListener('mouseout', function (e) {
          var tr = e.target && e.target.closest && e.target.closest('.np-picker-row');
          if (tr) tr.style.background = '';
        });
        rowsEl.addEventListener('click', function (e) {
          var tr = e.target && e.target.closest && e.target.closest('.np-picker-row');
          if (!tr) return;
          var pick = {
            id: tr.getAttribute('data-id'),
            name: tr.getAttribute('data-name'),
            address: tr.getAttribute('data-addr'),
            // The country the operator filtered by IS the branch's country
            // (empty = Auto/sender → domestic, no country change needed).
            country: (pickerModal.querySelector('#np-picker-country').value || '').toUpperCase()
          };
          var cb = pickerModal._onSelect;
          closePicker();
          if (cb) cb(pick);
        });
      }
    }

    // Ask NP for branches matching the current search box + country, filtered
    // SERVER-SIDE via `textSearch` — NP's real free-text filter that matches the
    // branch name/number AND the city, in Cyrillic and Latin, honouring multiple
    // words ("Полтава 12" → Відділення №12 in Poltava). This replaces the old
    // fetch-all + client-side filtering: NP returns just the (small) matching
    // page, so there is no 2175-branch download, no day-cache and no lag
    // (Tic-0026 / UAT-024 + Tic-0050). Empty box → first page of the country.
    function runPickerSearch() {
      if (!pickerModal) return;
      var bridge = window.__np_bridge;
      if (!bridge || !bridge.listDivisions) {
        pickerModal.querySelector('#np-picker-status').textContent = 'Branch picker requires the desktop bridge (BC).';
        return;
      }
      var statusEl = pickerModal.querySelector('#np-picker-status');
      var country = pickerModal.querySelector('#np-picker-country').value;
      var q = (pickerModal.querySelector('#np-picker-search').value || '').trim();
      statusEl.textContent = q ? 'Searching…' : ('Loading branches' + (country ? ' (' + country + ')' : '') + '…');
      var seq = ++pickerRequestSeq;
      var opts = { limit: 50 };
      if (country) opts.countryCodes = [country];
      if (q) opts.textSearch = q;
      bridge.listDivisions(opts)
        .then(function (data) {
          if (seq !== pickerRequestSeq) return; // stale response
          if (!data || data.error) {
            statusEl.textContent = 'Error: ' + ((data && data.error) || 'request failed');
            return;
          }
          var items = data.items || [];
          var total = (data.total != null) ? data.total : items.length;
          renderPickerRows(items);
          if (!items.length) {
            statusEl.textContent = q ? 'No branches match "' + escapeText(q) + '"' : 'No branches';
          } else if (q) {
            statusEl.textContent = total > items.length
              ? 'showing ' + items.length + ' of ' + total + ' — refine your search'
              : items.length + (items.length === 1 ? ' result' : ' results');
          } else {
            statusEl.textContent = total > items.length
              ? 'showing first ' + items.length + ' of ' + total + ' — type a city or branch name'
              : items.length + ' branches';
          }
        })
        .catch(function (e) {
          if (seq !== pickerRequestSeq) return;
          statusEl.textContent = 'Error: ' + (e.message || 'connection failed');
        });
    }

    // Apply selected branch into Shoptet's customer-contact "Poznámka"
    // (the canonical NP branch storage location — same field the widget
    // writes to at checkout). Editing requires opening the Úprava kontaktu
    // modal via the Modify button on the customer-contact card, filling
    // the modal's textarea, and saving. We do steps 1 + 2 programmatically
    // and let the operator confirm by clicking ULOŽIT manually so we don't
    // accidentally bypass Shoptet form validation.
    // #1 consistency: write the destination country into the order's shipping
    // address so Shoptet matches the (foreign) branch. Branch delivery only +
    // country only — per Samuel: for "NP na pobočku" just set the state; for
    // delivery-to-address the operator types the full address. Opens the "Edit
    // Shipping Address" modal and sets #deliveryCountryId to the Shoptet country
    // id (native value setter so Shoptet's framework sees it), then leaves the
    // SAVE to the operator (we never auto-submit Shoptet forms).
    function clearShoptetDeliveryAddressForBranch(countryId, iso) {
      var modifyBtn = document.querySelector('[data-testid="buttonModifyShippingAddress"]');
      if (!modifyBtn) {
        result.insertAdjacentHTML('beforeend',
          '<div style="margin-top:6px;color:#991b1b;font-size:11px">Modify shipping-address button not found — clear the delivery address manually.</div>');
        return;
      }
      modifyBtn.click();
      var attempts = 0, maxAttempts = 40;
      (function trySet() {
        attempts++;
        var streetEl = document.querySelector('#deliveryStreet, [name="deliveryStreet"]');
        var sel = document.querySelector('#deliveryCountryId')
          || document.querySelector('select[name="deliveryCountryId"]');
        if (!streetEl && !sel) {
          if (attempts < maxAttempts) { setTimeout(trySet, 100); return; }
          result.insertAdjacentHTML('beforeend',
            '<div style="margin-top:6px;color:#991b1b;font-size:11px">Shipping-address modal did not open — clear the delivery address manually.</div>');
          return;
        }
        // Rule 2: a saved NP branch makes the order's street/zip obsolete, so
        // wipe the postal address fields (keep the recipient name/company — NP
        // still needs who picks the parcel up). Use the native value setter so
        // Shoptet's framework registers the change.
        function clearField(f) {
          if (!f) return;
          try {
            var proto = f.tagName === 'SELECT' ? window.HTMLSelectElement.prototype
              : (f.tagName === 'TEXTAREA' ? window.HTMLTextAreaElement.prototype : window.HTMLInputElement.prototype);
            Object.getOwnPropertyDescriptor(proto, 'value').set.call(f, '');
          } catch (_) { try { f.value = ''; } catch (__) { /* noop */ } }
          f.dispatchEvent(new Event('input', { bubbles: true }));
          f.dispatchEvent(new Event('change', { bubbles: true }));
        }
        var ids = ['deliveryStreet', 'deliveryHouseNumber', 'deliveryCity', 'deliveryZip',
          'deliveryDistrict', 'deliveryAdditional', 'deliveryRegionName'];
        for (var k = 0; k < ids.length; k++) {
          clearField(document.querySelector('#' + ids[k] + ', [name="' + ids[k] + '"]'));
        }
        // Keep only the country, matching the chosen branch. countryId may be
        // null (domestic / Auto) — then leave the country as-is, just cleared.
        var countrySet = false;
        if (countryId != null && sel && sel.tagName === 'SELECT') {
          var hasOpt = false;
          for (var i = 0; i < sel.options.length; i++) {
            if (String(sel.options[i].value) === String(countryId)) { hasOpt = true; break; }
          }
          if (hasOpt) {
            Object.getOwnPropertyDescriptor(window.HTMLSelectElement.prototype, 'value').set.call(sel, String(countryId));
            sel.dispatchEvent(new Event('input', { bubbles: true }));
            sel.dispatchEvent(new Event('change', { bubbles: true }));
            countrySet = true;
          } else {
            result.insertAdjacentHTML('beforeend',
              '<div style="margin-top:6px;color:#92400e;font-size:11px">' + escapeText(iso)
              + ' is not selectable in this order\'s country list — set the country manually.</div>');
          }
        }
        try {
          var hl = streetEl || sel;
          if (hl) {
            hl.style.outline = '2px solid #16a34a';
            hl.scrollIntoView({ block: 'center', behavior: 'smooth' });
            setTimeout(function () { try { hl.style.outline = ''; } catch (_) {} }, 3500);
          }
        } catch (_) {}
        result.insertAdjacentHTML('beforeend',
          '<div style="margin-top:6px;color:#166534;font-size:11px">Delivery address cleared'
          + (countrySet ? ' and country set to <strong>' + escapeText(iso) + '</strong>' : '')
          + ' in the modal — review and click <strong>SAVE</strong>.</div>');
      })();
    }

    // Rule 2 (last-set-wins): after the branch note is SAVED, the order's
    // shipping street/zip is obsolete — open the Shipping-address modal, clear
    // the postal address and keep only the country matching the branch. Runs
    // AFTER the contact-note save (see watchContactModalClose), so it never races
    // it. The operator confirms with SAVE (we never auto-submit Shoptet forms).
    function applyBranchAddressConsistency(pick) {
      if (!pick) return;
      var iso = (pick.country || '').toUpperCase();
      // Empty country = Auto/domestic: clear the address but leave the country.
      var cid = iso ? ccIdByIso[iso] : null;
      clearShoptetDeliveryAddressForBranch(cid, iso);
    }

    function applyBranchToShoptetRemark(pick) {
      var modifyBtn = document.querySelector('[data-testid="buttonModifyCustomerContact"]');
      if (!modifyBtn) {
        // Older Shoptet templates / non-detail pages: fall back to shopRemark
        // (visible inline, no modal needed) and let the operator save manually.
        var shopRemarkEl = document.querySelector('textarea[name="shopRemark"]');
        if (!shopRemarkEl) {
          result.innerHTML = '<span style="color:#991b1b">Modify contact button not found and shopRemark missing — cannot change branch automatically.</span>';
          return;
        }
        var tagFb = 'Nova Post: ' + (pick.name || '') + (pick.address ? ', ' + pick.address : '') + ' [NP:' + pick.id + ']';
        var cleanedFb = stripNpRemark(shopRemarkEl.value);
        shopRemarkEl.value = cleanedFb ? cleanedFb + '\n' + tagFb : tagFb;
        shopRemarkEl.dispatchEvent(new Event('input', { bubbles: true }));
        shopRemarkEl.dispatchEvent(new Event('change', { bubbles: true }));
        result.innerHTML = '<span style="color:#1e293b">Fallback to Shop remark: <strong>' + escapeText(pick.name || pick.id) + '</strong>'
          + ' <code style="background:#f1f5f9;padding:1px 4px;border-radius:3px">[NP:' + escapeText(pick.id) + ']</code>'
          + '<br><span style="color:#92400e;font-size:11px">Save changes in Shoptet, then click Create Nova Post shipment.</span></span>';
        // No save-detection on the inline shopRemark path — apply the address
        // consistency (rule 2) immediately; the operator saves both manually.
        applyBranchAddressConsistency(pick);
        return;
      }
      result.innerHTML = '<span style="color:#1e293b">Opening Úprava kontaktu modal…</span>';
      // Open modal (Shoptet renders it lazily after click).
      modifyBtn.click();
      // Poll for the modal's note textarea. Heuristic: any visible textarea
      // that ISN'T shopRemark (which lives outside the modal). Modal is
      // usually rendered within ~200 ms but slow machines need a few tries.
      var attempts = 0;
      var maxAttempts = 40;
      function tryFillModal() {
        attempts++;
        var noteEl = null;
        var allTas = document.querySelectorAll('textarea');
        for (var i = 0; i < allTas.length; i++) {
          var t = allTas[i];
          if (t.name === 'shopRemark') continue;
          var r = t.getBoundingClientRect();
          if (r.width > 0 && r.height > 0) {
            noteEl = t;
            break;
          }
        }
        if (!noteEl) {
          if (attempts < maxAttempts) {
            setTimeout(tryFillModal, 100);
            return;
          }
          result.innerHTML = '<span style="color:#991b1b">Úprava kontaktu modal did not open (or its Note field was not found).</span>';
          return;
        }
        var tag = 'Nova Post: ' + (pick.name || '') + (pick.address ? ', ' + pick.address : '') + ' [NP:' + pick.id + ']';
        var cleaned = stripNpRemark(noteEl.value);
        noteEl.value = cleaned ? cleaned + '\n' + tag : tag;
        noteEl.dispatchEvent(new Event('input', { bubbles: true }));
        noteEl.dispatchEvent(new Event('change', { bubbles: true }));
        // Highlight the textarea so the operator sees what was filled.
        try {
          noteEl.style.outline = '2px solid #16a34a';
          noteEl.style.outlineOffset = '2px';
          setTimeout(function () { try { noteEl.style.outline = ''; } catch (_) {} }, 3500);
          noteEl.scrollIntoView({ block: 'center', behavior: 'smooth' });
        } catch (_) {}
        result.innerHTML = '<span style="color:#166534">Branch <strong>' + escapeText(pick.name || pick.id) + '</strong>'
          + ' <code style="background:#f1f5f9;padding:1px 4px;border-radius:3px">[NP:' + escapeText(pick.id) + ']</code>'
          + ' written into the modal Note field.</span>'
          + '<br><span style="color:#92400e;font-size:11px">Verify the value in the modal and click <strong>ULOŽIT</strong>. Then Create Nova Post shipment.</span>';
        // Rule 2 runs AFTER the contact-note save (watchContactModalClose), not
        // here — clearing the shipping address now would race the still-open
        // contact modal (only one Shoptet modal at a time).
        // The [NP:...] tag above lives only in the modal's Note field until the
        // operator clicks ULOŽIT/SAVE. If they instead close the modal with ×,
        // Esc or Cancel, Shoptet discards it — but the red "click ULOŽIT" banner
        // stayed up and looked like the branch was selected (Yurii, 2026-06-27).
        // Watch the modal: confirm the banner on save, clear it on cancel.
        watchContactModalClose(noteEl, pick);
      }
      setTimeout(tryFillModal, 200);
    }

    // Track the Shoptet "Úprava kontaktu" modal after we filled its Note field.
    // We can't ask Shoptet whether it saved, so we infer: a click on a Save/Uložiť
    // control before the modal goes away counts as saved; any other close (×, Esc,
    // Cancel) counts as discarded. On close we rewrite the banner accordingly so
    // it never claims a branch is pending when nothing was saved.
    function watchContactModalClose(noteEl, pick) {
      var scope = null;
      try { scope = noteEl.closest('[role="dialog"], .modal, .modal-content, .ui-dialog'); } catch (_) { /* noop */ }
      scope = scope || document;
      var saved = false;
      function onClick(ev) {
        var hit = ev.target && ev.target.closest
          ? ev.target.closest('button, a, input[type="submit"], [role="button"]') : null;
        var label = ((hit && (hit.textContent || hit.getAttribute('aria-label') || hit.value)) || '')
          .trim().toLowerCase();
        if (/ulo[žźżz]i[tť]|\bsave\b/.test(label)) saved = true;
        else if (/zru[šs]i[tť]|cancel|close|^×$/.test(label)) saved = false;
      }
      try { scope.addEventListener('click', onClick, true); } catch (_) { /* noop */ }
      var ticks = 0;
      (function poll() {
        ticks++;
        var present = document.body.contains(noteEl);
        var visible = present && noteEl.getBoundingClientRect().width > 0;
        if (visible) {
          if (ticks < 3000) setTimeout(poll, 200);          // ~10 min safety cap
          else { try { scope.removeEventListener('click', onClick, true); } catch (_) {} }
          return;
        }
        try { scope.removeEventListener('click', onClick, true); } catch (_) { /* noop */ }
        if (saved) {
          result.innerHTML = '<span style="color:#166534">Branch <strong>' + escapeText(pick.name || pick.id) + '</strong>'
            + ' <code style="background:#f1f5f9;padding:1px 4px;border-radius:3px">[NP:' + escapeText(pick.id) + ']</code>'
            + ' saved.</span>'
            + '<br><span style="color:#92400e;font-size:11px">Now click <strong>Create Nova Post shipment</strong>.</span>';
          // Rule 2: the branch note is committed — now clear the obsolete
          // shipping address and keep only the branch country. Separate Shoptet
          // modal, so safe to open now that the contact modal has closed.
          applyBranchAddressConsistency(pick);
        } else {
          result.innerHTML = '<span style="color:#64748b">Branch change cancelled — the contact dialog was closed without saving, nothing was changed. Click <strong>Change NP branch</strong> to try again.</span>';
        }
      })();
    }

    if (changeBranchBtn) {
      changeBranchBtn.addEventListener('click', function () {
        openBranchPicker(applyBranchToShoptetRemark);
      });
    }

    // Rule 3 (last-set-wins): no button, no BC dialog. We watch Shoptet's NATIVE
    // shipping-address modal; when the operator SAVES a REAL delivery address
    // (street+city+zip filled), it means "ship to this address, not an NP branch"
    // — so we silently strip the [NP:id] marker from the contact note and save
    // it. An EMPTY saved address is rule 2 clearing the address for a branch
    // (clearShoptetDeliveryAddressForBranch), so we MUST skip it there.
    //
    // Silently strip the NP marker from the contact note and save the modal
    // without operator interaction (the operator already committed intent by
    // saving the address). shopRemark fallback when there's no contact modal.
    function removeNpBranchNoteAndSave() {
      var modifyBtn = document.querySelector('[data-testid="buttonModifyCustomerContact"]');
      if (!modifyBtn) {
        var shopRemarkEl = document.querySelector('textarea[name="shopRemark"]');
        if (!shopRemarkEl) return;
        var beforeFb = shopRemarkEl.value || '';
        var afterFb = stripNpRemark(beforeFb);
        if (afterFb === beforeFb.trim()) return; // no marker
        shopRemarkEl.value = afterFb;
        shopRemarkEl.dispatchEvent(new Event('input', { bubbles: true }));
        shopRemarkEl.dispatchEvent(new Event('change', { bubbles: true }));
        result.innerHTML = '<span style="color:#166534">NP branch note removed — this order now ships to the delivery address.</span>'
          + '<br><span style="color:#92400e;font-size:11px">Save the order in Shoptet to persist.</span>';
        return;
      }
      modifyBtn.click();
      var attempts = 0, maxAttempts = 40;
      (function tryStripSave() {
        attempts++;
        var noteEl = null;
        var allTas = document.querySelectorAll('textarea');
        for (var i = 0; i < allTas.length; i++) {
          var t = allTas[i];
          if (t.name === 'shopRemark') continue;
          var r = t.getBoundingClientRect();
          if (r.width > 0 && r.height > 0) { noteEl = t; break; }
        }
        if (!noteEl) {
          if (attempts < maxAttempts) { setTimeout(tryStripSave, 100); return; }
          return; // modal didn't open — give up silently
        }
        var before = noteEl.value || '';
        var after = stripNpRemark(before);
        if (after === before.trim()) return; // no marker in the contact note
        noteEl.value = after;
        noteEl.dispatchEvent(new Event('input', { bubbles: true }));
        noteEl.dispatchEvent(new Event('change', { bubbles: true }));
        // Auto-save: find this modal's Save/ULOŽIT control and click it.
        var scope = null;
        try { scope = noteEl.closest('[role="dialog"], .modal, .modal-content, .ui-dialog'); } catch (_) { /* noop */ }
        scope = scope || document;
        var saveBtn = null;
        var btns = scope.querySelectorAll('button, a, input[type="submit"], [role="button"]');
        for (var j = 0; j < btns.length; j++) {
          var label = ((btns[j].textContent || btns[j].getAttribute('aria-label') || btns[j].value) || '')
            .trim().toLowerCase();
          if (/ulo[žźżz]i[tť]|\bsave\b/.test(label)) { saveBtn = btns[j]; break; }
        }
        if (saveBtn) {
          saveBtn.click();
          result.innerHTML = '<span style="color:#166534">NP branch note removed — this order now ships to the delivery address.</span>';
        } else {
          // Couldn't locate the save control — leave the modal for a manual save.
          result.innerHTML = '<span style="color:#92400e">NP branch note cleared in the contact modal — click <strong>ULOŽIT</strong> to save.</span>';
        }
      })();
    }

    // Watch the native shipping-address modal after it opens. On a Save click we
    // record whether a real address was present; once the modal closes, a saved
    // NON-EMPTY address triggers the silent marker removal above. Empty = rule-2
    // branch clear → skip. Guarded so we don't stack watchers.
    var shippingWatcherActive = false;
    function watchShippingAddressModalClose() {
      if (shippingWatcherActive) return;
      shippingWatcherActive = true;
      var attempts = 0, maxAttempts = 40;
      (function waitForModal() {
        attempts++;
        var streetEl = document.querySelector('#deliveryStreet, [name="deliveryStreet"]');
        if (!streetEl || streetEl.getBoundingClientRect().width === 0) {
          if (attempts < maxAttempts) { setTimeout(waitForModal, 100); return; }
          shippingWatcherActive = false;
          return;
        }
        var scope = null;
        try { scope = streetEl.closest('[role="dialog"], .modal, .modal-content, .ui-dialog'); } catch (_) { /* noop */ }
        scope = scope || document;
        var saved = false, savedHasAddress = false;
        function gv(sel) { var e = document.querySelector(sel); return e ? String(e.value || '').trim() : ''; }
        function readHasAddress() {
          var street = gv('#deliveryStreet') || gv('[name="deliveryStreet"]');
          var city = gv('#deliveryCity') || gv('[name="deliveryCity"]');
          var zip = gv('#deliveryZip') || gv('[name="deliveryZip"]');
          return !!(street && city && zip);
        }
        function onClick(ev) {
          var hit = ev.target && ev.target.closest
            ? ev.target.closest('button, a, input[type="submit"], [role="button"]') : null;
          var label = ((hit && (hit.textContent || hit.getAttribute('aria-label') || hit.value)) || '')
            .trim().toLowerCase();
          if (/ulo[žźżz]i[tť]|\bsave\b/.test(label)) { saved = true; savedHasAddress = readHasAddress(); }
          else if (/zru[šs]i[tť]|cancel|close|^×$/.test(label)) { saved = false; }
        }
        try { scope.addEventListener('click', onClick, true); } catch (_) { /* noop */ }
        var ticks = 0;
        (function poll() {
          ticks++;
          var present = document.body.contains(streetEl);
          var visible = present && streetEl.getBoundingClientRect().width > 0;
          if (visible) {
            if (ticks < 3000) setTimeout(poll, 200);            // ~10 min safety cap
            else { try { scope.removeEventListener('click', onClick, true); } catch (_) {} shippingWatcherActive = false; }
            return;
          }
          try { scope.removeEventListener('click', onClick, true); } catch (_) { /* noop */ }
          shippingWatcherActive = false;
          // Saved a REAL address AND the order still carries an NP marker →
          // switch to address delivery by stripping the marker. Empty address
          // (rule-2 branch clear) or no marker → do nothing.
          var hasNp = /\[NP:[^\]]+\]/.test(document.body ? document.body.textContent : '');
          if (saved && savedHasAddress && hasNp) removeNpBranchNoteAndSave();
        })();
      })();
    }
    // Kick off the watcher whenever the shipping-address modal is opened —
    // whether by the operator or by rule 2's programmatic click (the empty-address
    // guard above makes the rule-2 case a no-op). Tracked on window so
    // uninstallOverlay (BC logout) can detach it; otherwise it would keep firing.
    function onShippingModifyClick(ev) {
      var hit = ev.target && ev.target.closest
        ? ev.target.closest('[data-testid="buttonModifyShippingAddress"]') : null;
      if (hit) setTimeout(watchShippingAddressModalClose, 0);
    }
    document.addEventListener('click', onShippingModifyClick, true);
    window.__npShippingModifyClick = onShippingModifyClick;

    // (b) Optional parcel-dimensions modal — L/W/H (cm) + weight (kg). Any subset
    // may be filled; empty fields fall back to automatic sizing on the BE. Values
    // are stored in `manualDims` and passed to createShipment on the next click.
    function openDimsModal() {
      var cur = manualDims || {};
      var modal = document.createElement('div');
      modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:2147483647;display:flex;align-items:center;justify-content:center;font:13px/1.4 system-ui,-apple-system,sans-serif';
      function field(id, label, val, ph) {
        return '<label style="display:block;margin-bottom:8px">' + label
          + '<input id="' + id + '" type="text" inputmode="decimal" value="' + (val != null ? val : '')
          + '" placeholder="' + ph + '" style="width:100%;box-sizing:border-box;margin-top:2px;padding:6px 8px;border:1px solid #cbd5e1;border-radius:6px;font:inherit"></label>';
      }
      modal.innerHTML =
        '<div style="background:#fff;border-radius:10px;padding:18px;width:320px;box-shadow:0 10px 40px rgba(0,0,0,.25)">'
        + '<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:4px">'
        + '<span style="font-weight:700;font-size:14px">' + escapeText(t('orderDetail.dims.title')) + '</span>'
        + '<button id="np-dim-close" type="button" aria-label="' + escapeText(t('orderDetail.dims.close')) + '" style="background:none;border:none;font-size:22px;line-height:1;color:#64748b;cursor:pointer;padding:0;margin:-2px -4px 0 0">&times;</button>'
        + '</div>'
        + '<div style="color:#64748b;font-size:11px;margin-bottom:12px">' + escapeText(t('orderDetail.dims.help')) + '</div>'
        + field('np-dim-l', t('orderDetail.dims.length'), cur.l, t('orderDetail.dims.phLength'))
        + field('np-dim-w', t('orderDetail.dims.width'), cur.w, t('orderDetail.dims.phWidth'))
        + field('np-dim-h', t('orderDetail.dims.height'), cur.h, t('orderDetail.dims.phHeight'))
        // Weight is READ-ONLY (Tic-0032): NP derives the parcel weight from the
        // product weights, so we show the computed sum, not an editable field.
        + '<div style="margin:6px 0 2px"><span style="color:#334155">' + escapeText(t('orderDetail.dims.weightLabel')) + '</span><strong id="np-dim-wt-ro">…</strong>'
        + ' <span style="color:#64748b;font-size:11px">' + escapeText(t('orderDetail.dims.weightHint')) + '</span></div>'
        + '<div id="np-dim-note" style="min-height:14px;color:#0369a1;font-size:11px;margin-top:2px"></div>'
        + '<div style="display:flex;gap:8px;margin-top:14px;justify-content:flex-end">'
        + '<button id="np-dim-clear" type="button" class="btn btn-sm btn-secondary">' + escapeText(t('orderDetail.dims.clear')) + '</button>'
        + '<button id="np-dim-save" type="button" class="btn btn-sm" style="background:#e30613;color:#fff;border-color:#e30613">' + escapeText(t('orderDetail.dims.save')) + '</button>'
        + '</div></div>';
      document.body.appendChild(modal);
      function close() { if (modal.parentNode) modal.parentNode.removeChild(modal); }
      // Close ONLY via the × button or the action buttons — a backdrop click
      // must NOT dismiss the modal (Samuel 2026-07-06), so no operator loses
      // half-entered dimensions to a stray click outside the card.
      modal.querySelector('#np-dim-close').addEventListener('click', close);

      // Pre-fill a suggested box (real product dims × piece count, sorted) the
      // first time — the operator confirms or edits it. Skipped if they already
      // set values. Empty weight stays auto. (Tic-0032, Samuel 2026-07-04.)
      var noteEl = modal.querySelector('#np-dim-note');
      var bridge = window.__np_bridge || {};
      if (!manualDims && typeof bridge.suggestDimensions === 'function') {
        noteEl.textContent = t('orderDetail.dims.loading');
        bridge.suggestDimensions({ orderId: String(orderId), orderCode: String(orderCode), detailUrl: detailUrl })
          .then(function (s) {
            var wtRo = modal.querySelector('#np-dim-wt-ro');
            if (wtRo) wtRo.textContent = (s && s.weightKg != null) ? (String(s.weightKg).replace('.', ',') + ' kg') : '—';
            if (!s || s.error || !s.lengthCm) { noteEl.textContent = ''; return; }
            var lEl = modal.querySelector('#np-dim-l'), wEl = modal.querySelector('#np-dim-w'), hEl = modal.querySelector('#np-dim-h');
            if (!lEl.value) lEl.value = s.lengthCm;
            if (!wEl.value) wEl.value = s.widthCm;
            if (!hEl.value) hEl.value = s.heightCm;
            noteEl.textContent = s.fromProduct
              ? t('orderDetail.dims.suggestProduct')
              : t('orderDetail.dims.suggestDefault');
          })
          .catch(function () { noteEl.textContent = ''; var wtRo = modal.querySelector('#np-dim-wt-ro'); if (wtRo) wtRo.textContent = '—'; });
      }
      modal.querySelector('#np-dim-clear').addEventListener('click', function () { manualDims = null; renderDimsBtn(); close(); });
      modal.querySelector('#np-dim-save').addEventListener('click', function () {
        function num(id) { var v = parseFloat(String(modal.querySelector('#' + id).value || '').trim().replace(',', '.')); return (isFinite(v) && v > 0) ? v : null; }
        var d = { l: num('np-dim-l'), w: num('np-dim-w'), h: num('np-dim-h') };
        manualDims = (d.l || d.w || d.h) ? d : null;
        renderDimsBtn(); close();
      });
    }
    dimsBtn.addEventListener('click', openDimsModal);

    btn.addEventListener('click', async function () {
      var origLabel = btn.textContent;
      btn.disabled = true; btn.textContent = t('orderDetail.status.creating');
      result.innerHTML = '<span style="color:#64748b">' + escapeText(t('orderDetail.status.fetching')) + '</span>';

      try {
        // Cross-border (non-EU) shipments need per-item customs data (EN name,
        // HS code, country of origin) + (B2B) EORI. This single-create overlay
        // has no customs form, so for non-EU we hand THIS one order to the bulk
        // wizard via the shared bridge.routeShipmentStart — the SAME routing the
        // orders-list per-row create uses, so the decision lives in one place
        // (the preload). EU / domestic create directly here (fast path).
        // Capability-gated so older bridges fall through to legacy direct-create.
        var bridge = window.__np_bridge || {};
        if (typeof bridge.routeShipmentStart === 'function') {
          var routed = await bridge.routeShipmentStart({
            orderId: String(orderId), orderCode: String(orderCode), detailUrl: detailUrl,
            // Tic-0032: non-EU routes to the wizard — carry any manual box the
            // operator set here so the wizard pre-fills it (else it's lost).
            dims: manualDims || undefined
          });
          if (routed && routed.error) throw new Error(routed.error);
          if (routed && routed.handledByWizard) {
            // The wizard takes over the window and writes the tracking number
            // back to Shoptet itself on execute. Clear the overlay status so no
            // stale note lingers after the operator closes it.
            result.innerHTML = '';
            btn.disabled = false; btn.textContent = origLabel;
            return;
          }
        }

        var detail = await (window.__np_bridge || {}).createShipment({
          orderId: String(orderId), orderCode: String(orderCode), detailUrl: detailUrl,
          // (b) optional operator-entered real box for THIS shipment (cm). Weight
          // is derived from product weights on the BE, so it is not sent here.
          parcelLengthCm: (manualDims && manualDims.l) || undefined,
          parcelWidthCm:  (manualDims && manualDims.w) || undefined,
          parcelHeightCm: (manualDims && manualDims.h) || undefined
        });
        if (detail && detail.error) throw new Error(detail.error);
        if (!detail || !detail.shipment) throw new Error('No shipment returned');

        var s = detail.shipment;
        if (s.status === 'error') {
          var errMsg = s.error_message || 'unknown';
          result.innerHTML = '<span style="color:#991b1b">' + escapeText(t('orderDetail.status.errorPrefix')) + linkifyError(errMsg) + '</span>'
            + buildSettingsButtonHtml(errMsg);
          bindExternalLinks(result);
          btn.disabled = false; btn.textContent = t('orderDetail.btn.retry');
          return;
        }

        var html = '<div style="color:#166534;font-weight:600">' + escapeText(t('orderDetail.status.created')) + '</div>';
        if (s.np_tracking_number) html += '<div style="margin-top:4px">' + escapeText(t('orderDetail.status.trackingPrefix')) + '<code>' + s.np_tracking_number + '</code></div>';
        html += '<div id="np-writeback" style="margin-top:4px;font-size:11px;color:#64748b"></div>';
        html += '<button id="np-print-btn" style="margin-top:8px;width:100%;padding:8px;background:#fff;color:#1e293b;border:1px solid #e2e8f0;border-radius:6px;font:inherit;cursor:pointer">' + escapeText(t('orderDetail.btn.printLabel')) + '</button>';
        result.innerHTML = html;
        btn.style.display = 'none';

        // Write the tracking number + "Balení a Expedice" status into the
        // Shoptet order-detail form and save — Shoptet then emails the tracking
        // to the customer. We're already ON the detail page (overlay injects
        // here), so we touch the DOM directly. Human-like pacing between steps
        // (fill → status → save) per the project's Shoptet-automation rule.
        if (s.np_tracking_number) {
          writeTrackingToOrderDetail(s.np_tracking_number, result.querySelector('#np-writeback'));
        }

        var printBtn = result.querySelector('#np-print-btn');
        if (printBtn && detail.labelBase64) {
          printBtn.addEventListener('click', async function () {
            printBtn.disabled = true; printBtn.textContent = t('orderDetail.status.openingPrinter');
            try {
              await (window.__np_bridge || {}).printLabel(detail.labelBase64);
              printBtn.textContent = t('orderDetail.status.sentToPrinter');
            } catch (err) {
              printBtn.textContent = t('orderDetail.status.printFailedRetry');
              printBtn.disabled = false;
            }
          });
        }
      } catch (err) {
        var errStr = (err && err.message) || String(err);
        result.innerHTML = '<span style="color:#991b1b">' + linkifyError(errStr) + '</span>'
          + buildSettingsButtonHtml(errStr);
        bindExternalLinks(result);
        btn.disabled = false; btn.textContent = 'Retry';
      }
    });

    // Write the NP tracking number + "Balení a Expedice" (32) status into the
    // Shoptet order-detail form, then Save. Shoptet emails the tracking to the
    // customer on save. Status stays 32 (packing/dispatch) on create — it only
    // moves to "Odeslaná" (2) later, when NP confirms the courier picked it up
    // (LoadingCourier webhook). Human-like pacing between steps so the live
    // Shoptet doesn't see machine-instant form edits.
    async function writeTrackingToOrderDetail(trackingNumber, statusEl) {
      function setStatus(t) { if (statusEl) statusEl.textContent = t; }
      function gap(min, max) {
        var ms = Math.floor(min + Math.random() * (max - min));
        return new Promise(function (r) { setTimeout(r, ms); });
      }
      try {
        var input = document.querySelector('#package-number')
          || document.querySelector('[data-testid="inputPackageNumber"]')
          || document.querySelector('input[name="packageNumber"]');
        var sel = document.querySelector('[data-testid="selectOrderStatus"]')
          || document.querySelector('select[name="statusId"]');
        var saveBtn = document.querySelector('a.submit-js[rel="saveAndStay"], a.submit-js[data-testid="buttonSaveAndStay"]');
        if (!input || !sel || !saveBtn) {
          setStatus('⚠ Could not auto-write tracking (form fields not found) — set it manually.');
          return;
        }

        setStatus('Writing tracking…');
        await gap(300, 700);
        // Native setter so Shoptet's input bindings register the value.
        var nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
        nativeSetter.call(input, trackingNumber);
        input.dispatchEvent(new Event('input', { bubbles: true }));
        input.dispatchEvent(new Event('change', { bubbles: true }));

        await gap(500, 1200);
        // Move the order to the post-create status. Shoptet status IDs are
        // PER-SHOP, so match by LABEL using an ordered preference list (first
        // existing option wins): the real "Balení a Expedice"; on shops that
        // lack it (e.g. the 795991 test shop, which only has the default -1..-4
        // set) fall back to "Vyřizuje se". Also accept the well-known id 32.
        // If none exist, leave the status untouched — tracking still saved.
        var prefLabels = ['baleni a expedice', 'vyrizuje se'];
        var target = null;
        for (var p = 0; p < prefLabels.length && target === null; p++) {
          for (var i = 0; i < sel.options.length; i++) {
            var lbl = (sel.options[i].textContent || '').trim().toLowerCase();
            var nlbl = lbl.normalize ? lbl.normalize('NFD').replace(/[̀-ͯ]/g, '') : lbl;
            if (nlbl.indexOf(prefLabels[p]) !== -1) { target = sel.options[i].value; break; }
          }
        }
        if (target === null) {
          for (var k = 0; k < sel.options.length; k++) { if (sel.options[k].value === '32') { target = '32'; break; } }
        }
        var statusChanged = false;
        if (target !== null && sel.value !== target) {
          await gap(400, 900);
          sel.value = target;
          // Shoptet AUTO-SAVES the whole order form on a status change (the
          // select's js-onchange-submit). Dispatching change therefore persists
          // BOTH the tracking field set above AND the new status in one submit
          // — verified on 795991 (UAT-012, DIAG-autosave). We must NOT then also
          // click saveAndStay: that second save RACES the auto-save and
          // intermittently reverts the status to its old value (the bug the user
          // saw — "Vyřizuje se" flashing back to "Nevyřízená").
          sel.dispatchEvent(new Event('change', { bubbles: true }));
          statusChanged = true;
        }

        if (statusChanged) {
          // The auto-save does a full submit/reload of the detail, which strips
          // the overlay; the host re-installs it on the new page load.
          setStatus('✓ Tracking + status saved to the order.');
        } else {
          // No status to change (shop lacks the target status, or it's already
          // set) → the tracking field on its own won't auto-save, so persist it
          // explicitly via Save.
          await gap(500, 1100);
          setStatus('Saving…');
          saveBtn.click();
          await gap(800, 1500);
          setStatus('✓ Tracking saved to the order.');
          if (window.__np_bridge && window.__np_bridge.refreshUi) {
            setTimeout(function () { try { window.__np_bridge.refreshUi(); } catch (e) {} }, 1800);
          }
        }
      } catch (e) {
        setStatus('⚠ Auto-write failed: ' + ((e && e.message) || e) + ' — set tracking manually.');
      }
    }

    // Req-F-018 storno — confirmation dialog + action. Cancelling a shipment:
    //   1. cancels it in Nova Post, 2. removes the tracking code from the order,
    //   3. sets the order status to the operator-chosen value (dropdown loaded
    //      from the e-shop's own statusId options). OK / Cancel.
    cancelBtn.addEventListener('click', openCancelDialog);

    // Each button prints its own document set to its own printer role:
    // 'label' → thermal 10×10, 'customs' → A4. (Roles resolved in main.)
    function wirePrintButton(btn, what) {
      btn.addEventListener('click', async function () {
        if (!existingShipment || !existingShipment.id) return;
        btn.disabled = true;
        var orig = btn.textContent;
        btn.textContent = t('orderDetail.status.printing');
        try {
          var r = await (window.__np_bridge || {}).printShipment(existingShipment.id, { what: what });
          if (r && r.error) throw new Error(r.error);
          btn.textContent = t('orderDetail.status.sentToPrinter');
          setTimeout(function () { btn.disabled = false; btn.textContent = orig; }, 2000);
        } catch (e) {
          result.innerHTML = escapeText(t('orderDetail.status.printError')) + escapeText(e && e.message ? e.message : e);
          btn.disabled = false;
          btn.textContent = orig;
        }
      });
    }
    wirePrintButton(printLabelBtn, 'label');
    wirePrintButton(printCustomsBtn, 'customs');

    function openCancelDialog() {
      if (document.getElementById('np-cancel-modal')) return;
      var statusSel = document.querySelector('[data-testid="selectOrderStatus"]')
        || document.querySelector('select[name="statusId"]');
      var optsHtml = '';
      if (statusSel) {
        for (var i = 0; i < statusSel.options.length; i++) {
          var o = statusSel.options[i];
          if (o.value === '') continue;
          optsHtml += '<option value="' + escapeText(o.value) + '">'
            + escapeText((o.textContent || '').trim()) + '</option>';
        }
      }
      var modal = document.createElement('div');
      modal.id = 'np-cancel-modal';
      modal.style.cssText = 'position:fixed;inset:0;z-index:2147483647;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center';
      modal.innerHTML =
        '<div style="background:#fff;border-radius:8px;max-width:460px;width:92%;padding:18px 20px;font:13px -apple-system,sans-serif;color:#1e293b">'
        + '<h3 style="margin:0 0 12px;font-size:15px">' + escapeText(t('orderDetail.cancel.title')) + '</h3>'
        + '<ol style="margin:0 0 12px;padding-left:20px;line-height:1.7">'
        + '<li>' + escapeText(t('orderDetail.cancel.step1')) + '</li>'
        + '<li>' + escapeText(t('orderDetail.cancel.step2')) + '</li>'
        + '<li>' + escapeText(t('orderDetail.cancel.step3')) + '<br>'
        + '<select id="np-cancel-status" style="margin-top:6px;width:100%;padding:7px 9px;border:1px solid #cbd5e1;border-radius:6px;font:inherit">'
        + optsHtml + '</select></li>'
        + '</ol>'
        + '<div id="np-cancel-msg" style="font-size:11px;color:#64748b;min-height:15px"></div>'
        + '<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:12px">'
        + '<button id="np-cancel-no" type="button" style="padding:7px 18px;border:1px solid #cbd5e1;background:#fff;border-radius:6px;cursor:pointer">' + escapeText(t('orderDetail.cancel.dismiss')) + '</button>'
        + '<button id="np-cancel-ok" type="button" style="padding:7px 18px;border:0;background:#b91c1c;color:#fff;border-radius:6px;cursor:pointer">' + escapeText(t('orderDetail.cancel.confirm')) + '</button>'
        + '</div></div>';
      document.body.appendChild(modal);
      function closeDialog() {
        var m = document.getElementById('np-cancel-modal');
        if (m && m.parentNode) m.parentNode.removeChild(m);
      }
      modal.querySelector('#np-cancel-no').addEventListener('click', closeDialog);
      modal.querySelector('#np-cancel-ok').addEventListener('click', function () {
        var v = modal.querySelector('#np-cancel-status').value;
        doCancel(v, modal.querySelector('#np-cancel-msg'), closeDialog);
      });
    }

    async function doCancel(statusValue, msgEl, closeDialog) {
      function gap(min, max) {
        var ms = Math.floor(min + Math.random() * (max - min));
        return new Promise(function (r) { setTimeout(r, ms); });
      }
      function setMsg(msg) { if (msgEl) msgEl.textContent = msg; }
      try {
        setMsg(t('orderDetail.cancel.progress.cancelling'));
        var res = await (window.__np_bridge || {}).cancelShipment(existingShipment.id);
        if (res && res.error) throw new Error(res.error);

        await gap(300, 700);
        setMsg(t('orderDetail.cancel.progress.updating'));
        var input = document.querySelector('#package-number')
          || document.querySelector('[data-testid="inputPackageNumber"]')
          || document.querySelector('input[name="packageNumber"]');
        var sel = document.querySelector('[data-testid="selectOrderStatus"]')
          || document.querySelector('select[name="statusId"]');
        if (input) {
          var ns = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
          ns.call(input, '');
          input.dispatchEvent(new Event('input', { bubbles: true }));
          input.dispatchEvent(new Event('change', { bubbles: true }));
        }
        await gap(300, 700);
        if (sel && statusValue != null && statusValue !== '') {
          sel.value = String(statusValue);
          // The status select auto-saves the whole form on change (persisting
          // the cleared tracking too) — same mechanism as the create write-back.
          sel.dispatchEvent(new Event('change', { bubbles: true }));
        } else {
          var saveBtn = document.querySelector('a.submit-js[rel="saveAndStay"], a.submit-js[data-testid="buttonSaveAndStay"]');
          if (saveBtn) saveBtn.click();
        }
        if (closeDialog) closeDialog();
      } catch (e) {
        setMsg(t('orderDetail.cancel.failed') + ((e && e.message) || e));
      }
    }

    return 'installed';
  }

  function buildInstallScript(orderId, orderCode, detailUrl, opts) {
    return '(' + installOverlay.toString() + ')('
      + JSON.stringify(String(orderId)) + ','
      + JSON.stringify(String(orderCode)) + ','
      + JSON.stringify(String(detailUrl)) + ','
      + JSON.stringify(opts || {}) + ')';
  }

  // Extract current order's id + code + detail URL from a detail page's DOM.
  // Runs in page context.
  function extractCurrentOrderInfo() {
    var detailLink = document.querySelector('link[rel=canonical]');
    var url = (detailLink && detailLink.href) || location.href;
    // Order ID from URL param `id=NNN` in Shoptet admin.
    var m = location.search.match(/id=(\d+)/);
    var id = m ? m[1] : '';
    // Order code usually shown in page heading.
    var h = document.querySelector('h1, .heading');
    var code = h ? (h.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 40) : ('Order ' + id);
    return { id: id, code: code, detailUrl: url };
  }
  var EXTRACT_ORDER_INFO_SRC = '(' + extractCurrentOrderInfo.toString() + ')()';

  // Remove everything installOverlay injected into a Shoptet order-detail page —
  // the action buttons, the result row, the branch picker modal, the marker +
  // style nodes — and undo the #order-header push-down. Runs in page MAIN world
  // on BC logout so the overlay disappears immediately, without a page reload.
  function uninstallOverlay() {
    var ids = [
      'np-overlay', 'np-overlay-style', 'np-result', 'np-picker-modal',
      'np-create-btn', 'np-change-branch-btn', 'np-cancel-btn',
      'np-print-label-btn', 'np-print-customs-btn',
      'np-section'
    ];
    for (var i = 0; i < ids.length; i++) {
      var el = document.getElementById(ids[i]);
      if (el && el.parentNode) el.parentNode.removeChild(el);
    }
    try {
      var obs = window.__npOrderHeaderObs;
      if (obs && typeof obs.disconnect === 'function') obs.disconnect();
    } catch (e) { /* noop */ }
    try {
      var hdr = window.__npOrderHeaderEl;
      if (hdr && hdr.style) hdr.style.marginTop = '';
    } catch (e) { /* noop */ }
    try {
      var shipClick = window.__npShippingModifyClick;
      if (shipClick) document.removeEventListener('click', shipClick, true);
    } catch (e) { /* noop */ }
    window.__npOrderHeaderObs = null;
    window.__npOrderHeaderEl = null;
    window.__npShippingModifyClick = null;
    return 'ok';
  }
  var UNINSTALL_SRC = '(' + uninstallOverlay.toString() + ')()';

  var api = {
    buildInstallScript: buildInstallScript,
    EXTRACT_ORDER_INFO_SRC: EXTRACT_ORDER_INFO_SRC,
    UNINSTALL_SRC: UNINSTALL_SRC
  };
  if (typeof module !== 'undefined' && module.exports) module.exports = api;
  else { root.NP = root.NP || {}; root.NP.shoptetOverlay = api; }
})(typeof self !== 'undefined' ? self : this);
