// labelCenter.js — Label Center overlay: cross-device label queue, sheet assignment, and batch print.

import { buildLabelHTML, renderBarcode, formatPhone } from './labelGen.js';
import { showToast } from './toast.js';
import { searchLocal } from './assetBrowser.js';
import CONFIG from '../config.js';

// ── Sheet type configurations ─────────────────────────────────────────────────

const SHEET_CONFIGS = {
  OL875LP: {
    label:      'OL875LP \u2014 2.625\u2033 \xd7 1\u2033 (30-up)',
    labelW:     2.625,
    labelH:     1.0,
    cols:       3,
    rows:       10,
    pageSize:   30,
    colOffsets: [0.1875, 2.9375, 5.6875],
    rowOffsets: [0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5],
  },
  OL25LP: {
    label:      'OL25LP \u2014 1.75\u2033 \xd7 0.5\u2033 (80-up)',
    labelW:     1.75,
    labelH:     0.5,
    cols:       4,
    rows:       20,
    pageSize:   80,
    colOffsets: [0.328125, 2.359375, 4.390625, 6.421875],
    rowOffsets: Array.from({ length: 20 }, (_, i) => 0.5 + i * 0.5),
  },
};

let _sheetTypeKey = localStorage.getItem('lc_sheet_type') || 'OL875LP';
function activeSheet() { return SHEET_CONFIGS[_sheetTypeKey] ?? SHEET_CONFIGS.OL875LP; }

// ── Module state ──────────────────────────────────────────────────────────────

function posStart(page) { return (page - 1) * activeSheet().pageSize + 1; }
function posEnd(page)   { return page * activeSheet().pageSize; }

let _queue = [];               // Array<QueueItem> — synced from server
let _pickerTargetPos = null;   // which grid position the picker is open for
let _pageCount = 1;

function _loadPageCount() {
  const maxPos = Math.max(0,
    ..._queue.map(i => i.sheet_position ?? 0),
    ...[..._usedPositions],
  );
  const pagesNeeded = maxPos > 0 ? Math.ceil(maxPos / activeSheet().pageSize) : 1;
  const stored = parseInt(localStorage.getItem('lc_page_count') || '1', 10);
  _pageCount = Math.max(pagesNeeded, stored, 1);
}

function _savePageCount() {
  localStorage.setItem('lc_page_count', String(_pageCount));
}

let _usedPositions   = new Set(
  JSON.parse(localStorage.getItem('lc_used_positions') || '[]')
);

function _saveUsedPositions() {
  localStorage.setItem('lc_used_positions', JSON.stringify([..._usedPositions]));
}

// ── Derived helpers ───────────────────────────────────────────────────────────

function assignedItems()   { return _queue.filter(i => i.sheet_position != null); }
function unassignedItems() { return _queue.filter(i => i.sheet_position == null); }
function itemAtPos(pos)    { return _queue.find(i => i.sheet_position === pos) ?? null; }

// ── Public API ────────────────────────────────────────────────────────────────

export function initLabelCenter() {
  document.getElementById('btn-label-center')?.addEventListener('click', openLabelCenter);
  document.getElementById('lc-close-btn')?.addEventListener('click', closeLabelCenter);
  document.getElementById('lc-clear-all-btn')?.addEventListener('click', handleClearAll);
  document.getElementById('lc-autofill-btn')?.addEventListener('click', handleAutoFill);
  document.getElementById('lc-reset-all-btn')?.addEventListener('click', () => handleResetSheet(false));
  document.getElementById('lc-print-btn')?.addEventListener('click', handlePrintAll);
  document.getElementById('lc-picker-cancel')?.addEventListener('click', closePicker);

  document.getElementById('lc-sheet-type')?.addEventListener('change', async e => {
    const newKey = e.target.value;
    if (newKey === _sheetTypeKey) return;
    const hasAssigned = _queue.some(i => i.sheet_position != null);
    if (hasAssigned) {
      const confirmed = await _showSheetChangeConfirm(newKey);
      if (!confirmed) {
        e.target.value = _sheetTypeKey; // revert
        return;
      }
      await handleResetSheet(true);
    }
    _sheetTypeKey = newKey;
    localStorage.setItem('lc_sheet_type', newKey);
    _usedPositions.clear();
    _saveUsedPositions();
    _pageCount = 1;
    _savePageCount();
    renderAllPages();
  });

  // Close picker on outside click
  document.addEventListener('click', e => {
    const picker = document.getElementById('lc-picker');
    if (picker && !picker.hidden && !picker.contains(e.target)) {
      closePicker();
    }
  }, { capture: true });

  // Close overlay on Escape
  document.addEventListener('keydown', e => {
    const overlay = document.getElementById('label-center-overlay');
    if (e.key === 'Escape' && overlay && !overlay.hidden) {
      const picker = document.getElementById('lc-picker');
      if (picker && !picker.hidden) {
        closePicker();
      } else {
        closeLabelCenter();
      }
    }
  });
}

export async function addToQueue(asset) {
  const customerName = asset.customer?.business_name
    ?? asset.customer?.business_then_name
    ?? asset.customer?.name
    ?? '—';
  const phone = asset.contact?.phone ?? asset.customer?.phone ?? null;

  try {
    const res = await fetch('/api/label-queue', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        asset_id:      asset.id,
        asset_name:    asset.name,
        asset_serial:  asset.asset_serial ?? asset.serial ?? null,
        customer_name: customerName,
        customer_phone: phone,
      }),
    });
    if (!res.ok) {
      const { error } = await res.json().catch(() => ({}));
      showToast(error ?? 'Failed to add to queue', 'error');
      return;
    }
    const { queue } = await res.json();
    _queue = queue;
    updateBadgeDOM(queue.length);

    const btn = document.getElementById('action-add-to-queue');
    if (btn) {
      btn.classList.add('added');
      btn.innerHTML = `${iconQueuePlus()} Added ✓`;
      setTimeout(() => {
        btn.classList.remove('added');
        btn.innerHTML = `${iconQueuePlus()} Add to Sheet`;
      }, 1800);
    }

    showToast(`${esc(asset.name)} added to Label Center`, 'success');
  } catch (err) {
    showToast('Failed to add to queue: ' + err.message, 'error');
  }
}

export async function addToQueueAndOpen(asset) {
  await addToQueue(asset);
  openLabelCenter();
}

export async function refreshBadge() {
  try {
    const res = await fetch('/api/label-queue', { credentials: 'same-origin' });
    if (!res.ok) return;
    const { queue } = await res.json();
    _queue = queue;
    updateBadgeDOM(queue.length);
  } catch { /* ignore — badge stays hidden */ }
}

// ── Overlay lifecycle ─────────────────────────────────────────────────────────

async function openLabelCenter() {
  const overlay = document.getElementById('label-center-overlay');
  if (!overlay) return;
  overlay.hidden = false;

  const sheetSelect = document.getElementById('lc-sheet-type');
  if (sheetSelect) sheetSelect.value = _sheetTypeKey;

  try {
    const res = await fetch('/api/label-queue', { credentials: 'same-origin' });
    if (!res.ok) throw new Error('Failed to load queue');
    const { queue } = await res.json();
    _queue = queue;
    updateBadgeDOM(queue.length);
  } catch (err) {
    showToast('Could not load label queue: ' + err.message, 'error');
    _queue = [];
  }

  renderQueuePanel();
  renderAllPages();
}

export function closeLabelCenter() {
  const overlay = document.getElementById('label-center-overlay');
  if (overlay) overlay.hidden = true;
  closePicker();
}

// ── Queue panel ───────────────────────────────────────────────────────────────

