// New POS logic for CNPERP
// Contract:
// - Uses existing endpoints:
//   GET /api/v1/settings/
//   GET /api/v1/pos/sessions?status=open&branch_id=...
//   POST /api/v1/pos/sessions/open
//   GET /api/v1/pos/products?branch_id=...&search=...
//   GET /api/v1/pos/customers?branch_id=...&search=...
//   GET /api/v1/banking/accounts?branch_id=...
//   GET /api/v1/pos/branch-defaults/{branch_id}/card-bank
//   GET /api/v1/pos/defaults/card-bank
//   POST /api/v1/pos/sales
//   POST /api/v1/pos/sessions/close

(function () {
  // State
  let sessionId = null;
  let user = null;
  let branchId = null;
  let isHeadOffice = false;  // Track if current branch is HQ
  let appSettings = { currency: 'BWP', currencySymbol: 'P', vatRate: 14.0, locale: 'en-BW', timezone: 'Africa/Gaborone', companyName: 'CNPERP' };
  let products = [];
  let customers = [];
  let cart = [];
  let selectedCustomer = null;
  let paymentMethod = 'cash';
  let bankAccounts = [];
  let defaultCardBankId = null;
  let recentReceipts = [];
  let receiptsModal = null;
  let selectedQuotation = null;
  let includeZeroStock = false;
  let branchesList = [];
  let posPaperFormats = [];
  let defaultPaperFormat = '80mm';

  // Quotation mode state
  let isQuotationMode = false;
  let quoteItems = [];
  let quoteCustomer = {};
  let quoteNotes = '';

  // Utils
  const authHeader = () => (window.auth && window.auth.authHeader ? window.auth.authHeader() : {});
  const getUser = () => (window.auth && auth.isAuthenticated() ? auth.getUser() : null);
  const getBranchId = () => {
    try { const u = getUser(); if (u && (u.branch_id || u.branchId)) return u.branch_id || u.branchId; } catch { }
    return localStorage.getItem('selected_branch_id') || localStorage.getItem('branch_id');
  };
  const fmt = (n) => {
    try { return new Intl.NumberFormat(appSettings.locale, { style: 'currency', currency: appSettings.currency }).format(n); }
    catch { return (appSettings.currencySymbol || 'P') + Number(n || 0).toFixed(2); }
  };

  // Header
  function updateDateTime() {
    const now = new Date();
    const el = document.getElementById('dateTime'); if (el) el.textContent = now.toLocaleString();
  }

  async function resolveAndSetBranchDisplay() {
    try {
      const el = document.getElementById('branchName');
      if (!el) return;
      // Prefer explicit user metadata if available
      const u = getUser();
      const code = u?.branch_code || u?.branchCode;
      const name = u?.branch_name || u?.branchName;
      if (code || name) {
        el.textContent = name ? (code ? `${code} - ${name}` : name) : code;
        return;
      }
      // Fallback: fetch public branches and resolve by ID
      if (!branchId) { el.textContent = 'Unknown'; return; }
      const r = await fetch('/api/v1/branches/public', { headers: authHeader() });
      if (r.ok) {
        const list = await r.json();
        const br = Array.isArray(list) ? list.find(b => String(b.id) === String(branchId)) : null;
        if (br) {
          const label = br.code ? (br.name ? `${br.code} - ${br.name}` : br.code) : (br.name || branchId);
          el.textContent = label;
          isHeadOffice = br.is_head_office || br.is_hq || false;  // Set HQ flag
          return;
        }
      }
      // Last resort: show truncated ID
      el.textContent = String(branchId || '').substring(0, 8) || 'Unknown';
    } catch {
      const el = document.getElementById('branchName');
      if (el) el.textContent = String(branchId || 'Unknown');
    }
  }

  async function ensureBranchContext() {
    if (branchId) return;
    try {
      const r = await fetch('/api/v1/branches/public', { headers: authHeader() });
      if (r.ok) {
        const list = await r.json();
        const first = Array.isArray(list) && list.length ? list[0] : null;
        if (first && first.id) {
          branchId = first.id;
          isHeadOffice = first.is_head_office || first.is_hq || false;  // Set HQ flag
          try { localStorage.setItem('selected_branch_id', branchId); } catch { }
        }
      }
    } catch { }
  }

  async function loadAppSettings() {
    try {
      const r = await fetch('/api/v1/settings/', { headers: authHeader() });
      if (r.ok) {
        const j = await r.json();
        const d = j.data || {};
        const cur = d.currency || {};
        appSettings = {
          currency: cur.currency || 'BWP',
          currencySymbol: cur.currency_symbol || 'P',
          vatRate: cur.vat_rate || 14.0,
          locale: cur.locale || 'en-BW',
          timezone: cur.timezone || 'Africa/Gaborone',
          companyName: (d.business && d.business.company_name) || 'CNPERP'
        };
      }
    } catch { }
    const brand = document.getElementById('brandName'); if (brand) brand.textContent = 'CNP ERP POINT OF SALE';
  }

  // Session
  function showToast(message, variant = 'success') {
    try {
      const container = document.getElementById('toastContainer');
      if (!container) return;
      const id = 't_' + Date.now();
      const bg = variant === 'success' ? 'bg-success' : (variant === 'warning' ? 'bg-warning text-dark' : 'bg-danger');
      const el = document.createElement('div');
      el.className = `toast align-items-center text-white border-0 ${bg}`;
      el.id = id;
      el.role = 'alert'; el.ariaLive = 'assertive'; el.ariaAtomic = 'true';
      el.innerHTML = `<div class="d-flex"><div class="toast-body">${message}</div><button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button></div>`;
      container.appendChild(el);
      const t = new bootstrap.Toast(el, { delay: 2500 }); t.show();
      el.addEventListener('hidden.bs.toast', () => el.remove());
    } catch { }
  }

  async function ensureSession() {
    user = getUser();
    branchId = getBranchId();
    if (!branchId) { await ensureBranchContext(); }
    const cashierName = document.getElementById('cashierName');
    if (cashierName) cashierName.textContent = user ? (user.first_name ? `${user.first_name} ${user.last_name || ''}`.trim() : (user.username || 'User')) : 'Guest';
    await resolveAndSetBranchDisplay();

    // Fetch branch info to determine if HQ
    try {
      if (branchId) {
        const r = await fetch(`/api/v1/branches/${branchId}`, { headers: authHeader() });
        if (r.ok) {
          const br = await r.json();
          isHeadOffice = br.is_head_office || br.is_hq || false;
        }
      }
    } catch { }

    // try reuse
    try {
      // localStorage cache first
      const cached = localStorage.getItem('current_pos_session_id');
      if (cached) { sessionId = cached; }

      const q = new URL('/api/v1/pos/sessions', window.location.origin);
      q.searchParams.set('status', 'open'); if (branchId) q.searchParams.set('branch_id', branchId);
      const r = await fetch(q.toString(), { headers: authHeader() });
      if (r.ok) {
        const j = await r.json();
        const list = Array.isArray(j) ? j : (j.data || j.value || []);
        const mine = list.find && user ? list.find(s => String(s.user_id) === String(user.id)) : null;
        if (mine && mine.id) { sessionId = mine.id; localStorage.setItem('current_pos_session_id', sessionId); showToast('Reusing open POS session', 'success'); return; }
        // if we had a cached id but server doesn't list it (closed), clear it
        if (cached && (!mine || mine.id !== cached)) { localStorage.removeItem('current_pos_session_id'); }
      }
    } catch { }
    // open
    try {
      if (!user) { alert('Please login to open a POS session.'); return; }
      if (!branchId) { alert('Branch not resolved for POS session.'); return; }
      const payload = { user_id: user.id, branch_id: branchId, till_id: `TILL_${user.id}_${Date.now()}`, float_amount: 0 };
      const r2 = await fetch('/api/v1/pos/sessions/open', { method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeader() }, body: JSON.stringify(payload) });
      if (r2.ok) { const j2 = await r2.json(); sessionId = j2?.data?.session_id; if (sessionId) { localStorage.setItem('current_pos_session_id', sessionId); showToast('Session opened', 'success'); } }
      else {
        try { const err = await r2.json(); alert('Could not open POS session: ' + (err.detail || r2.statusText)); } catch { alert('Could not open POS session.'); }
      }
    } catch { }
  }

  // Products
  async function loadProducts(search) {
    try {
      const url = new URL('/api/v1/pos/products', window.location.origin);
      if (branchId) url.searchParams.set('branch_id', branchId);
      if (includeZeroStock) url.searchParams.set('include_zero_stock', 'true');
      if (search && String(search).trim()) url.searchParams.set('search', String(search).trim());
      console.log('[POS] Loading products:', url.toString());
      const r = await fetch(url.toString(), { headers: authHeader() });
      console.log('[POS] Products response:', r.status, r.ok);
      if (r.ok) {
        const j = await r.json();
        products = j.data || j.value || j || [];
        console.log('[POS] Products loaded:', products.length);
        renderProducts();
      } else {
        const errorText = await r.text();
        console.error('[POS] Failed to load products:', r.status, errorText);
        showToast('Failed to load products: ' + r.statusText, 'danger');
      }
    } catch (e) {
      console.error('[POS] Error loading products:', e);
      showToast('Error loading products: ' + e.message, 'danger');
    }
  }
  // Category state
  let selectedCategory = null;

  function renderCategoryButtons() {
    const categories = new Set(products.map(p => p.category).filter(c => c));
    const container = document.getElementById('categoryButtons');
    if (!container) return;
    container.innerHTML = '';

    categories.forEach(cat => {
      const btn = document.createElement('button');
      btn.type = 'button';
      btn.className = 'pos-cat-btn';
      btn.textContent = cat;
      btn.onclick = () => filterByCategory(cat);
      container.appendChild(btn);
    });
  }

  function filterByCategory(category) {
    selectedCategory = category;
    const catAll = document.getElementById('catAll');
    if (catAll) catAll.classList.remove('pos-cat-active');

    document.querySelectorAll('#categoryButtons button').forEach(btn => {
      btn.classList.remove('pos-cat-active');
    });

    if (selectedCategory) {
      const btn = Array.from(document.querySelectorAll('#categoryButtons button')).find(b => b.textContent === selectedCategory);
      if (btn) btn.classList.add('pos-cat-active');
    }
    renderProducts();
  }

  function showAllCategories() {
    selectedCategory = null;
    const catAll = document.getElementById('catAll');
    if (catAll) catAll.classList.add('pos-cat-active');

    document.querySelectorAll('#categoryButtons button').forEach(btn => {
      btn.classList.remove('pos-cat-active');
    });
    renderProducts();
  }

  function renderProducts() {
    const grid = document.getElementById('productsGrid'); if (!grid) return; grid.innerHTML = '';

    let filtered = products;
    if (selectedCategory) {
      filtered = products.filter(p => p.category === selectedCategory);
    }

    if (!filtered.length) { grid.innerHTML = '<div class="text-center text-secondary py-5">No products found</div>'; return; }

    filtered.forEach(p => {
      const col = document.createElement('div');
      col.className = 'pos-product-cell';
      const card = document.createElement('div');
      card.className = 'product-card h-100';

      // Stock indicator badge
      const stockBadge = document.createElement('div');
      stockBadge.className = 'position-absolute top-0 end-0 m-2';
      const localQty = Number(p.local_quantity ?? p.quantity ?? 0);
      const otherQty = Number(p.other_branches_quantity || 0);
      if (localQty > 0) {
        stockBadge.innerHTML = `<span class="badge bg-success" title="${localQty} at this branch" style="font-size: 0.9rem; padding: 0.5rem 0.75rem;">${localQty}</span>`;
      } else if (otherQty > 0) {
        stockBadge.innerHTML = `<span class="badge bg-warning text-dark" title="${otherQty} at other branches — tap to choose source" style="cursor:pointer; font-size: 0.8rem;" onclick="event.stopPropagation(); showBranchAvailability('${p.id}', '${escapeAttr(p.name)}')"><i class="bi bi-geo-alt"></i> ${otherQty}</span>`;
      }
      card.appendChild(stockBadge);

      const name = document.createElement('div');
      name.className = 'product-name';
      name.textContent = p.name;

      const price = document.createElement('div');
      price.className = 'product-price';
      price.textContent = fmt(p.selling_price);

      const stock = document.createElement('div');
      stock.className = 'product-stock';
      if (localQty > 0) {
        stock.textContent = `${localQty} at this branch`;
        stock.style.color = 'var(--text-secondary)';
      } else if (otherQty > 0) {
        stock.textContent = `0 here · ${otherQty} at other branch(es)`;
        stock.style.color = '#f59e0b';
      } else {
        stock.textContent = 'Out of stock';
        stock.style.color = '#ef4444';
      }

      const sku = document.createElement('div');
      sku.className = 'product-sku';
      sku.textContent = p.sku || 'No SKU';

      // Brand / condition badge row
      const meta = document.createElement('div');
      meta.className = 'product-sku';
      const metaParts = [p.brand, p.condition].filter(Boolean);
      if (metaParts.length) meta.textContent = metaParts.join(' · ');

      const addBtn = document.createElement('button');
      addBtn.className = 'btn btn-sm mt-2';

      // Adjust button based on mode
      if (isQuotationMode) {
        addBtn.style.background = 'var(--warning)';
        addBtn.innerHTML = '<i class="bi bi-plus-circle me-1"></i>Add to Quote';
      } else {
        addBtn.style.background = 'var(--primary)';
        addBtn.innerHTML = '<i class="bi bi-plus-circle me-1"></i>Add to Cart';
      }

      addBtn.style.color = 'white';
      addBtn.style.fontWeight = '600';
      addBtn.style.width = '100%';
      addBtn.onclick = (e) => { e.stopPropagation(); addToCart(p); };

      card.appendChild(name);
      card.appendChild(price);
      card.appendChild(stock);
      card.appendChild(sku);
      if (metaParts.length) card.appendChild(meta);
      card.appendChild(addBtn);
      card.style.cursor = 'pointer';
      if (document.body.classList.contains('pos-mode-touch')) {
        card.onclick = () => addToCart(p);
      }

      col.appendChild(card);
      grid.appendChild(col);
    });
  }

  function escapeAttr(s) {
    return String(s || '').replace(/\\/g, '\\\\').replace(/'/g, "\\'");
  }

  function cartLineKey(it) {
    const src = it.inventory_source_branch_id || branchId || '';
    const mode = it.fulfillment_mode || 'local';
    return `${it.id}|${src}|${mode}`;
  }

  function localQtyForProduct(p) {
    return Number(p?.local_quantity ?? p?.quantity ?? 0);
  }

  function cartLocalQtyUsed(productId) {
    return cart
      .filter((it) => it.id === productId && (it.fulfillment_mode || 'local') === 'local')
      .reduce((sum, it) => sum + it.quantity, 0);
  }

  // Cart
  function addToCart(p, opts = {}) {
    if (isQuotationMode) {
      addToQuote(p);
      return;
    }

    const fulfillment = opts.fulfillment_mode || 'local';
    const sourceBranchId = opts.inventory_source_branch_id || branchId;
    const sourceBranchName = opts.source_branch_name || null;
    const localQty = localQtyForProduct(p);
    const crossBranch = String(sourceBranchId) !== String(branchId);

    if (!opts.force && fulfillment === 'local') {
      const used = cartLocalQtyUsed(p.id);
      if (localQty <= 0) {
        const otherQty = Number(p.other_branches_quantity || 0);
        if (otherQty > 0) {
          showBranchAvailability(p.id, p.name);
          return;
        }
        showToast('No stock at this branch.', 'danger');
        return;
      }
      if (used + 1 > localQty) {
        showToast(`Only ${localQty} at this branch — choose another branch for more.`, 'warning');
        showBranchAvailability(p.id, p.name);
        return;
      }
    }

    const line = {
      id: p.id,
      name: p.name,
      unit_price: Number(p.selling_price),
      quantity: 1,
      is_taxable: p.is_taxable !== false,
      category: p.category || null,
      brand: p.brand || null,
      condition: p.condition || null,
      location: p.location || null,
      fulfillment_mode: fulfillment,
      inventory_source_branch_id: crossBranch ? sourceBranchId : null,
      source_branch_name: crossBranch ? sourceBranchName : null,
    };

    const idx = cart.findIndex((i) => cartLineKey(i) === cartLineKey(line));
    if (idx >= 0) cart[idx].quantity += 1;
    else cart.push(line);
    renderCart();
    recalcTotals();
    if (crossBranch) {
      const label = fulfillment === 'transfer_to_pos' ? 'Transfer to this branch' : 'Customer collects at branch';
      showToast(`${p.name}: ${label} (${sourceBranchName || 'source branch'})`, 'info');
    }
  }

  function addToCartWithFulfillment(product, sourceBranchId, fulfillmentMode, branchLabel) {
    const full = products.find((p) => String(p.id) === String(product?.id)) || product;
    addToCart(full, {
      force: true,
      fulfillment_mode: fulfillmentMode,
      inventory_source_branch_id: sourceBranchId,
      source_branch_name: branchLabel,
    });
    const modalEl = document.getElementById('branchAvailabilityModal');
    const inst = bootstrap.Modal.getInstance(modalEl);
    if (inst) inst.hide();
  }

  async function showBranchAvailability(productId, productName) {
    const modalEl = document.getElementById('branchAvailabilityModal');
    const listDiv = document.getElementById('branchAvailabilityList');
    const noStockDiv = document.getElementById('noBranchStock');
    const nameEl = document.getElementById('modalProductName');
    if (!modalEl || !listDiv) return;

    if (nameEl) nameEl.textContent = productName || productId;
    listDiv.innerHTML = '<div class="text-center py-3"><div class="spinner-border spinner-border-sm text-primary"></div></div>';
    noStockDiv.classList.add('d-none');

    const product = products.find((p) => String(p.id) === String(productId)) || {
      id: productId,
      name: productName,
      selling_price: 0,
      quantity: 0,
      local_quantity: 0,
      other_branches_quantity: 0,
    };

    try {
      const r = await fetch(`/api/v1/pos/products/${productId}/branch-availability`, { headers: authHeader() });
      if (!r.ok) throw new Error('Failed to load branch stock');
      const j = await r.json();
      const branches = j.data || [];

      const stocked = branches.filter((b) => Number(b.available_quantity) > 0);
      if (!stocked.length) {
        listDiv.innerHTML = '';
        noStockDiv.classList.remove('d-none');
      } else {
        let html = '<table class="table table-sm table-dark table-striped mb-0"><thead><tr><th>Branch</th><th>Code</th><th class="text-end">Qty</th><th>Action</th></tr></thead><tbody>';
        stocked.forEach((b) => {
          const isPosBranch = String(b.branch_id) === String(branchId);
          const safeName = escapeAttr(b.branch_name || '');
          let actions = '';
          if (isPosBranch) {
            actions = `<button type="button" class="btn btn-sm btn-success" onclick="window.addToCartWithFulfillment(window._posPendingProduct,'${b.branch_id}','local','${safeName}')">Sell from here</button>`;
          } else {
            actions = `
              <button type="button" class="btn btn-sm btn-outline-info me-1" title="Customer collects at ${safeName}" onclick="window.addToCartWithFulfillment(window._posPendingProduct,'${b.branch_id}','customer_collect','${safeName}')"><i class="bi bi-person-walking"></i> Collect there</button>
              <button type="button" class="btn btn-sm btn-outline-warning" title="Transfer stock to this till branch" onclick="window.addToCartWithFulfillment(window._posPendingProduct,'${b.branch_id}','transfer_to_pos','${safeName}')"><i class="bi bi-truck"></i> Transfer here</button>`;
          }
          html += `<tr><td>${b.branch_name}</td><td>${b.branch_code || '-'}</td><td class="text-end"><span class="badge bg-success">${b.available_quantity}</span></td><td class="text-nowrap">${actions}</td></tr>`;
        });
        html += '</tbody></table>';
        listDiv.innerHTML = html;
      }

      window._posPendingProduct = product;
      new bootstrap.Modal(modalEl).show();
    } catch (err) {
      console.error(err);
      listDiv.innerHTML = '<div class="alert alert-danger">Could not load branch availability</div>';
      new bootstrap.Modal(modalEl).show();
    }
  }

  async function loadBranchesList() {
    if (branchesList.length) return branchesList;
    try {
      const r = await fetch('/api/v1/branches/public', { headers: authHeader() });
      if (r.ok) {
        branchesList = await r.json();
        if (!Array.isArray(branchesList)) branchesList = branchesList.data || [];
      }
    } catch { branchesList = []; }
    return branchesList;
  }

  async function openBranchStockModal() {
    const modalEl = document.getElementById('branchStockModal');
    if (!modalEl) return;
    const sel = document.getElementById('stockViewBranch');
    await loadBranchesList();
    if (sel) {
      sel.innerHTML = '';
      branchesList.forEach((b) => {
        const opt = document.createElement('option');
        opt.value = b.id;
        opt.textContent = b.code ? `${b.code} — ${b.name}` : b.name;
        if (String(b.id) === String(branchId)) opt.selected = true;
        sel.appendChild(opt);
      });
    }
    new bootstrap.Modal(modalEl).show();
    await refreshBranchStockTable();
  }

  async function refreshBranchStockTable() {
    const table = document.getElementById('branchStockTable');
    const sel = document.getElementById('stockViewBranch');
    const searchEl = document.getElementById('stockViewSearch');
    if (!table || !sel) return;
    const bid = sel.value;
    if (!bid) {
      table.innerHTML = '<p class="text-secondary">Select a branch</p>';
      return;
    }
    table.innerHTML = '<div class="text-center py-3"><div class="spinner-border spinner-border-sm"></div></div>';
    try {
      const url = new URL('/api/v1/pos/branch-stock', window.location.origin);
      url.searchParams.set('branch_id', bid);
      const term = (searchEl && searchEl.value || '').trim();
      if (term) url.searchParams.set('search', term);
      const r = await fetch(url.toString(), { headers: authHeader() });
      if (!r.ok) throw new Error(r.statusText);
      const j = await r.json();
      const rows = j.data || [];
      if (!rows.length) {
        table.innerHTML = '<p class="text-secondary text-center py-4">No received stock at this branch</p>';
        return;
      }
      let html = '<table class="table table-sm table-dark table-striped"><thead><tr><th>Product</th><th>SKU</th><th class="text-end">Qty</th><th class="text-end">Price</th></tr></thead><tbody>';
      rows.forEach((row) => {
        html += `<tr><td>${row.name}</td><td>${row.sku || '-'}</td><td class="text-end">${row.available_quantity}</td><td class="text-end">${fmt(row.selling_price)}</td></tr>`;
      });
      html += '</tbody></table>';
      table.innerHTML = html;
    } catch (e) {
      table.innerHTML = `<div class="alert alert-danger">${e.message}</div>`;
    }
  }

  window.showBranchAvailability = showBranchAvailability;
  window.addToCartWithFulfillment = addToCartWithFulfillment;
  function updateQty(i, d) { cart[i].quantity = Math.max(1, cart[i].quantity + d); renderCart(); recalcTotals(); }
  function removeItem(i) { cart.splice(i, 1); renderCart(); recalcTotals(); }
  function renderCart() {
    const wrap = document.getElementById('cartList');
    const emptyCart = document.getElementById('emptyCart');
    if (!wrap) return;

    wrap.innerHTML = '';
    if (!cart.length) {
      if (emptyCart) emptyCart.style.display = 'flex';
      return;
    }
    if (emptyCart) emptyCart.style.display = 'none';

    cart.forEach((it, i) => {
      const row = document.createElement('div');
      row.className = 'bag-item';

      const itemName = document.createElement('div');
      itemName.className = 'bag-item-name';
      itemName.textContent = it.name;
      if (it.fulfillment_mode && it.fulfillment_mode !== 'local') {
        const fb = document.createElement('div');
        fb.className = 'small text-warning';
        const modeLabel = it.fulfillment_mode === 'transfer_to_pos'
          ? 'Transfer to this branch'
          : 'Customer collects at branch';
        fb.textContent = `${modeLabel}${it.source_branch_name ? ` · ${it.source_branch_name}` : ''}`;
        itemName.appendChild(fb);
      } else if (it.inventory_source_branch_id && String(it.inventory_source_branch_id) !== String(branchId)) {
        const fb = document.createElement('div');
        fb.className = 'small text-info';
        fb.textContent = `Stock from ${it.source_branch_name || 'other branch'}`;
        itemName.appendChild(fb);
      }
      // Show serial number badge(s) for serialised items
      if (it.is_serialized && it.serial_numbers && it.serial_numbers.length) {
        const snBadge = document.createElement('div');
        snBadge.style.cssText = 'font-size:.68rem;margin-top:.15rem;';
        snBadge.innerHTML = it.serial_numbers.map(s =>
          `<span style="background:#e8f5e9;color:#1b5e20;border-radius:.3rem;padding:.1rem .35rem;font-family:monospace;margin-right:.2rem;">
            <i class="bi bi-upc-scan" style="font-size:.65rem;"></i> ${s}
          </span>`
        ).join('');
        itemName.appendChild(snBadge);
      }

      const qtyControl = document.createElement('div');
      qtyControl.className = 'bag-item-qty';

      const minus = document.createElement('button');
      minus.className = 'qty-btn';
      minus.innerHTML = '<i class="bi bi-dash-lg"></i>';
      // Serialised items: qty is driven by scanned serials — disable manual change
      if (it.is_serialized) { minus.disabled = true; minus.title = 'Scan serial to adjust quantity'; }
      else minus.onclick = () => updateQty(i, -1);

      const qty = document.createElement('span');
      qty.className = 'qty-display';
      qty.textContent = String(it.quantity);

      const plus = document.createElement('button');
      plus.className = 'qty-btn';
      plus.innerHTML = '<i class="bi bi-plus-lg"></i>';
      if (it.is_serialized) { plus.disabled = true; plus.title = 'Scan serial to adjust quantity'; }
      else plus.onclick = () => updateQty(i, 1);

      qtyControl.appendChild(minus);
      qtyControl.appendChild(qty);
      qtyControl.appendChild(plus);

      const itemPrice = document.createElement('div');
      itemPrice.className = 'bag-item-price';
      itemPrice.textContent = fmt(it.unit_price * it.quantity);

      const del = document.createElement('button');
      del.className = 'btn btn-sm';
      del.style.padding = '0.35rem 0.5rem';
      del.style.color = '#ef4444';
      del.style.border = '1px solid #ef4444';
      del.style.background = 'transparent';
      del.innerHTML = '<i class="bi bi-trash"></i>';
      del.onclick = () => removeItem(i);

      row.appendChild(itemName);
      row.appendChild(qtyControl);
      row.appendChild(itemPrice);
      row.appendChild(del);
      wrap.appendChild(row);
    });
  }

  function recalcTotals() {
    const vatRate = Number(appSettings.vatRate || 14.0);
    let subtotal = 0, discount = 0, vat = 0;
    cart.forEach(it => { const line = it.unit_price * it.quantity; subtotal += line; if (it.is_taxable) vat += line * (vatRate / 100); });
    const total = subtotal - discount + vat;
    const S = id => document.getElementById(id);
    S('subtotal').textContent = fmt(subtotal);
    S('discount').textContent = fmt(discount);
    S('vat').textContent = fmt(vat);
    S('total').textContent = fmt(total);
    if (paymentMethod === 'cash') updateChange();
  }

  function updateChange() {
    const total = parseFloat(document.getElementById('total').textContent.replace(/[^0-9.\-]/g, '')) || 0;
    const tendered = Number(document.getElementById('amountTendered').value || 0);
    const change = Math.max(0, tendered - total);
    document.getElementById('changeDue').textContent = fmt(change);
  }

  // Customers
  function customerCreditApproved(c) {
    if (!c) return false;
    if (c.credit_approved === true) return true;
    return Number(c.credit_limit || 0) > 0;
  }

  function updateCreditPaymentOption() {
    const creditBtn = document.getElementById('pm-credit');
    const statusEl = document.getElementById('customerCreditStatus');
    if (!creditBtn) return;

    const approved = customerCreditApproved(selectedCustomer);
    const hasCustomer = !!selectedCustomer;

    if (!hasCustomer) {
      creditBtn.classList.add('disabled-credit');
      creditBtn.title = 'Select a customer to use credit (must be approved by management)';
      if (statusEl) statusEl.classList.add('d-none');
      if (paymentMethod === 'credit') setPayment('cash');
      return;
    }

    if (approved) {
      creditBtn.classList.remove('disabled-credit');
      creditBtn.title = 'Charge to customer account (credit approved)';
      if (statusEl) {
        statusEl.classList.remove('d-none');
        statusEl.innerHTML = '<i class="bi bi-check-circle text-success me-1"></i>Credit approved — limit ' + fmt(selectedCustomer.credit_limit || 0);
        statusEl.className = 'mt-2 small text-success';
      }
    } else {
      creditBtn.classList.add('disabled-credit');
      creditBtn.title = 'This customer is cash/card only until management sets a credit limit';
      if (statusEl) {
        statusEl.classList.remove('d-none');
        statusEl.innerHTML = '<i class="bi bi-info-circle me-1"></i>Cash or card only — credit requires management approval';
        statusEl.className = 'mt-2 small text-warning';
      }
      if (paymentMethod === 'credit') setPayment('cash');
    }
  }

  function openCustomerModal() { new bootstrap.Modal(document.getElementById('customerModal')).show(); loadCustomers(); }

  function openNewCustomerModal() {
    const form = document.getElementById('newCustomerForm');
    const err = document.getElementById('newCustomerError');
    if (form) form.reset();
    if (err) { err.classList.add('d-none'); err.textContent = ''; }
    new bootstrap.Modal(document.getElementById('newCustomerModal')).show();
    setTimeout(() => document.getElementById('newCustomerName')?.focus(), 200);
  }

  async function submitNewCustomer(e) {
    if (e) e.preventDefault();
    const name = (document.getElementById('newCustomerName')?.value || '').trim();
    const errEl = document.getElementById('newCustomerError');
    const saveBtn = document.getElementById('saveNewCustomerBtn');
    if (!name) {
      if (errEl) { errEl.textContent = 'Customer name is required.'; errEl.classList.remove('d-none'); }
      return;
    }
    const payload = {
      name,
      phone: (document.getElementById('newCustomerPhone')?.value || '').trim() || null,
      email: (document.getElementById('newCustomerEmail')?.value || '').trim() || null,
      address: (document.getElementById('newCustomerAddress')?.value || '').trim() || null,
      branch_id: branchId || null,
    };
    if (saveBtn) saveBtn.disabled = true;
    try {
      const r = await fetch('/api/v1/pos/customers', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...authHeader() },
        body: JSON.stringify(payload),
      });
      const j = await r.json();
      if (!r.ok || !j.success) {
        const msg = j.detail || j.error || r.statusText || 'Could not create customer';
        if (errEl) { errEl.textContent = typeof msg === 'string' ? msg : JSON.stringify(msg); errEl.classList.remove('d-none'); }
        return;
      }
      const created = j.data;
      bootstrap.Modal.getInstance(document.getElementById('newCustomerModal'))?.hide();
      bootstrap.Modal.getInstance(document.getElementById('customerModal'))?.hide();
      await selectCustomer(created);
      showToast(`${created.name} registered — cash/card sales only until credit is approved`, 'success');
      await loadCustomers(name);
    } catch (ex) {
      if (errEl) { errEl.textContent = ex.message; errEl.classList.remove('d-none'); }
    } finally {
      if (saveBtn) saveBtn.disabled = false;
    }
  }
  async function loadCustomers(search) {
    try {
      const url = new URL('/api/v1/pos/customers', window.location.origin);
      if (branchId) url.searchParams.set('branch_id', branchId);
      if (search && String(search).trim()) url.searchParams.set('search', String(search).trim());
      console.log('[POS] Loading customers:', url.toString());
      const r = await fetch(url.toString(), { headers: authHeader() });
      console.log('[POS] Customers response:', r.status, r.ok);
      if (r.ok) {
        const j = await r.json();
        customers = j.data || [];
        console.log('[POS] Customers loaded:', customers.length);
        renderCustomers();
      } else {
        const errorText = await r.text();
        console.error('[POS] Failed to load customers:', r.status, errorText);
        showToast('Failed to load customers: ' + r.statusText, 'danger');
      }
    } catch (e) {
      console.error('[POS] Error loading customers:', e);
      showToast('Error loading customers: ' + e.message, 'danger');
    }
  }
  function renderCustomers() {
    const box = document.getElementById('customerList'); const empty = document.getElementById('noCustomers');
    box.innerHTML = ''; if (!customers.length) { empty.classList.remove('d-none'); return; } else { empty.classList.add('d-none'); }
    customers.forEach(c => {
      const div = document.createElement('div'); div.className = 'border rounded p-2 mb-2';
      div.style.cursor = 'pointer';
      const approved = customerCreditApproved(c);
      const creditBadge = approved
        ? '<span class="badge bg-success ms-1">Credit OK</span>'
        : '<span class="badge bg-secondary ms-1">Cash/Card</span>';
      const title = document.createElement('div');
      title.innerHTML = '<strong>' + (c.name || 'Unnamed') + '</strong> <span class="ms-2 badge bg-secondary">' + (c.customer_type || 'Retail') + '</span>' + creditBadge;
      const det = document.createElement('div'); det.className = 'small text-secondary'; det.textContent = [c.email, c.phone].filter(Boolean).join(' • ');
      div.onclick = () => selectCustomer(c);
      div.appendChild(title); div.appendChild(det); box.appendChild(div);
    });
  }
  async function selectCustomer(c) {
    selectedCustomer = c; document.getElementById('customerSummary').textContent = c.name || 'Customer';
    updateCreditPaymentOption();

    // Update quotation customer if in quotation mode
    if (isQuotationMode) {
      quoteCustomer = c;
      const customerName = document.getElementById('quoteCustomerName');
      if (customerName) customerName.textContent = c.name || 'Customer';
    }

    if (customerCreditApproved(c)) {
      try {
        const r = await fetch(`/api/v1/sales/customers/${c.id}/balance`, { headers: authHeader() });
        if (r.ok) {
          const j = await r.json();
          document.getElementById('customerBalance').textContent = fmt(j.balance || 0);
          document.getElementById('customerBalanceWrap').classList.remove('d-none');
        }
      } catch { /* ignore */ }
    } else {
      document.getElementById('customerBalanceWrap').classList.add('d-none');
    }
    bootstrap.Modal.getInstance(document.getElementById('customerModal'))?.hide();
  }
  function useWalkIn() {
    selectedCustomer = null;
    document.getElementById('customerSummary').textContent = 'Walk-in Customer';
    document.getElementById('customerBalanceWrap').classList.add('d-none');
    updateCreditPaymentOption();
  }

  // Banking defaults
  async function resolveCardDefaults() {
    defaultCardBankId = null;
    try {
      const br = branchId; if (!br) return;
      const r = await fetch(`/api/v1/pos/branch-defaults/${br}/card-bank`, { headers: authHeader() });
      if (r.ok) { const j = await r.json(); defaultCardBankId = j?.data?.default_card_bank_account_id || null; }
      if (!defaultCardBankId) { const g = await fetch('/api/v1/pos/defaults/card-bank', { headers: authHeader() }); if (g.ok) { const j2 = await g.json(); defaultCardBankId = j2?.data?.default_card_bank_account_id || null; } }
    } catch { }
  }
  async function loadBankAccounts() {
    try {
      const url = new URL('/api/v1/banking/accounts', window.location.origin); if (branchId) url.searchParams.set('branch_id', branchId);
      const r = await fetch(url.toString(), { headers: authHeader() }); const j = await r.json(); bankAccounts = j.data || [];
      const sel = document.getElementById('cardBankSelect'); sel.innerHTML = '';
      if (!bankAccounts.length) { sel.innerHTML = '<option value="">No bank accounts</option>'; return; }
      bankAccounts.forEach(a => { const opt = document.createElement('option'); opt.value = a.id; opt.textContent = a.name + (a.account_number ? ' (' + a.account_number + ')' : ''); sel.appendChild(opt); });
      if (defaultCardBankId) { const exists = bankAccounts.some(a => String(a.id) === String(defaultCardBankId)); if (exists) { sel.value = defaultCardBankId; showDefaultBadge(defaultCardBankId); document.getElementById('cardControls').style.display = 'block'; } }
    } catch { }
  }
  function showDefaultBadge(id) {
    const acc = bankAccounts.find(a => String(a.id) === String(id)); const badge = document.getElementById('defaultCardBadge');
    if (acc) { document.getElementById('defaultCardName').textContent = acc.name + (acc.account_number ? ' (' + acc.account_number + ')' : ''); badge.classList.remove('d-none'); }
    else { badge.classList.add('d-none'); }
  }

  // Payment
  function setPayment(method) {
    if (method === 'credit' && !customerCreditApproved(selectedCustomer)) {
      showToast(selectedCustomer
        ? 'This customer is not approved for credit. Use cash or card, or ask management to set a credit limit.'
        : 'Select a credit-approved customer for account sales.', 'warning');
      method = 'cash';
    }
    paymentMethod = method;
    const ids = ['pm-cash', 'pm-card', 'pm-credit']; ids.forEach(id => document.getElementById(id).classList.toggle('active', id === 'pm-' + method));
    document.getElementById('cashControls').style.display = (method === 'cash') ? 'flex' : 'none';
    document.getElementById('cardControls').style.display = (method === 'card') ? 'block' : 'none';
    if (method === 'card') showDefaultBadge(defaultCardBankId); else document.getElementById('defaultCardBadge').classList.add('d-none');
  }

  // Sale — include serial_numbers for serialised items
  function cartToItems() {
    return cart.map(it => {
      const line = { product_id: it.id, quantity: it.quantity, unit_price: it.unit_price, discount_amount: 0, is_taxable: it.is_taxable };
      if (it.serial_numbers && it.serial_numbers.length) line.serial_numbers = it.serial_numbers;
      const mode = it.fulfillment_mode || 'local';
      if (mode !== 'local' || (it.inventory_source_branch_id && String(it.inventory_source_branch_id) !== String(branchId))) {
        line.fulfillment_mode = mode;
        line.inventory_source_branch_id = it.inventory_source_branch_id || branchId;
      }
      return line;
    });
  }
  // Quotation handling
  async function searchQuotations() {
    const searchTerm = document.getElementById('posQuotationSearch')?.value || '';
    const resultsDiv = document.getElementById('posQuotationResults');
    if (!resultsDiv) return;
    if (!searchTerm.trim()) { resultsDiv.innerHTML = ''; return; }
    try {
      const resp = await fetch(`/api/v1/quotations?status=created,sent,accepted`, { headers: authHeader() });
      if (!resp.ok) throw new Error('Failed to search quotations');
      const data = await resp.json();
      const quotations = (data.quotations || []).filter(q => {
        const term = searchTerm.toLowerCase();
        return (q.quote_number && q.quote_number.toLowerCase().includes(term)) ||
          (q.customer_name && q.customer_name.toLowerCase().includes(term));
      });
      if (quotations.length === 0) {
        resultsDiv.innerHTML = '<small class="text-secondary">No matching quotations</small>';
        return;
      }
      resultsDiv.innerHTML = quotations.map(q => `
        <button type="button" class="btn btn-sm btn-outline-info" onclick="window.applyQuotation('${q.id}', '${q.quote_number}')">
          <i class="bi bi-file-earmark-text"></i> ${q.quote_number} - ${q.customer_name} (${fmt(q.total)})
        </button>
      `).join('');
    } catch (err) {
      console.error('Quotation search failed:', err);
      resultsDiv.innerHTML = '<small class="text-danger">Search failed</small>';
    }
  }

  async function applyQuotation(quotationId, quotationNumber) {
    try {
      const resp = await fetch(`/api/v1/quotations/${quotationId}/apply-to-sale`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...authHeader() },
        body: JSON.stringify({})
      });
      if (!resp.ok) throw new Error('Failed to apply quotation');
      const data = await resp.json();
      selectedQuotation = data.quotation;
      // Clear cart and populate with quotation items
      cart = [];
      const items = data.formatted_items || [];
      items.forEach(item => {
        if (item.product_id) {
          // Find product in products list
          const product = products.find(p => p.id === item.product_id);
          if (product) {
            for (let i = 0; i < item.quantity; i++) {
              cart.push({ ...product, qty: 1 });
            }
          }
        }
      });
      // Set customer from quotation
      if (selectedQuotation.customer_id) {
        const customer = customers.find(c => c.id === selectedQuotation.customer_id);
        if (customer) {
          selectedCustomer = customer;
          document.getElementById('customerSummary').textContent = customer.name;
        }
      }
      // Update display
      const qResults = document.getElementById('posQuotationResults');
      if (qResults) {
        qResults.innerHTML = `<span class="badge bg-success"><i class="bi bi-check-circle"></i> Applied: ${quotationNumber}</span>`;
      }
      renderCart(); recalcTotals();
      showToast(`Quotation ${quotationNumber} applied with ${items.length} items`, 'success');
    } catch (err) {
      console.error('Apply quotation failed:', err);
      showToast('Failed to apply quotation', 'danger');
    }
  }

  function clearQuotationSelection() {
    selectedQuotation = null;
    const qResults = document.getElementById('posQuotationResults');
    if (qResults) qResults.innerHTML = '';
    const qSearch = document.getElementById('posQuotationSearch');
    if (qSearch) qSearch.value = '';
  }

  // Make applyQuotation available globally for onclick
  window.applyQuotation = applyQuotation;

  function getSelectedPaperFormat() {
    const sel = document.getElementById('posPaperFormat');
    return sel && sel.value ? sel.value : defaultPaperFormat;
  }

  async function loadPosPrintFormats() {
    try {
      const r = await fetch('/api/v1/pos/print/formats', { headers: authHeader() });
      if (!r.ok) return;
      const j = await r.json();
      defaultPaperFormat = j.default_format || '80mm';
      posPaperFormats = j.formats || [];
      const sel = document.getElementById('posPaperFormat');
      if (!sel || !posPaperFormats.length) return;
      sel.innerHTML = posPaperFormats.map((f) =>
        `<option value="${f.code}">${f.label}</option>`
      ).join('');
      sel.value = defaultPaperFormat;
    } catch (err) {
      console.warn('Could not load POS print formats', err);
    }
  }

  function printPosJob(job) {
    if (!job) return;
    const fmt = job.paper_format || getSelectedPaperFormat();

    if (job.plain_text && (fmt === 'continuous' || job.print_method === 'continuous_text')) {
      const win = window.open('', '_blank');
      if (!win) {
        showToast('Allow pop-ups to print continuous receipts', 'warning');
        return;
      }
      const escaped = String(job.plain_text)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;');
      win.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8"><title>Receipt</title>
<style>@page{margin:5mm}body{margin:0;font-family:'Courier New',monospace;font-size:10pt;white-space:pre-wrap}</style></head>
<body><pre>${escaped}</pre></body></html>`);
      win.document.close();
      setTimeout(() => { try { win.print(); } catch { } }, 300);
      return;
    }

    if (job.html) {
      const win = window.open('', '_blank');
      if (!win) {
        showToast('Allow pop-ups to print receipts', 'warning');
        return;
      }
      win.document.write(job.html);
      win.document.close();
      setTimeout(() => { try { win.print(); } catch { } }, 250);
      return;
    }

    if (job.pdf_path) {
      const url = job.pdf_path.startsWith('http')
        ? job.pdf_path
        : new URL(job.pdf_path, window.location.origin).href;
      window.open(url, '_blank');
    }
  }

  async function fetchPrintJob({ saleId, receiptId, paperFormat } = {}) {
    const fmt = encodeURIComponent(paperFormat || getSelectedPaperFormat());
    const url = saleId
      ? `/api/v1/pos/sales/${saleId}/print-job?paper_format=${fmt}`
      : `/api/v1/receipts/${receiptId}/print-job?paper_format=${fmt}`;
    const r = await fetch(url, { headers: authHeader() });
    if (!r.ok) {
      const err = await r.json().catch(() => ({}));
      throw new Error(err.detail || 'Print job failed');
    }
    const j = await r.json();
    return j.data;
  }

  async function confirmSale() {
    if (!sessionId) { alert('No open POS session.'); return; }
    if (!cart.length) { alert('Cart is empty.'); return; }
    const totalNum = parseFloat(document.getElementById('total').textContent.replace(/[^0-9.\-]/g, '')) || 0;
    const payload = {
      session_id: sessionId,
      items: cartToItems(),
      customer_id: selectedCustomer ? selectedCustomer.id : null,
      payment_method: paymentMethod,
      amount_tendered: paymentMethod === 'cash' ? Number(document.getElementById('amountTendered').value || 0) : totalNum,
      vat_rate: Number(appSettings.vatRate || 14.0),
      currency: appSettings.currency,
      use_ifrs_posting: true,
      quotation_id: selectedQuotation ? selectedQuotation.id : null,
      receipt_format: getSelectedPaperFormat()
    };
    if (paymentMethod === 'credit' && !selectedCustomer) { alert('Choose a customer for credit sales.'); return; }
    if (paymentMethod === 'credit' && !customerCreditApproved(selectedCustomer)) {
      alert('This customer is not approved for credit. Use cash or card.');
      return;
    }
    if (paymentMethod === 'cash' && payload.amount_tendered < totalNum) { alert('Insufficient cash tendered.'); return; }
    if (paymentMethod === 'card') {
      const chosen = document.getElementById('cardBankSelect').value || defaultCardBankId;
      if (chosen) payload.card_bank_account_id = chosen;
    }
    try {
      const r = await fetch('/api/v1/pos/sales', { method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeader() }, body: JSON.stringify(payload) });
      const j = await r.json();
      if (r.ok && j.success) {
        try {
          const printJob = j.data?.print_job;
          if (printJob) {
            printPosJob(printJob);
          } else {
            const receipt = j.data?.receipt;
            const url = receipt?.pdf_path || receipt?.pdf_url;
            if (url) {
              window.open(url, '_blank');
            } else if (receipt?.html_content) {
              const w = window.open('', '_blank');
              w.document.write(receipt.html_content);
              w.document.close();
              w.print();
            }
          }
        } catch (printErr) {
          console.warn('Receipt print failed', printErr);
        }
        // Reset UI
        cart = []; renderCart(); recalcTotals();
        document.getElementById('amountTendered').value = '';
        document.getElementById('changeDue').textContent = fmt(0);
        document.getElementById('productSearch').value = '';
        document.getElementById('barcodeScan').value = '';
        document.getElementById('customerSummary').textContent = 'Walk-in Customer';
        document.getElementById('customerBalanceWrap').classList.add('d-none');
        selectedCustomer = null; paymentMethod = 'cash'; setPayment('cash'); updateCreditPaymentOption();
        selectedQuotation = null;
        const qResults = document.getElementById('posQuotationResults');
        if (qResults) qResults.innerHTML = '';
        const qSearch = document.getElementById('posQuotationSearch');
        if (qSearch) qSearch.value = '';
        await loadRecentReceipts();
      } else {
        alert('Sale failed: ' + (j.detail || j.error || r.statusText));
      }
    } catch (e) { alert('Sale error: ' + e); }
  }

  // Misc
  function debounce(fn, ms) { let t; return function (...a) { clearTimeout(t); t = setTimeout(() => fn.apply(this, a), ms); }; }
  async function testCardBank() { if (paymentMethod !== 'card') { alert('Switch to Card to test.'); return; } const chosen = document.getElementById('cardBankSelect').value; if (chosen) { const acc = bankAccounts.find(a => String(a.id) === String(chosen)); alert('Card will use: ' + (acc ? (acc.name + (acc.account_number ? ' (' + acc.account_number + ')' : '')) : ('Account ' + chosen))); return; } if (defaultCardBankId) { const acc2 = bankAccounts.find(a => String(a.id) === String(defaultCardBankId)); alert('Card will use default: ' + (acc2 ? (acc2.name + (acc2.account_number ? ' (' + acc2.account_number + ')' : '')) : ('Account ' + defaultCardBankId))); } else { alert('No default card bank configured. Set branch or global default in Settings.'); } }
  async function closeSession() { if (!sessionId) return alert('No open session'); const cashSubmitted = prompt('Enter cash submitted amount', '0'); if (cashSubmitted == null) return; try { const r = await fetch('/api/v1/pos/sessions/close', { method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeader() }, body: JSON.stringify({ session_id: sessionId, cash_submitted: Number(cashSubmitted) }) }); const j = await r.json(); if (r.ok && j.success) { alert('Session closed.'); sessionId = null; } else alert('Close failed.'); } catch (e) { alert('Close error: ' + e); } }

  // Receipt history
  async function ensureReceiptModal() {
    if (!receiptsModal) {
      const el = document.getElementById('recentReceiptsModal');
      if (el) {
        receiptsModal = new bootstrap.Modal(el);
      }
    }
    return receiptsModal;
  }

  function updateReceiptSummary() {
    const el = document.getElementById('recentReceiptsSummary');
    if (!el) return;
    if (!recentReceipts.length) {
      el.textContent = 'No receipts recorded yet';
      return;
    }
    const printed = recentReceipts.filter(r => r.printed).length;
    const latest = recentReceipts[0];
    let dateLabel = '';
    if (latest?.created_at) {
      const dt = new Date(latest.created_at);
      if (!Number.isNaN(dt.valueOf())) {
        dateLabel = dt.toLocaleString();
      }
    }
    el.textContent = `${recentReceipts.length} receipt${recentReceipts.length === 1 ? '' : 's'} • ${printed} printed${dateLabel ? ` • latest ${dateLabel}` : ''}`;
  }

  function renderRecentReceipts(showError) {
    const body = document.getElementById('recentReceiptsBody');
    const empty = document.getElementById('recentReceiptsEmpty');
    const wrap = document.getElementById('recentReceiptsTableWrap');
    if (!body) return;

    if (!Array.isArray(recentReceipts) || recentReceipts.length === 0) {
      body.innerHTML = '';
      if (wrap) wrap.style.display = 'none';
      if (empty) {
        empty.classList.remove('d-none');
        if (showError) {
          const msg = empty.querySelector('div.text-secondary');
          if (msg) msg.textContent = 'Unable to load receipts right now';
        }
      }
      updateReceiptSummary();
      return;
    }

    if (wrap) wrap.style.display = 'block';
    if (empty) empty.classList.add('d-none');

    body.innerHTML = recentReceipts.map((receipt) => {
      const created = receipt.created_at ? new Date(receipt.created_at).toLocaleString() : '-';
      const amount = fmt(receipt.amount || 0);
      const source = receipt.sale_id ? `Sale ${String(receipt.sale_id).slice(0, 8)}…` : (receipt.invoice_id ? `Invoice ${String(receipt.invoice_id).slice(0, 8)}…` : '-');
      const printedClass = receipt.printed ? 'bg-success' : 'bg-warning text-dark';
      const printedLabel = receipt.printed ? 'Printed' : 'Not Printed';
      const paymentLabel = receipt.payment_method ? receipt.payment_method.toUpperCase() : '';
      return `
        <tr data-id="${receipt.id}">
          <td>
            <div class="fw-semibold">${receipt.receipt_number}</div>
            ${paymentLabel ? `<div class="text-secondary small">${paymentLabel}</div>` : ''}
          </td>
          <td>${created}</td>
          <td>${amount}</td>
          <td>${source}</td>
          <td><span class="badge ${printedClass}">${printedLabel}</span>${receipt.print_count ? `<span class="ms-1 text-secondary small">(${receipt.print_count})</span>` : ''}</td>
          <td>
            <div class="btn-group btn-group-sm">
              <button type="button" class="btn btn-outline-light receipt-preview" data-id="${receipt.id}" title="Preview"><i class="bi bi-eye"></i></button>
              <button type="button" class="btn btn-outline-success receipt-print" data-id="${receipt.id}" title="Print"><i class="bi bi-printer"></i></button>
              <button type="button" class="btn btn-outline-info receipt-download" data-id="${receipt.id}" title="Open PDF"><i class="bi bi-file-earmark-pdf"></i></button>
            </div>
          </td>
        </tr>
      `;
    }).join('');

    body.querySelectorAll('.receipt-preview').forEach(btn => {
      btn.onclick = () => previewExistingReceipt(btn.dataset.id);
    });
    body.querySelectorAll('.receipt-print').forEach(btn => {
      btn.onclick = () => printExistingReceipt(btn.dataset.id);
    });
    body.querySelectorAll('.receipt-download').forEach(btn => {
      btn.onclick = () => openReceiptPdf(btn.dataset.id);
    });

    updateReceiptSummary();
  }

  async function fetchReceiptById(id) {
    const resp = await fetch(`/api/v1/receipts/${id}`, { headers: authHeader() });
    if (!resp.ok) {
      throw new Error('Receipt not found');
    }
    return resp.json();
  }

  async function loadRecentReceipts() {
    const loading = document.getElementById('recentReceiptsLoading');
    if (loading) loading.classList.remove('d-none');
    try {
      if (!branchId) await ensureBranchContext();
      const limitSelect = document.getElementById('recentReceiptsLimit');
      const limit = limitSelect ? Number(limitSelect.value || 20) : 20;
      const params = new URLSearchParams({ limit: String(limit) });
      if (branchId) params.set('branch_id', branchId);
      const resp = await fetch(`/api/v1/receipts/recent?${params.toString()}`, { headers: authHeader() });
      if (!resp.ok) {
        recentReceipts = [];
        renderRecentReceipts(true);
        if (resp.status === 401) return;
        throw new Error('Failed to fetch receipts');
      }
      recentReceipts = await resp.json();
      renderRecentReceipts(false);
    } catch (err) {
      console.error('Error loading recent receipts', err);
      showToast('Unable to load receipts', 'danger');
      recentReceipts = [];
      renderRecentReceipts(true);
    } finally {
      if (loading) loading.classList.add('d-none');
    }
  }

  async function previewExistingReceipt(id) {
    try {
      const job = await fetchPrintJob({ receiptId: id });
      const win = window.open('', '_blank');
      if (!win) return;
      if (job.html) {
        win.document.write(job.html);
      } else if (job.plain_text) {
        const escaped = String(job.plain_text).replace(/&/g, '&amp;').replace(/</g, '&lt;');
        win.document.write(`<pre style="font-family:monospace;font-size:10pt;">${escaped}</pre>`);
      } else {
        win.document.write('<p>Receipt preview is unavailable.</p>');
      }
      win.document.close();
    } catch (err) {
      console.error('Preview receipt error', err);
      showToast('Could not preview receipt', 'danger');
    }
  }

  async function printExistingReceipt(id) {
    try {
      const job = await fetchPrintJob({ receiptId: id });
      printPosJob(job);
      await fetch(`/api/v1/receipts/${id}/print`, { method: 'POST', headers: authHeader() });
      showToast('Receipt sent to printer', 'success');
      await loadRecentReceipts();
    } catch (err) {
      console.error('Print receipt error', err);
      showToast('Could not print receipt', 'danger');
    }
  }

  async function openReceiptPdf(id) {
    try {
      let receipt = recentReceipts.find(r => r.id === id);
      if (!receipt) {
        receipt = await fetchReceiptById(id);
      }
      if (receipt?.pdf_path) {
        const url = receipt.pdf_path.startsWith('http') ? receipt.pdf_path : new URL(receipt.pdf_path, window.location.origin).href;
        window.open(url, '_blank');
      } else {
        await previewExistingReceipt(id);
      }
    } catch (err) {
      console.error('Open receipt PDF error', err);
      showToast('Could not open receipt file', 'danger');
    }
  }

  // ========== QUOTATION MODE FUNCTIONS ==========
  function switchToSalesMode() {
    isQuotationMode = false;
    document.getElementById('salesModePanel').style.display = 'flex';
    document.getElementById('quotationModePanel').style.display = 'none';

    const modeSalesBtn = document.getElementById('modeSales');
    const modeQuotationsBtn = document.getElementById('modeQuotations');
    if (modeSalesBtn) {
      modeSalesBtn.classList.add('active-sales');
      modeSalesBtn.classList.remove('active-quote');
    }
    if (modeQuotationsBtn) {
      modeQuotationsBtn.classList.remove('active-quote', 'active-sales');
    }

    renderProducts();
    showToast('Switched to Sales Mode', 'info');
  }

  function switchToQuotationMode() {
    isQuotationMode = true;
    document.getElementById('salesModePanel').style.display = 'none';
    document.getElementById('quotationModePanel').style.display = 'flex';

    const modeSalesBtn = document.getElementById('modeSales');
    const modeQuotationsBtn = document.getElementById('modeQuotations');
    if (modeSalesBtn) modeSalesBtn.classList.remove('active-sales');
    if (modeQuotationsBtn) {
      modeQuotationsBtn.classList.add('active-quote');
      modeQuotationsBtn.classList.remove('active-sales');
    }

    renderProducts();
    renderQuoteItems();
    renderQuoteTotals();
    showToast('Switched to Quotation Creation Mode', 'info');
  }

  function addToQuote(product) {
    if (!product) {
      showToast('Invalid product', 'danger');
      return;
    }

    const existingItem = quoteItems.find(item => item.id === product.id);
    if (existingItem) {
      existingItem.quantity += 1;
    } else {
      quoteItems.push({
        id: product.id,
        name: product.name,
        selling_price: product.selling_price || 0,
        quantity: 1
      });
    }

    renderQuoteItems();
    renderQuoteTotals();
    showToast(`${product.name} added to quote`, 'success');
  }

  function removeFromQuote(productId) {
    quoteItems = quoteItems.filter(item => item.id !== productId);
    renderQuoteItems();
    renderQuoteTotals();
  }

  function updateQuoteItemQuantity(productId, quantity) {
    const item = quoteItems.find(i => i.id === productId);
    if (item) {
      if (quantity <= 0) {
        removeFromQuote(productId);
      } else {
        item.quantity = quantity;
        renderQuoteItems();
        renderQuoteTotals();
      }
    }
  }

  // Expose to global scope for inline onclick handlers
  window.removeFromQuote = removeFromQuote;
  window.updateQuoteItemQuantity = updateQuoteItemQuantity;

  function renderQuoteItems() {
    const itemsList = document.getElementById('quoteItemsList');
    if (!itemsList) return;

    if (quoteItems.length === 0) {
      itemsList.innerHTML = '<div class="text-muted">No items in quote</div>';
      return;
    }

    let html = '<div class="list-group">';
    quoteItems.forEach(item => {
      const subtotal = item.quantity * item.selling_price;
      html += `
      <div class="list-group-item d-flex justify-content-between align-items-center">
        <div>
          <div class="fw-bold">${item.name}</div>
          <small class="text-muted">${item.quantity} x ${fmt(item.selling_price)}</small>
        </div>
        <div class="text-end">
          <div class="fw-bold">${fmt(subtotal)}</div>
          <button class="btn btn-sm btn-danger" onclick="removeFromQuote('${item.id}')">Remove</button>
        </div>
      </div>
    `;
    });
    html += '</div>';
    itemsList.innerHTML = html;
  }

  function renderQuoteTotals() {
    const subtotalEl = document.getElementById('quoteSubtotal');
    const discountEl = document.getElementById('quoteDiscount');
    const vatEl = document.getElementById('quoteVat');
    const totalEl = document.getElementById('quoteTotal');

    if (!subtotalEl) return;

    const subtotal = quoteItems.reduce((sum, item) => sum + (item.quantity * item.selling_price), 0);
    const discount = 0; // No discount by default for quotes
    const discountedSubtotal = subtotal - discount;
    const vat = discountedSubtotal * (appSettings.vatRate / 100);
    const total = discountedSubtotal + vat;

    subtotalEl.textContent = fmt(subtotal);
    discountEl.textContent = fmt(discount);
    vatEl.textContent = fmt(vat);
    totalEl.textContent = fmt(total);
  }

  async function createQuotation() {
    try {
      if (quoteItems.length === 0) {
        showToast('Please add items to the quotation', 'warning');
        return;
      }

      const subtotal = quoteItems.reduce((sum, item) => sum + (item.quantity * item.selling_price), 0);
      const discount = 0;
      const discountedSubtotal = subtotal - discount;
      const vat = discountedSubtotal * (appSettings.vatRate / 100);
      const total = discountedSubtotal + vat;

      // Get current date in ISO format
      const today = new Date().toISOString().split('T')[0];

      const payload = {
        branch_id: branchId,  // Add branch_id
        customer_id: quoteCustomer && quoteCustomer.id ? quoteCustomer.id : null,  // Use null for walk-in customers
        date: today,
        items: quoteItems.map(item => {
          const product = products.find(p => p.id === item.id);
          return {
            product_id: item.id,
            quantity: item.quantity,
            price: item.selling_price,  // Changed from unit_price to price
            discount_amount: 0,
            is_taxable: product ? (product.is_taxable !== false) : true
          };
        }),
        subtotal: subtotal,
        discount_amount: discount,
        vat: vat,
        total: total,
        discount_percent: 0,
        vat_percent: appSettings.vatRate,
        notes: quoteNotes || '',
        status: 'draft'
      };

      console.log('[POS] Creating quotation with payload:', JSON.stringify(payload, null, 2));
      showToast('Creating quotation...', 'info');
      const resp = await fetch('/api/v1/quotations', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...authHeader() },
        body: JSON.stringify(payload)
      });

      console.log('[POS] Quotation response status:', resp.status, resp.ok);

      if (!resp.ok) {
        let errData = {};
        try {
          errData = await resp.json();
        } catch {
          errData = { detail: 'Invalid response from server' };
        }
        console.error('[POS] Quotation API error (full):', JSON.stringify(errData, null, 2));
        const errorMsg = errData.detail || errData.message || (typeof errData === 'string' ? errData : JSON.stringify(errData));
        showToast(`Failed to create quotation: ${errorMsg}`, 'danger');
        return;
      }

      const quotation = await resp.json();
      console.log('[POS] Quotation created:', JSON.stringify(quotation, null, 2));
      clearQuotation();
      showToast(`Quotation created successfully (ID: ${quotation.id})`, 'success');

    } catch (err) {
      console.error('Create quotation error:', err);
      showToast('Error creating quotation: ' + err.message, 'danger');
    }
  }

  function clearQuotation() {
    quoteItems = [];
    quoteCustomer = {};
    quoteNotes = '';
    document.getElementById('quoteNotes').value = '';
    renderQuoteItems();
    renderQuoteTotals();
    const customerName = document.getElementById('quoteCustomerName');
    if (customerName) customerName.textContent = 'No customer selected';
    showToast('Quote cleared', 'info');
  }

  async function openRecentReceiptsModal() {
    const modal = await ensureReceiptModal();
    if (!modal) return;
    modal.show();
    await loadRecentReceipts();
  }

  // Wire up
  document.addEventListener('DOMContentLoaded', async () => {
    // Auth handled by auth-fetch.js on API calls - load settings first
    await loadAppSettings(); updateDateTime(); setInterval(updateDateTime, 1000);
    await loadPosPrintFormats();
    await ensureSession(); await resolveCardDefaults(); await loadBankAccounts(); await loadProducts();
    renderCategoryButtons();  // Add category buttons
    await loadRecentReceipts();

    // Inputs
    document.getElementById('productSearch').addEventListener('input', debounce((e) => loadProducts(e.target.value), 250));
    const showZeroEl = document.getElementById('showZeroStock');
    const gridHint = document.getElementById('productGridHint');
    if (showZeroEl) {
      showZeroEl.addEventListener('change', async () => {
        includeZeroStock = showZeroEl.checked;
        if (gridHint) {
          gridHint.textContent = includeZeroStock
            ? 'Including out-of-stock at your branch'
            : 'Showing in-stock at your branch (or available elsewhere)';
        }
        await loadProducts(document.getElementById('productSearch')?.value || '');
      });
    }
    const stockByBranchBtn = document.getElementById('btnStockByBranch');
    if (stockByBranchBtn) stockByBranchBtn.onclick = openBranchStockModal;
    const stockRefresh = document.getElementById('stockViewRefresh');
    if (stockRefresh) stockRefresh.onclick = refreshBranchStockTable;
    const stockSearch = document.getElementById('stockViewSearch');
    if (stockSearch) stockSearch.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); refreshBranchStockTable(); } });
    document.getElementById('catAll').onclick = showAllCategories;  // Add "All" category button handler
    document.getElementById('chooseCustomerBtn').onclick = openCustomerModal;
    document.getElementById('openNewCustomerBtn')?.addEventListener('click', openNewCustomerModal);
    document.getElementById('newCustomerForm')?.addEventListener('submit', submitNewCustomer);
    document.getElementById('useWalkIn').onclick = () => { useWalkIn(); bootstrap.Modal.getInstance(document.getElementById('customerModal')).hide(); };
    document.getElementById('customerSearch').addEventListener('input', debounce((e) => loadCustomers(e.target.value), 300));
    const clearCustSearch = document.getElementById('clearCustomerSearch');
    if (clearCustSearch) clearCustSearch.onclick = () => { document.getElementById('customerSearch').value = ''; loadCustomers(); };

    updateCreditPaymentOption();

    // Payments
    document.getElementById('pm-cash').onclick = () => setPayment('cash');
    document.getElementById('pm-card').onclick = () => setPayment('card');
    document.getElementById('pm-credit').onclick = () => setPayment('credit');
    document.getElementById('amountTendered').addEventListener('input', updateChange);
    document.getElementById('reloadBanks').onclick = async () => { await resolveCardDefaults(); await loadBankAccounts(); };
    document.getElementById('testCardBtn').onclick = testCardBank;
    document.getElementById('confirmBtn').onclick = confirmSale;
    document.getElementById('closeSessionBtn').onclick = closeSession;

    // Quotation picker handlers
    const qSearchBtn = document.getElementById('posQuotationSearchBtn');
    const qClearBtn = document.getElementById('posClearQuotationBtn');
    const qSearchInput = document.getElementById('posQuotationSearch');
    if (qSearchBtn) qSearchBtn.onclick = searchQuotations;
    if (qClearBtn) qClearBtn.onclick = clearQuotationSelection;
    if (qSearchInput) {
      qSearchInput.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') { e.preventDefault(); searchQuotations(); }
      });
    }

    // Mode switch handlers
    const modeSalesBtn = document.getElementById('modeSales');
    const modeQuotationsBtn = document.getElementById('modeQuotations');
    if (modeSalesBtn) modeSalesBtn.onclick = switchToSalesMode;
    if (modeQuotationsBtn) modeQuotationsBtn.onclick = switchToQuotationMode;

    // Quotation mode button handlers
    const chooseCustomerBtnQuote = document.getElementById('chooseCustomerBtnQuote');
    const useWalkInQuote = document.getElementById('useWalkInQuote');
    const createQuoteBtn = document.getElementById('createQuoteBtn');
    const clearQuoteBtn = document.getElementById('clearQuoteBtn');
    if (chooseCustomerBtnQuote) chooseCustomerBtnQuote.onclick = openCustomerModal;
    if (useWalkInQuote) useWalkInQuote.onclick = () => {
      quoteCustomer = {};
      document.getElementById('quoteCustomerName').textContent = 'Walk-in Customer';
      showToast('Quote will be for walk-in customer', 'info');
    };
    if (createQuoteBtn) createQuoteBtn.onclick = createQuotation;
    if (clearQuoteBtn) clearQuoteBtn.onclick = clearQuotation;

    const refreshReceiptsBtn = document.getElementById('refreshReceiptsList');
    if (refreshReceiptsBtn) { refreshReceiptsBtn.onclick = () => loadRecentReceipts(); }
    const limitSelect = document.getElementById('recentReceiptsLimit');
    if (limitSelect) { limitSelect.addEventListener('change', () => loadRecentReceipts()); }
    const viewReceiptsBtn = document.getElementById('viewReceiptsBtn');
    if (viewReceiptsBtn) viewReceiptsBtn.onclick = () => openRecentReceiptsModal();

    window.posRerenderProducts = renderProducts;

    // ── Barcode / serial scan ─────────────────────────────────────────────
    // Priority: 1) product SKU/barcode match  2) serial number lookup
    const scan = document.getElementById('barcodeScan');
    if (scan) {
      try { scan.focus(); } catch { }
      scan.addEventListener('keypress', async (e) => {
        if (e.key !== 'Enter') return;
        const code = String(scan.value || '').trim();
        if (!code) return;
        scan.value = '';

        // 1. Local product cache hit (instant)
        const byCode = (p) => String(p.sku || '').toLowerCase() === code.toLowerCase()
                            || String(p.barcode || '').toLowerCase() === code.toLowerCase();
        let match = products.find(byCode);
        if (match) { addToCart(match); recalcTotals(); return; }

        // 2. Server product search (in case product isn't in cache)
        await loadProducts(code);
        match = products.find(byCode);
        if (match) { addToCart(match); recalcTotals(); return; }

        // 3. Serial number lookup — handles serialised stock scan
        try {
          const r = await fetch(`/api/v1/serial-numbers/scan/${encodeURIComponent(code)}`, { headers: authHeader() });
          const d = await r.json();
          if (!d.found) {
            showToast(`"${code}" — not found as product barcode or serial number`, 'danger');
            return;
          }
          if (!d.can_sell) {
            showToast(`Serial ${code}: ${d.reason}`, 'danger');
            return;
          }
          // Check if the product is already in the local cache
          let product = products.find(p => p.id === d.product_id);
          if (!product) {
            // Fetch product record for price / taxability
            await loadProducts(d.product_name || '');
            product = products.find(p => p.id === d.product_id);
          }
          if (!product) {
            // Build a minimal product object from scan data
            product = { id: d.product_id, name: d.product_name, sku: d.product_sku, selling_price: d.selling_price, is_taxable: true };
          }

          // Add to cart — serialised items must carry exactly 1 serial per unit
          const existingIdx = cart.findIndex(i => i.id === d.product_id && i.is_serialized);
          if (existingIdx >= 0) {
            // Append serial to existing line
            cart[existingIdx].quantity += 1;
            cart[existingIdx].serial_numbers = [...(cart[existingIdx].serial_numbers || []), code];
          } else {
            cart.push({
              id: d.product_id,
              name: d.product_name || product.name,
              unit_price: d.selling_price || Number(product.selling_price) || 0,
              quantity: 1,
              is_taxable: true,
              is_serialized: true,
              serial_numbers: [code],
              category: product.category || null,
              brand: product.brand || null,
              condition: product.condition || null,
              location: product.location || null,
            });
          }
          renderCart();
          recalcTotals();
          showToast(`Serial ${code} — ${d.product_name} added to cart`, 'success');
        } catch (err) {
          showToast(`Scan error: ${err.message}`, 'danger');
        }
      });
    }
  });
})();