function renderQueuePanel() {
  const list = document.getElementById('lc-queue-list');
  const countEl = document.getElementById('lc-queue-count');
  const printBtn = document.getElementById('lc-print-btn');

  if (countEl) {
    countEl.textContent = `${_queue.length} item${_queue.length !== 1 ? 's' : ''} queued`;
  }
  if (printBtn) {
    printBtn.disabled = assignedItems().length === 0;
  }

  if (!list) return;

  if (!_queue.length) {
    list.innerHTML = `<div class="lc-queue-empty">No labels queued. Tap "Add to Sheet" on any asset card to get started.</div>`;
    return;
  }

  list.innerHTML = _queue.map(item => {
    const pos = item.sheet_position;
    return `
<div class="lc-queue-item${pos != null ? ' assigned' : ''}" data-asset-id="${item.asset_id}">
  <div class="lc-queue-item-info">
    <div class="lc-queue-item-name">${esc(item.asset_name)}</div>
    <div class="lc-queue-item-customer">${esc(item.customer_name)}</div>
    ${pos != null ? `<div class="lc-queue-item-pos">Position ${pos}</div>` : ''}
  </div>
  <button class="lc-queue-item-remove" data-remove-id="${item.asset_id}" title="Remove from queue" aria-label="Remove ${esc(item.asset_name)} from queue">✕</button>
</div>`;
  }).join('');

  list.querySelectorAll('[data-remove-id]').forEach(btn => {
    btn.addEventListener('click', e => {
      e.stopPropagation();
      handleRemoveItem(parseInt(btn.dataset.removeId, 10));
    });
  });
}

// ── Sheet grid (multi-page) ────────────────────────────────────────────────

function renderAllPages() {
  const container = document.getElementById('lc-pages-container');
  if (!container) return;

  _loadPageCount();

  let html = '';
  for (let p = 1; p <= _pageCount; p++) {
    const start    = posStart(p);
    const end      = posEnd(p);
    const hasItems = _queue.some(i => i.sheet_position != null && i.sheet_position >= start && i.sheet_position <= end);
    html += `
<div class="lc-page-card" data-page="${p}">
  <div class="lc-page-card-header">
    <span class="lc-page-card-title">Page ${p}<span class="lc-page-pos-range">pos. ${start}–${end}</span></span>
    <div class="lc-page-card-btns">
      <button class="btn btn-ghost lc-page-autofill-btn" data-page="${p}" style="font-size:.78rem;padding:4px 10px">Auto-fill</button>
      <button class="btn btn-ghost lc-page-print-btn" data-page="${p}" style="font-size:.78rem;padding:4px 10px"${!hasItems ? ' disabled' : ''}>Print</button>
      <button class="btn btn-ghost btn-danger lc-page-reset-btn" data-page="${p}" style="font-size:.78rem;padding:4px 10px"${!hasItems ? ' disabled' : ''}>Reset</button>
      <button class="btn btn-ghost lc-page-remove-btn" data-page="${p}" style="font-size:.78rem;padding:4px 8px;border-color:var(--red-200,#fecaca);color:var(--red,#dc2626);" title="Remove page ${p}">
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/><path d="M10 11v6M14 11v6"/></svg>
      </button>
    </div>
  </div>
  <div class="lc-sheet-grid" id="lc-page-grid-${p}"></div>
</div>`;
  }

  html += `
<button class="lc-add-page-btn" id="lc-add-page-btn">
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
  Add page
</button>`;

  container.innerHTML = html;

  for (let p = 1; p <= _pageCount; p++) {
    renderPageGrid(p);
  }

  container.querySelectorAll('.lc-page-autofill-btn').forEach(btn => {
    btn.addEventListener('click', () => handleAutoFillPage(+btn.dataset.page));
  });
  container.querySelectorAll('.lc-page-print-btn').forEach(btn => {
    btn.addEventListener('click', () => handlePrintPage(+btn.dataset.page));
  });
  container.querySelectorAll('.lc-page-reset-btn').forEach(btn => {
    btn.addEventListener('click', () => handleResetPage(+btn.dataset.page));
  });
  container.querySelectorAll('.lc-page-remove-btn').forEach(btn => {
    btn.addEventListener('click', () => handleRemovePage(+btn.dataset.page));
  });
  document.getElementById('lc-add-page-btn')?.addEventListener('click', () => {
    _pageCount++;
    _savePageCount();
    renderAllPages();
  });
}

function renderPageGrid(page) {
  const grid = document.getElementById(`lc-page-grid-${page}`);
  if (!grid) return;

  grid.style.gridTemplateColumns = `repeat(${activeSheet().cols}, 1fr)`;

  const start = posStart(page);
  const end   = posEnd(page);
  let html = '';
  const barcodeQueue = [];

  for (let pos = start; pos <= end; pos++) {
    const item     = itemAtPos(pos);
    const localPos = pos - start + 1;

    if (item) {
      let labelHTML;
      let svgIdForSn = null, serial = null;

      if (_sheetTypeKey === 'OL25LP') {
        const sn = item.asset_serial ?? '';
        if (sn) {
          const bcPreviewId = `lc-cell-bc-${item.asset_id}-${pos}`;
          svgIdForSn = bcPreviewId;
          serial = sn;
          labelHTML = `<div class="lc-ol25lp-preview">
            <img id="${bcPreviewId}" class="lc-ol25lp-bc" alt="">
            <div class="lc-ol25lp-sn"><span>${esc(sn)}</span></div>
          </div>`;
        } else {
          labelHTML = `<div class="lc-ol25lp-preview"><div class="lc-ol25lp-sn lc-ol25lp-name">${esc(item.asset_name)}</div></div>`;
        }
      } else {
        const assetObj = {
          id:           item.asset_id,
          name:         item.asset_name,
          asset_serial: item.asset_serial ?? null,
          custom_line:  item.custom_line ?? null,
          customer: {
            business_name: item.customer_name,
            phone:         item.customer_phone ?? null,
          },
        };
        const prefix = `lc-cell-${item.asset_id}-${pos}`;
        ({ html: labelHTML, svgIdForSn, serial } = buildLabelHTML(assetObj, prefix));
      }
      if (svgIdForSn && serial) barcodeQueue.push({ svgIdForSn, serial });

      html += `
<div class="lc-cell filled" data-pos="${pos}" data-asset-id="${item.asset_id}"
     aria-label="Position ${pos}: ${esc(item.asset_name)}">
  <div class="lc-cell-content">${labelHTML}</div>
  <div class="lc-cell-pos-number">${localPos}</div>
  <div class="lc-cell-actions">
    <button class="lc-cell-action lc-cell-action--delete" title="Remove label" aria-label="Remove label">
      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
        <polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/>
      </svg>
      <span class="lc-cell-action-label">Remove</span>
    </button>
    <button class="lc-cell-action lc-cell-action--edit" title="Edit label" aria-label="Edit label">
      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
        <path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
        <path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
      </svg>
      <span class="lc-cell-action-label">Edit</span>
    </button>
  </div>
</div>`;
    } else if (_usedPositions.has(pos)) {
      html += `
<div class="lc-cell used" data-pos="${pos}" role="button" tabindex="0"
     aria-label="Position ${pos}: marked as used — click to clear">
  <div class="lc-cell-pos-number">${localPos}</div>
  <span style="font-size:.6rem;position:absolute;bottom:3px;right:4px;opacity:.6">Used</span>
</div>`;
    } else {
      html += `
<div class="lc-cell empty" data-pos="${pos}" role="button" tabindex="0"
     aria-label="Position ${pos}: empty — click to assign">
  <div class="lc-cell-pos-number">${localPos}</div>
</div>`;
    }
  }

  grid.innerHTML = html;
  barcodeQueue.forEach(({ svgIdForSn, serial }) => renderBarcode(svgIdForSn, serial));

  grid.querySelectorAll('.lc-cell').forEach(cell => {
    cell.addEventListener('click', () => handleCellClick(parseInt(cell.dataset.pos, 10)));
    cell.addEventListener('keydown', e => {
      if (e.key === 'Enter' || e.key === ' ') {
        e.preventDefault();
        handleCellClick(parseInt(cell.dataset.pos, 10));
      }
    });
  });

  grid.querySelectorAll('.lc-cell-action--delete').forEach(btn => {
    btn.addEventListener('click', e => {
      e.stopPropagation();
      const pos = parseInt(btn.closest('.lc-cell').dataset.pos, 10);
      const item = itemAtPos(pos);
      if (item) unassignPosition(item.asset_id);
    });
  });

  grid.querySelectorAll('.lc-cell-action--edit').forEach(btn => {
    btn.addEventListener('click', e => {
      e.stopPropagation();
      const pos  = parseInt(btn.closest('.lc-cell').dataset.pos, 10);
      const item = itemAtPos(pos);
      if (item) _showEditLabelForm(pos, item);
    });
  });
}

function handleCellClick(pos) {
  const item = itemAtPos(pos);
  if (item) {
    return; // handled by action buttons on hover
  } else if (_usedPositions.has(pos)) {
    // Toggle: un-mark as used
    _usedPositions.delete(pos);
    _saveUsedPositions();
    renderAllPages();
  } else {
    openPicker(pos);
  }
}

// ── Assignment picker ─────────────────────────────────────────────────────────

function openPicker(pos) {
  _pickerTargetPos = pos;
  const picker = document.getElementById('lc-picker');
  const posLabel = document.getElementById('lc-picker-pos-label');
  const pickerList = document.getElementById('lc-picker-list');
  if (!picker || !pickerList) return;

  if (posLabel) posLabel.textContent = pos;

  const unassigned = unassignedItems();

  const itemsHTML = unassigned.length
    ? unassigned.map(item => `
<button class="lc-picker-item" data-pick-id="${item.asset_id}">
  <div class="lc-picker-item-name">${esc(item.asset_name)}</div>
  <div class="lc-picker-item-customer">${esc(item.customer_name)}</div>
</button>`).join('')
    : `<div class="lc-picker-empty">No unassigned labels</div>`;

  pickerList.innerHTML = itemsHTML + `
<div class="lc-picker-footer-row">
  <button class="lc-picker-mark-used" id="lc-picker-mark-used-btn">
    <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
      <circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>
    </svg>
    Mark as used
  </button>
  <button class="lc-picker-custom-label" id="lc-picker-custom-label-btn">
    <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
      <rect x="3" y="5" width="18" height="14" rx="2"/><line x1="8" y1="10" x2="16" y2="10"/><line x1="8" y1="14" x2="13" y2="14"/>
    </svg>
    Custom label
  </button>
</div>`;

  pickerList.querySelectorAll('[data-pick-id]').forEach(btn => {
    btn.addEventListener('click', () => {
      assignPosition(parseInt(btn.dataset.pickId, 10), _pickerTargetPos);
      closePicker();
    });
  });

  document.getElementById('lc-picker-mark-used-btn')?.addEventListener('click', () => {
    _usedPositions.add(_pickerTargetPos);
    _saveUsedPositions();
    closePicker();
    renderAllPages();
  });

  document.getElementById('lc-picker-custom-label-btn')?.addEventListener('click', () => {
    const targetPos = _pickerTargetPos;
    closePicker();
    _showCustomLabelForm(targetPos);
  });

  // Position the picker near the clicked cell
  const cell = document.querySelector(`.lc-cell[data-pos="${pos}"]`);
  if (cell) {
    const rect = cell.getBoundingClientRect();
    const pickerW = 290;
    const pickerH = 480;
    let left = rect.left + rect.width / 2 - pickerW / 2;
    let top  = rect.bottom + 8;

    left = Math.max(8, Math.min(left, window.innerWidth  - pickerW - 8));
    top  = Math.max(8, Math.min(top,  window.innerHeight - pickerH - 8));

    picker.style.left = left + 'px';
    picker.style.top  = top  + 'px';
  }

  picker.hidden = false;
  _setupPickerSearch(pos);
}

function _setupPickerSearch(pos) {
  const input   = document.getElementById('lc-picker-search');
  const results = document.getElementById('lc-picker-search-results');
  if (!input || !results) return;

  input.value = '';
  results.hidden = true;
  results.innerHTML = '';

  let _timer      = null;
  let _activeIdx  = -1;
  let _items      = [];
  let _remoteReq  = 0; // cancel stale remote results

  function _render(items) {
    _items     = items;
    _activeIdx = -1;
    if (!items.length) { results.hidden = true; return; }

    results.innerHTML = items.map((item, i) => {
      const meta = [item.customerName, item.serial].filter(Boolean).join(' · ');
      return `<button class="lc-picker-search-result" data-idx="${i}">
        <div class="lc-picker-search-result-name">${esc(item.asset?.name ?? `Asset ${item.asset?.id}`)}</div>
        ${meta ? `<div class="lc-picker-search-result-meta">${esc(meta)}</div>` : ''}
      </button>`;
    }).join('');
    results.hidden = false;

    results.querySelectorAll('[data-idx]').forEach(btn => {
      btn.addEventListener('click', () => _pick(_items[+btn.dataset.idx]));
    });
  }

  function _updateActive() {
    results.querySelectorAll('.lc-picker-search-result').forEach((btn, i) => {
      btn.classList.toggle('active', i === _activeIdx);
      if (i === _activeIdx) btn.scrollIntoView({ block: 'nearest' });
    });
  }

  async function _search(q) {
    const req = ++_remoteReq;

    // Immediate local results
    const local = searchLocal(q).map(r => ({ ...r }));
    _render(local);

    // Show spinner row while remote is in flight
    if (local.length === 0) {
      results.innerHTML = `<div class="lc-picker-search-spinner">Searching…</div>`;
      results.hidden = false;
    }

    try {
      const res  = await fetch(`/api/asset/search?query=${encodeURIComponent(q)}`);
      if (req !== _remoteReq) return; // stale
      if (!res.ok) return;
      const data = await res.json();
      const localIds = new Set(local.map(r => r.asset?.id));
      const remote   = (data.assets ?? []).map(a => ({
        asset:        a,
        serial:       a.asset_serial ?? a.serial ?? null,
        customerName: a.customer?.business_name ?? a.customer?.business_then_name ?? a.customer?.name ?? null,
      })).filter(r => !localIds.has(r.asset?.id));
      _render([...local, ...remote]);
    } catch { /* leave local results */ }
  }

  function _pick(item) {
    if (!item?.asset) return;
    input.value = '';
    results.hidden = true;
    addAndAssign(item.asset, item.customerName, pos);
    closePicker();
  }

  input.addEventListener('input', () => {
    clearTimeout(_timer);
    const q = input.value.trim();
    if (q.length < 2) { results.hidden = true; _items = []; return; }
    _timer = setTimeout(() => _search(q), 220);
  });

  input.addEventListener('keydown', e => {
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      _activeIdx = Math.min(_activeIdx + 1, _items.length - 1);
      _updateActive();
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      _activeIdx = Math.max(_activeIdx - 1, 0);
      _updateActive();
    } else if (e.key === 'Enter' && _activeIdx >= 0) {
      e.preventDefault();
      _pick(_items[_activeIdx]);
    } else if (e.key === 'Escape') {
      e.stopPropagation();
      closePicker();
    }
  });

  // Focus search on open
  setTimeout(() => input.focus(), 40);
}

function _showCustomLabelForm(pos) {
  // Remove any existing form
  document.getElementById('lc-custom-label-backdrop')?.remove();

  const backdrop = document.createElement('div');
  backdrop.id = 'lc-custom-label-backdrop';
  backdrop.className = 'lc-print-dialog-backdrop';

  backdrop.innerHTML = `
<div class="lc-custom-label-form" role="dialog" aria-modal="true" aria-label="Custom label">
  <div class="lc-custom-label-form-header">Custom Label — Position ${pos}</div>
  <div class="lc-clf-field">
    <label class="lc-clf-label">Device name <span class="lc-clf-required">*</span></label>
    <input class="lc-clf-input" id="clf-device" type="text" placeholder="e.g. Dell Latitude 5520" autocomplete="off">
    <div class="lc-clf-error" id="clf-device-err" hidden>Required</div>
  </div>
  <div class="lc-clf-field">
    <label class="lc-clf-label">Owner name <span class="lc-clf-required">*</span></label>
    <input class="lc-clf-input" id="clf-owner" type="text" placeholder="e.g. Jane Smith" autocomplete="off">
    <div class="lc-clf-error" id="clf-owner-err" hidden>Required</div>
  </div>
  <div class="lc-clf-field">
    <label class="lc-clf-label">Phone <span style="color:var(--text-muted);font-size:11px">(optional)</span></label>
    <input class="lc-clf-input" id="clf-phone" type="text" placeholder="e.g. 555-867-5309" autocomplete="off">
  </div>
  <div class="lc-clf-field">
    <label class="lc-clf-label">Serial number <span style="color:var(--text-muted);font-size:11px">(optional)</span></label>
    <input class="lc-clf-input" id="clf-serial" type="text" placeholder="e.g. ABC123XYZ" autocomplete="off">
  </div>
  <div class="lc-clf-field">
    <label class="lc-clf-label">Custom line <span style="color:var(--text-muted);font-size:11px">(optional)</span></label>
    <input class="lc-clf-input" id="clf-custom" type="text" placeholder="e.g. Room 204 / Dept. 12" autocomplete="off">
  </div>
  <div class="lc-print-dialog-btns">
    <button class="lc-print-dialog-btn lc-print-dialog-btn--primary" id="clf-submit-btn">Add to position ${pos}</button>
    <button class="lc-print-dialog-btn" id="clf-cancel-btn">Cancel</button>
  </div>
</div>`;

  document.body.appendChild(backdrop);

  const deviceInput  = document.getElementById('clf-device');
  const ownerInput   = document.getElementById('clf-owner');
  const phoneInput   = document.getElementById('clf-phone');
  const serialInput  = document.getElementById('clf-serial');
  const customInput  = document.getElementById('clf-custom');
  const deviceErr    = document.getElementById('clf-device-err');
  const ownerErr     = document.getElementById('clf-owner-err');

  setTimeout(() => deviceInput?.focus(), 40);

  backdrop.addEventListener('click', e => { if (e.target === backdrop) backdrop.remove(); });
  document.getElementById('clf-cancel-btn')?.addEventListener('click', () => backdrop.remove());

  document.getElementById('clf-submit-btn')?.addEventListener('click', async () => {
    const deviceName = deviceInput.value.trim();
    const ownerName  = ownerInput.value.trim();
    const phone      = phoneInput.value.trim() || null;
    const serial     = serialInput.value.trim() || null;
    const customLine = customInput.value.trim() || null;

    let valid = true;
    if (!deviceName) { deviceErr.hidden = false; valid = false; } else { deviceErr.hidden = true; }
    if (!ownerName)  { ownerErr.hidden  = false; valid = false; } else { ownerErr.hidden  = true; }
    if (!valid) return;

    const fakeAsset = {
      id:           -Date.now(),
      name:         deviceName,
      asset_serial: serial,
      custom_line:  customLine,
      customer:     { business_name: ownerName, phone },
    };

    backdrop.remove();
    await addAndAssign(fakeAsset, ownerName, pos);
  });
}

async function updateLabelFields(assetId, { name, serial, customerName, phone, customLine }) {
  const res = await fetch(`/api/label-queue/${assetId}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ asset_name: name, asset_serial: serial ?? null, customer_name: customerName, customer_phone: phone ?? null, custom_line: customLine ?? null }),
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error ?? 'Update failed');
  _queue = data.queue;
  renderAllPages();
}

function _showEditLabelForm(pos, item) {
  document.getElementById('lc-edit-label-backdrop')?.remove();

  const backdrop = document.createElement('div');
  backdrop.id = 'lc-edit-label-backdrop';
  backdrop.className = 'lc-print-dialog-backdrop';

  backdrop.innerHTML = `
<div class="lc-custom-label-form" role="dialog" aria-modal="true" aria-label="Edit label">
  <div class="lc-custom-label-form-header">Edit Label — Position ${pos}</div>
  <div class="lc-clf-field">
    <label class="lc-clf-label">Device name <span class="lc-clf-required">*</span></label>
    <input class="lc-clf-input" id="elf-device" type="text" value="${esc(item.asset_name)}" autocomplete="off">
    <div class="lc-clf-error" id="elf-device-err" hidden>Required</div>
  </div>
  <div class="lc-clf-field">
    <label class="lc-clf-label">Owner name <span class="lc-clf-required">*</span></label>
    <input class="lc-clf-input" id="elf-owner" type="text" value="${esc(item.customer_name)}" autocomplete="off">
    <div class="lc-clf-error" id="elf-owner-err" hidden>Required</div>
  </div>
  <div class="lc-clf-field">
    <label class="lc-clf-label">Phone <span style="color:var(--text-muted);font-size:11px">(optional)</span></label>
    <input class="lc-clf-input" id="elf-phone" type="text" value="${esc(item.customer_phone ?? '')}" autocomplete="off">
  </div>
  <div class="lc-clf-field">
    <label class="lc-clf-label">Serial number <span style="color:var(--text-muted);font-size:11px">(optional)</span></label>
    <input class="lc-clf-input" id="elf-serial" type="text" value="${esc(item.asset_serial ?? '')}" autocomplete="off">
  </div>
  <div class="lc-clf-field">
    <label class="lc-clf-label">Custom line <span style="color:var(--text-muted);font-size:11px">(optional)</span></label>
    <input class="lc-clf-input" id="elf-custom" type="text" value="${esc(item.custom_line ?? '')}" placeholder="e.g. Room 204 / Dept. 12" autocomplete="off">
  </div>
  <div class="lc-print-dialog-btns">
    <button class="lc-print-dialog-btn lc-print-dialog-btn--primary" id="elf-submit-btn">Save changes</button>
    <button class="lc-print-dialog-btn" id="elf-cancel-btn">Cancel</button>
  </div>
</div>`;

  document.body.appendChild(backdrop);

  const deviceInput  = document.getElementById('elf-device');
  const ownerInput   = document.getElementById('elf-owner');
  const phoneInput   = document.getElementById('elf-phone');
  const serialInput  = document.getElementById('elf-serial');
  const customInput  = document.getElementById('elf-custom');
  const deviceErr    = document.getElementById('elf-device-err');
  const ownerErr     = document.getElementById('elf-owner-err');

  setTimeout(() => deviceInput?.focus(), 40);

  backdrop.addEventListener('click', e => { if (e.target === backdrop) backdrop.remove(); });
  document.getElementById('elf-cancel-btn')?.addEventListener('click', () => backdrop.remove());

  document.getElementById('elf-submit-btn')?.addEventListener('click', async () => {
    const deviceName = deviceInput.value.trim();
    const ownerName  = ownerInput.value.trim();
    const phone      = phoneInput.value.trim() || null;
    const serial     = serialInput.value.trim() || null;
    const customLine = customInput.value.trim() || null;

    let valid = true;
    if (!deviceName) { deviceErr.hidden = false; valid = false; } else { deviceErr.hidden = true; }
    if (!ownerName)  { ownerErr.hidden  = false; valid = false; } else { ownerErr.hidden  = true; }
    if (!valid) return;

    backdrop.remove();
    await updateLabelFields(item.asset_id, { name: deviceName, serial, customerName: ownerName, phone, customLine });
  });
}

function closePicker() {
  _pickerTargetPos = null;
  const picker  = document.getElementById('lc-picker');
  if (picker) picker.hidden = true;
  const input   = document.getElementById('lc-picker-search');
  const results = document.getElementById('lc-picker-search-results');
  if (input)   input.value = '';
  if (results) { results.hidden = true; results.innerHTML = ''; }
}

// ── API mutations (all re-render after response) ──────────────────────────────

// Add an asset directly to the queue and immediately assign it to a sheet position.
// Used by the picker's inline search — bypasses the normal "add to queue" flow.
async function addAndAssign(asset, customerName, pos) {
  const name  = customerName ?? asset.customer?.business_name ?? asset.customer?.business_then_name ?? asset.customer?.name ?? '—';
  const phone = asset.contact?.phone ?? asset.customer?.phone ?? null;

  try {
    // 1. Add (or update) the queue entry
    const addRes = await fetch('/api/label-queue', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        asset_id:       asset.id,
        asset_name:     asset.name,
        asset_serial:   asset.asset_serial ?? asset.serial ?? null,
        customer_name:  name,
        customer_phone: phone,
        custom_line:    asset.custom_line ?? null,
      }),
    });
    if (!addRes.ok) {
      const { error } = await addRes.json().catch(() => ({}));
      showToast(error ?? 'Failed to add asset', 'error');
      return;
    }
    const { queue: q1 } = await addRes.json();
    _queue = q1;

    // 2. Assign to the target position
    const patchRes = await fetch(`/api/label-queue/${asset.id}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sheet_position: pos }),
    });
    if (!patchRes.ok) {
      const { error } = await patchRes.json().catch(() => ({}));
      showToast(error ?? 'Failed to assign position', 'error');
      return;
    }
    const { queue: q2 } = await patchRes.json();
    _queue = q2;
    updateBadgeDOM(_queue.length);
    renderQueuePanel();
    renderAllPages();
  } catch (err) {
    showToast('Error: ' + err.message, 'error');
  }
}

async function assignPosition(assetId, pos) {
  try {
    const res = await fetch(`/api/label-queue/${assetId}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sheet_position: pos }),
    });
    if (!res.ok) {
      const { error } = await res.json().catch(() => ({}));
      showToast(error ?? 'Failed to assign position', 'error');
      return;
    }
    const { queue } = await res.json();
    _queue = queue;
    renderQueuePanel();
    renderAllPages();
  } catch (err) {
    showToast('Error assigning position: ' + err.message, 'error');
  }
}

async function unassignPosition(assetId) {
  try {
    const res = await fetch(`/api/label-queue/${assetId}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sheet_position: null }),
    });
    if (!res.ok) {
      showToast('Failed to unassign position', 'error');
      return;
    }
    const { queue } = await res.json();
    _queue = queue;
    renderQueuePanel();
    renderAllPages();
  } catch (err) {
    showToast('Error unassigning position: ' + err.message, 'error');
  }
}

async function handleRemoveItem(assetId) {
  try {
    const res = await fetch(`/api/label-queue/${assetId}`, { method: 'DELETE' });
    if (!res.ok) {
      showToast('Failed to remove item', 'error');
      return;
    }
    const { queue } = await res.json();
    _queue = queue;
    updateBadgeDOM(queue.length);
    renderQueuePanel();
    renderAllPages();
  } catch (err) {
    showToast('Error removing item: ' + err.message, 'error');
  }
}

async function handleClearAll() {
  if (!_queue.length) return;
  try {
    const res = await fetch('/api/label-queue', { method: 'DELETE' });
    if (!res.ok) { showToast('Failed to clear queue', 'error'); return; }
    const { queue } = await res.json();
    _queue = queue;
    updateBadgeDOM(0);
    closePicker();
    renderQueuePanel();
    renderAllPages();
  } catch (err) {
    showToast('Error clearing queue: ' + err.message, 'error');
  }
}

// ── Per-page auto-fill ────────────────────────────────────────────────────────

async function handleAutoFillPage(page) {
  const unassigned = unassignedItems();
  if (!unassigned.length) { showToast('No unassigned labels to auto-fill', 'info'); return; }

  const start = posStart(page);
  const end   = posEnd(page);
  const emptyPositions = [];
  for (let pos = start; pos <= end && emptyPositions.length < unassigned.length; pos++) {
    if (!itemAtPos(pos) && !_usedPositions.has(pos)) emptyPositions.push(pos);
  }

  if (!emptyPositions.length) { showToast(`Page ${page} is full`, 'info'); return; }

  try {
    const results = await Promise.all(emptyPositions.map((pos, i) => {
      const item = unassigned[i];
      if (!item) return Promise.resolve(null);
      return fetch(`/api/label-queue/${item.asset_id}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ sheet_position: pos }),
      }).then(r => r.json());
    }));
    const last = results.filter(Boolean).pop();
    if (last?.queue) { _queue = last.queue; }
    else { const r = await fetch('/api/label-queue'); const { queue } = await r.json(); _queue = queue; }
    renderQueuePanel();
    renderAllPages();
  } catch (err) {
    showToast('Auto-fill error: ' + err.message, 'error');
  }
}

// ── Remove page ───────────────────────────────────────────────────────────────

async function handleRemovePage(page) {
  if (_pageCount <= 1) { showToast('Cannot remove the only page', 'info'); return; }
  const start     = posStart(page);
  const end       = posEnd(page);
  const pageItems = _queue.filter(i => i.sheet_position != null && i.sheet_position >= start && i.sheet_position <= end);

  if (pageItems.length === 0) {
    await _doRemovePage(page, 'unassign');
  } else {
    _showRemovePageDialog(page, pageItems);
  }
}

function _showRemovePageDialog(page, pageItems) {
  const n = pageItems.length;
  const backdrop = document.createElement('div');
  backdrop.className = 'lc-print-dialog-backdrop';
  backdrop.innerHTML = `
<div class="lc-print-dialog" role="dialog" aria-modal="true">
  <div class="lc-print-dialog-title">Page ${page} has ${n} assigned label${n !== 1 ? 's' : ''}</div>
  <div class="lc-print-dialog-body">What would you like to do with the assigned labels before removing this page?</div>
  <div class="lc-print-dialog-actions">
    <button class="lc-print-dialog-btn" id="lc-rp-move">
      <div class="lc-print-dialog-btn-label">
        Yes, keep their positions
        <div class="lc-print-dialog-btn-desc">Move labels to the same spot on another page if available, otherwise place in next open position</div>
      </div>
    </button>
    <button class="lc-print-dialog-btn" id="lc-rp-unassign">
      <div class="lc-print-dialog-btn-label">
        No, put back in queue
        <div class="lc-print-dialog-btn-desc">Unassign them — they'll remain in the label queue for later</div>
      </div>
    </button>
    <button class="lc-print-dialog-btn" id="lc-rp-cancel">
      <div class="lc-print-dialog-btn-label">Cancel</div>
    </button>
  </div>
</div>`;
  document.body.appendChild(backdrop);
  const dismiss = () => backdrop.remove();
  backdrop.addEventListener('click', e => { if (e.target === backdrop) dismiss(); });
  document.getElementById('lc-rp-move')?.addEventListener('click', async () => { dismiss(); await _doRemovePage(page, 'move'); });
  document.getElementById('lc-rp-unassign')?.addEventListener('click', async () => { dismiss(); await _doRemovePage(page, 'unassign'); });
  document.getElementById('lc-rp-cancel')?.addEventListener('click', dismiss);
}

async function _doRemovePage(page, action) {
  const start     = posStart(page);
  const end       = posEnd(page);
  const pageItems = _queue.filter(i => i.sheet_position != null && i.sheet_position >= start && i.sheet_position <= end);

  // Record the local positions (1-30) of each displaced item so we can try to preserve them
  const localPosMap = new Map(pageItems.map(i => [i.asset_id, i.sheet_position - start + 1]));

  try {
    // Step 1: unassign all items on this page
    if (pageItems.length > 0) {
      await Promise.all(pageItems.map(item =>
        fetch(`/api/label-queue/${item.asset_id}`, {
          method: 'PATCH', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ sheet_position: null }),
        })
      ));
    }

    // Step 2: shift items on pages above this one down by activeSheet().pageSize (sequential, ascending)
    const r0 = await fetch('/api/label-queue');
    const { queue: q0 } = await r0.json();
    const aboveItems = q0
      .filter(i => i.sheet_position != null && i.sheet_position > end)
      .sort((a, b) => a.sheet_position - b.sheet_position);
    for (const item of aboveItems) {
      await fetch(`/api/label-queue/${item.asset_id}`, {
        method: 'PATCH', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ sheet_position: item.sheet_position - activeSheet().pageSize }),
      });
    }

    // Step 3: shift used positions in localStorage
    const newUsed = new Set();
    for (const pos of _usedPositions) {
      if (pos < start) newUsed.add(pos);
      else if (pos > end) newUsed.add(pos - activeSheet().pageSize);
    }
    _usedPositions = newUsed;
    _saveUsedPositions();

    // Step 4: decrement page count
    _pageCount--;
    _savePageCount();

    // Step 5: re-fetch current queue state
    const r1 = await fetch('/api/label-queue');
    const { queue: q1 } = await r1.json();
    _queue = q1;

    // Step 6: if 'move', place displaced items preserving local position where possible
    if (action === 'move' && pageItems.length > 0) {
      const unassigned = _queue.filter(i => i.sheet_position == null);
      const displaced  = pageItems.map(orig => unassigned.find(u => u.asset_id === orig.asset_id)).filter(Boolean);

      if (displaced.length > 0) {
        const maxPos = _pageCount * activeSheet().pageSize;
        const taken  = () => new Set(_queue.map(i => i.sheet_position).filter(Boolean));

        for (const item of displaced) {
          const localPos = localPosMap.get(item.asset_id); // 1-30 within original page
          let targetPos = null;

          // Try each remaining page at the same local slot
          for (let p = 1; p <= _pageCount; p++) {
            const candidate = posStart(p) + localPos - 1;
            if (candidate <= posEnd(p) && !taken().has(candidate) && !_usedPositions.has(candidate)) {
              targetPos = candidate;
              break;
            }
          }

          // Fallback: first available position anywhere
          if (!targetPos) {
            for (let pos = 1; pos <= maxPos; pos++) {
              if (!taken().has(pos) && !_usedPositions.has(pos)) { targetPos = pos; break; }
            }
          }

          if (targetPos) {
            const res = await fetch(`/api/label-queue/${item.asset_id}`, {
              method: 'PATCH', headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ sheet_position: targetPos }),
            });
            const data = await res.json();
            if (data?.queue) _queue = data.queue; // keep queue fresh for taken() calls
          }
        }

        const r2 = await fetch('/api/label-queue');
        const { queue: q2 } = await r2.json();
        _queue = q2;
      }
    }

    renderQueuePanel();
    renderAllPages();
    showToast(`Page ${page} removed`, 'success');
  } catch (err) {
    showToast('Error removing page: ' + err.message, 'error');
  }
}

async function handleAutoFill() {
  const unassigned = unassignedItems();
  if (!unassigned.length) {
    showToast('No unassigned labels to auto-fill', 'info');
    return;
  }

  // Find empty positions across all pages in order, skipping used ones
  const maxPos = _pageCount * activeSheet().pageSize;
  const emptyPositions = [];
  for (let pos = 1; pos <= maxPos && emptyPositions.length < unassigned.length; pos++) {
    if (!itemAtPos(pos) && !_usedPositions.has(pos)) emptyPositions.push(pos);
  }

  if (!emptyPositions.length) {
    showToast('Sheet is full', 'info');
    return;
  }

  // Send all PATCH requests, then apply the last response state
  try {
    const promises = emptyPositions.map((pos, i) => {
      const item = unassigned[i];
      if (!item) return Promise.resolve(null);
      return fetch(`/api/label-queue/${item.asset_id}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ sheet_position: pos }),
      }).then(r => r.json());
    });

    const results = await Promise.all(promises);
    // Use the last successful queue state
    const last = results.filter(Boolean).pop();
    if (last?.queue) {
      _queue = last.queue;
    } else {
      // Fallback: re-fetch
      const r = await fetch('/api/label-queue');
      const { queue } = await r.json();
      _queue = queue;
    }
    renderQueuePanel();
    renderAllPages();
  } catch (err) {
    showToast('Auto-fill error: ' + err.message, 'error');
  }
}

async function handleResetPage(page) {
  const start    = posStart(page);
  const end      = posEnd(page);
  const assigned = _queue.filter(i => i.sheet_position != null && i.sheet_position >= start && i.sheet_position <= end);
  if (!assigned.length) { showToast(`Page ${page} is already empty`, 'info'); return; }

  try {
    const results = await Promise.all(assigned.map(item =>
      fetch(`/api/label-queue/${item.asset_id}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ sheet_position: null }),
      }).then(r => r.json())
    ));
    const last = results.filter(Boolean).pop();
    if (last?.queue) {
      _queue = last.queue;
    } else {
      const r = await fetch('/api/label-queue');
      const { queue } = await r.json();
      _queue = queue;
    }
    renderQueuePanel();
    renderAllPages();
    showToast(`Page ${page} reset`, 'success');
  } catch (err) {
    showToast('Error resetting page: ' + err.message, 'error');
  }
}

async function handleResetSheet(silent = false) {
  const assigned = assignedItems();
  if (!assigned.length) {
    if (!silent) showToast('Sheet is already empty', 'info');
    return;
  }
  if (!silent && !confirm('Reset the sheet? All label positions will be cleared. Labels will stay in the queue.')) return;

  try {
    const res = await fetch('/api/label-queue', {
      method: 'DELETE',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ reset_sheet_only: true }),
    });
    if (!res.ok) { showToast('Failed to reset sheet', 'error'); return; }
    const { queue } = await res.json();
    _queue = queue;
    _usedPositions.clear();
    _saveUsedPositions();
    _pageCount = 1;
    _savePageCount();
    closePicker();
    renderQueuePanel();
    renderAllPages();
    showToast('Sheet reset — labels are still queued', 'success');
  } catch (err) {
    showToast('Error resetting sheet: ' + err.message, 'error');
  }
}

// ── Print sheet ───────────────────────────────────────────────────────────────

function _openPrintWindow(name, items) {
  const win = window.open('', name,
    'width=960,height=800,menubar=no,toolbar=no,location=no,status=no,scrollbars=yes');
  if (!win) { alert('Popup blocked — please allow popups for this site.'); return null; }
  win.document.write(items);
  win.document.close();
  return win;
}

function _watchPrintWindow(win, onClose) {
  let count = 0;
  const poll = setInterval(() => {
    if (win.closed || ++count >= 180) {
      clearInterval(poll);
      if (win.closed) onClose();
    }
  }, 1000);
}

function handlePrintPage(page) {
  const start    = posStart(page);
  const end      = posEnd(page);
  const assigned = _queue.filter(i => i.sheet_position != null && i.sheet_position >= start && i.sheet_position <= end);
  if (!assigned.length) { showToast('No labels on this page', 'info'); return; }

  // Normalize positions to 1–30 within this page for the print layout
  const normalized = assigned.map(i => ({ ...i, sheet_position: i.sheet_position - start + 1 }));
  const win = _openPrintWindow(`lc_print_p${page}`, buildSheetHTMLMulti(normalized));
  if (win) _watchPrintWindow(win, () => _showPostPrintDialog(page));
}

function handlePrintAll() {
  const assigned = assignedItems();
  if (!assigned.length) { showToast('No labels assigned to the sheet yet', 'info'); return; }

  // Build one HTML document with all pages, each as a separate .page div
  const pageGroups = [];
  for (let p = 1; p <= _pageCount; p++) {
    const start = posStart(p);
    const end   = posEnd(p);
    const items = assigned
      .filter(i => i.sheet_position >= start && i.sheet_position <= end)
      .map(i => ({ ...i, sheet_position: i.sheet_position - start + 1 }));
    if (items.length) pageGroups.push({ page: p, items });
  }

  const win = _openPrintWindow('lc_print_all', buildAllPagesHTML(pageGroups));
  if (win) _watchPrintWindow(win, () => _showPostPrintDialog(null));
}

function _showSheetChangeConfirm(newKey) {
  return new Promise(resolve => {
    const newLabel = SHEET_CONFIGS[newKey]?.label ?? newKey;
    const backdrop = document.createElement('div');
    backdrop.className = 'lc-print-dialog-backdrop';
    backdrop.innerHTML = `
<div class="lc-print-dialog" role="dialog" aria-modal="true" aria-labelledby="lc-sc-title">
  <div class="lc-print-dialog-title" id="lc-sc-title">Change sheet type?</div>
  <div class="lc-print-dialog-body">Switching to <strong>${esc(newLabel)}</strong> will clear all position assignments. Labels will remain in the queue.</div>
  <div class="lc-print-dialog-actions">
    <button class="lc-print-dialog-btn" id="lc-sc-confirm">
      <div class="lc-print-dialog-btn-label">
        Switch sheet type
        <div class="lc-print-dialog-btn-desc">Clear position assignments and switch</div>
      </div>
    </button>
    <button class="lc-print-dialog-btn" id="lc-sc-cancel">
      <div class="lc-print-dialog-btn-label">
        Cancel
        <div class="lc-print-dialog-btn-desc">Keep current sheet type</div>
      </div>
    </button>
  </div>
</div>`;
    document.body.appendChild(backdrop);
    const dismiss = ok => { backdrop.remove(); resolve(ok); };
    backdrop.querySelector('#lc-sc-confirm').addEventListener('click', () => dismiss(true));
    backdrop.querySelector('#lc-sc-cancel').addEventListener('click', () => dismiss(false));
    backdrop.addEventListener('click', e => { if (e.target === backdrop) dismiss(false); });
  });
}

function _showPostPrintDialog(page) {
  const isAll  = page === null;
  const title  = isAll ? 'All pages printed?' : `Page ${page} printed?`;
  const sheetLabel = isAll ? 'Clear all sheet layouts' : `Clear page ${page} layout`;
  const sheetDesc  = isAll
    ? 'Remove all position assignments — labels stay in the queue'
    : `Remove position assignments for page ${page} — labels stay in the queue`;

  const backdrop = document.createElement('div');
  backdrop.className = 'lc-print-dialog-backdrop';
  backdrop.innerHTML = `
<div class="lc-print-dialog" role="dialog" aria-modal="true" aria-labelledby="lc-pd-title">
  <div class="lc-print-dialog-title" id="lc-pd-title">${esc(title)}</div>
  <div class="lc-print-dialog-body">What would you like to do with the queued labels and sheet layout?</div>
  <div class="lc-print-dialog-actions">
    <button class="lc-print-dialog-btn" id="lc-pd-nothing">
      <div class="lc-print-dialog-btn-label">
        Keep everything
        <div class="lc-print-dialog-btn-desc">Leave the queue and sheet as-is</div>
      </div>
    </button>
    <button class="lc-print-dialog-btn" id="lc-pd-clear-sheet">
      <div class="lc-print-dialog-btn-label">
        ${esc(sheetLabel)}
        <div class="lc-print-dialog-btn-desc">${esc(sheetDesc)}</div>
      </div>
    </button>
    <button class="lc-print-dialog-btn danger" id="lc-pd-clear-queue">
      <div class="lc-print-dialog-btn-label">
        Clear everything
        <div class="lc-print-dialog-btn-desc">Remove all labels from the queue and reset the sheet</div>
      </div>
    </button>
  </div>
</div>`;

  document.body.appendChild(backdrop);
  const dismiss = () => backdrop.remove();
  backdrop.addEventListener('click', e => { if (e.target === backdrop) dismiss(); });

  document.getElementById('lc-pd-nothing')?.addEventListener('click', dismiss);

  document.getElementById('lc-pd-clear-sheet')?.addEventListener('click', async () => {
    dismiss();
    if (isAll) {
      await handleResetSheet(true);
    } else {
      await handleResetPage(page);
    }
  });

  document.getElementById('lc-pd-clear-queue')?.addEventListener('click', async () => {
    dismiss();
    await handleClearAll();
  });
}

function buildAllPagesHTML(pageGroups) {
  // Build the inner label HTML for each page group, then wrap in a single print document
  // Each group's items are already normalized to positions 1–30

  const allBarcodes = [];
  const pageDivs    = [];

  for (const { page, items } of pageGroups) {
    const labels = _buildLabelData(items, page);
    allBarcodes.push(...labels.filter(l => l.hasSn).map(l => [l.bcId, l.serial]));
    const isFirst = pageDivs.length === 0;
    pageDivs.push(`<div class="page${isFirst ? '' : ' page-break'}">\n${labels.map(l => l.slDiv).join('\n')}\n</div>`);
  }

  return _buildPrintDocument(pageDivs.join('\n'), JSON.stringify(allBarcodes),
    `${pageGroups.reduce((n, g) => n + g.items.length, 0)} label(s) across ${pageGroups.length} page(s)`);
}

function buildSheetHTMLMulti(items) {
  // items: normalized to positions 1–30
  const labels      = _buildLabelData(items, 1);
  const barcodeArray = JSON.stringify(labels.filter(l => l.hasSn).map(l => [l.bcId, l.serial]));
  const pageDiv      = `<div class="page">\n${labels.map(l => l.slDiv).join('\n')}\n</div>`;
  return _buildPrintDocument(pageDiv, barcodeArray,
    `${items.length} label${items.length !== 1 ? 's' : ''}`);
}

function _buildLabelData(items, pageNum) {
  const sheet = activeSheet();
  return items.map(item => {
    const colIdx = (item.sheet_position - 1) % sheet.cols;
    const rowIdx = Math.floor((item.sheet_position - 1) / sheet.cols);
    const leftIn = sheet.colOffsets[colIdx];
    const topIn  = sheet.rowOffsets[rowIdx];
    const bcId   = `sbc-${item.asset_id}-${pageNum}-${item.sheet_position}`;

    if (_sheetTypeKey === 'OL25LP') {
      return _buildOL25LPLabelData(item, leftIn, topIn, bcId);
    }

    const name          = item.asset_name ?? 'Unknown Asset';
    const serial        = item.asset_serial ?? '';
    const hasSn         = serial.length > 0;
    const customerName  = item.customer_name ?? '';
    const customerPhone = formatPhone(item.customer_phone ?? '');
    const customLine    = item.custom_line ?? '';

    const snBarcode = hasSn
      ? `<div class="lbc"><img id="${bcId}" class="sbc-bc" alt="barcode"><div class="sbc-cap"><span>SN: ${esc(serial)}</span></div></div>`
      : '';

    const slDiv = `  <div class="sl" style="left:${leftIn}in;top:${topIn}in">
    <div class="sl-hdr">
      <div class="sl-hdr-left">
        <img src="/assets/logo-swirl.png" class="sl-logo" alt="">
        <span class="sl-co">deRenzy Business Technologies</span>
      </div>
      <span class="sl-phone">(413) 739-4706</span>
    </div>
    <div class="sl-body">
      <div class="sl-text">
        <div class="sl-name">${esc(name)}</div>
        ${customerName  ? `<div class="sl-info">${esc(customerName)}</div>`  : ''}
        ${customerPhone ? `<div class="sl-info">${esc(customerPhone)}</div>` : ''}
        ${customLine    ? `<div class="sl-info">${esc(customLine)}</div>`    : ''}
      </div>
      ${snBarcode}
    </div>
  </div>`;

    return { slDiv, hasSn, serial, bcId };
  });
}

function _buildOL25LPLabelData(item, leftIn, topIn, bcId) {
  const serial = item.asset_serial ?? '';
  const hasSn  = serial.length > 0;

  const slDiv = `  <div class="sl sl-ol25lp" style="left:${leftIn}in;top:${topIn}in">
    <div class="sl25-wrap">
      ${hasSn ? `<img id="${bcId}" class="sl25-bc" alt="">` : ''}
      ${hasSn ? `<div class="sl25-sn"><span>${esc(serial)}</span></div>` : ''}
    </div>
  </div>`;

  return { slDiv, hasSn, serial, bcId };
}

function _buildPrintDocument(pagesHTML, barcodeArrayJSON, titleSuffix) {
  return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Label Sheet \u2014 ${titleSuffix}</title>
  <script src="/vendor/JsBarcode.all.min.js"><\/script>
  <style>
    @page { size: letter; margin: 0; }
    html, body { margin: 0; padding: 0; }

    @media screen {
      body { background: #9ca3af; font-family: 'Segoe UI', Arial, sans-serif; }
      .toolbar { background: #1a3565; color: #fff; padding: 10px 20px;
                 display: flex; align-items: center; justify-content: space-between; gap: 12px;
                 font-size: .85rem; }
      .toolbar-info { opacity: .8; }
      .toolbar-btns { display: flex; gap: 8px; }
      .toolbar-btn { background: none; border: 1px solid rgba(255,255,255,.4); color: #fff;
                     border-radius: 4px; padding: 5px 14px; cursor: pointer; font-size: .82rem; }
      .toolbar-btn:hover { background: rgba(255,255,255,.15); }
      .toolbar-btn.primary { background: #C4622A; border-color: #C4622A; }
      .toolbar-btn.primary:hover { background: #a3501f; }
      .page { background: white; width: 8.5in; min-height: 11in; margin: 16px auto;
              position: relative; box-shadow: 0 4px 20px rgba(0,0,0,.2); }
      .page-break { margin-top: 32px; }
      .sl { outline: 2px dashed rgba(196,98,42,.4); outline-offset: -1px; }
    }

    @media print {
      body { background: white; }
      .toolbar { display: none; }
      .page { width: 8.5in; min-height: 11in; margin: 0; box-shadow: none; }
      .page-break { page-break-before: always; }
      .sl { outline: none; }
    }

    .page { position: relative; }
    .sl {
      position: absolute;
      width: 2.625in;
      height: 1in;
      overflow: hidden;
      box-sizing: border-box;
      padding: 0.05in 0.07in;
      display: flex;
      flex-direction: column;
      font-family: 'Segoe UI', Arial, sans-serif;
    }
    .sl-hdr {
      display: flex; align-items: center; justify-content: space-between;
      border-bottom: 0.5pt solid #ddd;
      padding-bottom: 1.5pt; margin-bottom: 1.5pt; flex-shrink: 0;
    }
    .sl-hdr-left { display: flex; align-items: center; gap: 2.5pt; min-width: 0; }
    .sl-logo  { height: 0.14in; width: auto; }
    .sl-co    { font-size: 5.5pt; font-weight: 700; color: #1a3565;
                text-transform: uppercase; letter-spacing: .04em; white-space: nowrap; }
    .sl-phone { font-size: 5.5pt; font-weight: 700; color: #1a3565; white-space: nowrap; }
    .sl-body  { display: flex; align-items: center; gap: 3pt;
                flex: 1; min-height: 0; overflow: hidden; }
    .sl-text  { flex: 1; display: flex; flex-direction: column;
                justify-content: center; min-width: 0; overflow: hidden; }
    .sl-name  { font-size: 10pt; font-weight: 700; color: #111;
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .sl-info  { font-size: 7pt; color: #333; overflow: hidden; margin-top: 1pt;
                display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
    .lbc      { position: relative; flex-shrink: 0; width: 45%; align-self: stretch;
                overflow: hidden; }
    .sbc-bc   { width: 100%; height: 100%; object-fit: fill; display: block;
                image-rendering: pixelated; border-bottom-right-radius: 7px; }
    .sbc-cap  { position: absolute; bottom: 1.5pt; left: 0; right: 0; text-align: center; }
    .sbc-cap span { display: inline-block; font-size: 5pt; color: #444; font-weight: 600;
                    font-family: monospace; background: #fff; padding: 0.5pt 2pt; }
    /* OL25LP \u2014 1.75\u2033 \xd7 0.5\u2033 minimal barcode label */
    .sl-ol25lp { width: 1.75in; height: 0.5in; padding: 0; overflow: hidden; }
    .sl25-wrap { position: relative; width: 100%; height: 100%; }
    .sl25-bc   { width: 100%; height: 100%; object-fit: fill; display: block;
                 image-rendering: pixelated; }
    .sl25-sn   { position: absolute; bottom: 1.5pt; left: 0; right: 0;
                 text-align: center; pointer-events: none; }
    .sl25-sn span { display: inline-block; font-size: 4.5pt; font-weight: 700;
                    font-family: monospace; color: #000; background: #fff;
                    padding: 0.5pt 2.5pt; line-height: 1.1; white-space: nowrap;
                    border: 0.5pt solid #fff; }
  </style>
</head>
<body>
<div class="toolbar">
  <span class="toolbar-info">Label Sheet \u2014 ${titleSuffix}</span>
  <div class="toolbar-btns">
    <button class="toolbar-btn primary" onclick="window.print()">\uD83D\uDDA8 Print</button>
    <button class="toolbar-btn" onclick="window.close()">Close</button>
  </div>
</div>
${pagesHTML}
<script>
  var barcodes = ${barcodeArrayJSON};
  var printed  = false;
  function doPrint() { if (!printed) { printed = true; window.print(); } }

  document.addEventListener('DOMContentLoaded', function() {
    if (!barcodes.length) { setTimeout(doPrint, 300); return; }

    if (typeof JsBarcode !== 'undefined') {
      barcodes.forEach(function(b) {
        var c = document.createElement('canvas');
        JsBarcode(c, b[1], { format:'CODE128', width:4, height:400, displayValue:false, margin:0 });
        var img = document.getElementById(b[0]);
        if (img) img.src = c.toDataURL('image/png');
      });
    }

    var imgs    = document.querySelectorAll('.sbc-bc, .sl25-bc');
    var pending = imgs.length;
    if (!pending) { setTimeout(doPrint, 200); return; }

    function onLoad() { if (--pending <= 0) setTimeout(doPrint, 100); }
    imgs.forEach(function(img) {
      if (img.complete && img.naturalWidth > 0) { pending--; }
      else {
        img.addEventListener('load',  onLoad, { once: true });
        img.addEventListener('error', onLoad, { once: true });
      }
    });
    if (pending <= 0) setTimeout(doPrint, 100);
    else setTimeout(doPrint, 800);
  });
<\/script>
</body>
</html>`;
}

// ── Badge ─────────────────────────────────────────────────────────────────────

function updateBadgeDOM(count) {
  const badge = document.getElementById('lc-badge');
  if (!badge) return;
  badge.textContent = count;
  badge.hidden = count === 0;
}

// ── Icons ─────────────────────────────────────────────────────────────────────

function iconQueuePlus() {
  return `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <rect x="3" y="3" width="18" height="18" rx="2"/>
    <line x1="12" y1="8" x2="12" y2="16"/>
    <line x1="8" y1="12" x2="16" y2="12"/>
  </svg>`;
}

// Make iconQueuePlus available for assetCard.js to import
export { iconQueuePlus };

// ── Utils ─────────────────────────────────────────────────────────────────────

function esc(str) {
  if (!str) return '';
  return String(str)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}
