// assets/js/dashboard.js
// Complete dashboard script (charts, pumps, modal, printing).
// Print function fetches station info from API (api.php?action=get_station)
// and inserts company name, address, phone, RNC, and centered mensaje1/mensaje2 in the ticket.
// Save as UTF-8 without BOM

(function () {
  'use strict';

  const cfg = window.__FP_CONFIG || {};
  const api = cfg.api || 'api.php';
  const pumpSvg = cfg.pumpSvg || '';

  /* ---------- Helpers ---------- */
  function esc(s) {
    return String(s || '').replace(/[&<>"']/g, function (m) {
      return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[m]);
    });
  }

  function fmtCurrency(v) { return '$' + (parseFloat(v || 0).toFixed(2)); }

  function fmtDateTime(dtStr) {
    if (!dtStr) return ['', ''];
    try {
      const normalized = String(dtStr).replace(' ', 'T');
      const d = new Date(normalized);
      if (isNaN(d)) {
        const parts = String(dtStr).split(' ');
        return [parts[0] || dtStr, parts[1] || ''];
      }
      return [d.toLocaleDateString(), d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })];
    } catch (e) {
      const parts = String(dtStr).split(' ');
      return [parts[0] || dtStr, parts[1] || ''];
    }
  }

  /**
   * fetchJson(url)
   * Adds a cache-busting timestamp parameter to each request so you always get current ("horita") data.
   */
  async function fetchJson(url) {
    const sep = url.indexOf('?') === -1 ? '?' : '&';
    const timeUrl = url + sep + '_=' + Date.now();
    const r = await fetch(timeUrl, { cache: 'no-store' });
    const text = await r.text();
    let parsed = null;
    try { parsed = text ? JSON.parse(text) : null; } catch (e) { parsed = null; }
    if (!r.ok) {
      const bodyMsg = parsed ? (parsed.error || parsed.msg || JSON.stringify(parsed)) : text;
      const err = new Error('HTTP ' + r.status + (bodyMsg ? ' - ' + String(bodyMsg) : ''));
      err.status = r.status; err.body = bodyMsg;
      throw err;
    }
    return parsed !== null ? parsed : text;
  }

  function ensureChartLoaded() {
    return new Promise((resolve, reject) => {
      if (typeof window.Chart !== 'undefined') return resolve();
      const src = 'https://cdn.jsdelivr.net/npm/chart.js@4.3.0/dist/chart.umd.min.js';
      const s = document.createElement('script');
      s.src = src;
      s.async = true;
      s.onload = () => { if (typeof window.Chart !== 'undefined') resolve(); else reject(new Error('Chart loaded but Chart is undefined')); };
      s.onerror = (e) => reject(new Error('Failed to load Chart.js: ' + (e && e.message ? e.message : e)));
      document.head.appendChild(s);
    });
  }

  /* ---------- DOM refs (set on DOMContentLoaded) ---------- */
  let priceTilesEl, pumpsGrid, psModal, psModalBody, psModalTitle, psModalSubtitle, psModalContent;
  let psModalClose, psModalClose2, psSearch, psExpandAll, psCollapseAll;

  /* ---------- Station cache & getter ---------- */
  let stationInfo = null;
  let _stationPromise = null;

  // getStation: fetches station from API once and caches result (but supports refresh if forced)
  async function getStation(force = false) {
    if (!force && stationInfo) return stationInfo;
    if (!force && _stationPromise) return _stationPromise;
    _stationPromise = (async () => {
      try {
        const res = await fetchJson(api + '?action=get_station');
        if (res && res.ok && res.station) {
          stationInfo = res.station;
          return stationInfo;
        }
        // Some APIs may return station directly or array
        if (res && res.station) {
          stationInfo = res.station;
          return stationInfo;
        }
        if (res && Array.isArray(res) && res.length) {
          stationInfo = res[0];
          return stationInfo;
        }
        stationInfo = null;
        return null;
      } catch (e) {
        console.warn('getStation error', e);
        stationInfo = null;
        return null;
      } finally {
        _stationPromise = null;
      }
    })();
    return _stationPromise;
  }

  /* ---------- Charts state ---------- */
  let productSalesChart = null;
  let dailySalesChart = null;
  const PALETTE = ['#ff2e00', '#000000', '#7f7f83', '#2a7a4b', '#6c757d', '#0d6efd', '#fd7e14', '#6f42c1', '#0dcaf0'];
  function paletteColor(i) { return PALETTE[i % PALETTE.length]; }
  function hexToRgba(hex, alpha) {
    if (!hex) return 'rgba(13,110,253,' + alpha + ')';
    if (hex.indexOf('gradient') !== -1) return 'rgba(13,110,253,' + alpha + ')';
    const h = hex.replace('#', '');
    const full = (h.length === 3) ? h.split('').map(c => c + c).join('') : h;
    const bigint = parseInt(full, 16);
    const r = (bigint >> 16) & 255, g = (bigint >> 8) & 255, b = bigint & 255;
    return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')';
  }

  /* ---------- UI template helpers ---------- */
  function productTileHtml(product, idx) {
    const paletteSet = [
      'linear-gradient(135deg,#0d6efd,#0056d6)',
      'linear-gradient(135deg,#6f42c1,#5a2ea6)',
      'linear-gradient(135deg,#0dcaf0,#0aa5d7)',
      'linear-gradient(135deg,#20c997,#17a589)',
      'linear-gradient(135deg,#fd7e14,#e56a00)'
    ];
    const bg = (product.bg_color && product.bg_color.trim()) ? product.bg_color : paletteSet[idx % paletteSet.length];
    let out = '';
    out += '<div class="col-12 col-md-6 col-lg-3">';
    out += '<div class="price-card" style="background:' + bg + '; color:#fff;">';
    out += '<div><div class="price-amount" style="color:#fff;">$' + (parseFloat(product.price || 0).toFixed(2)) + '</div>';
    out += '<div class="price-name" style="color:rgba(255,255,255,0.95)">' + esc(product.name) + '</div></div>';
    out += '<div class="d-flex justify-content-between align-items-center mt-2">';
    out += '<div class="price-sub" style="color:rgba(255,255,255,0.9)">SKU: ' + esc(product.sku || '') + '</div>';
    out += '<div style="font-size:12px;opacity:.9;color:rgba(255,255,255,0.9)">' + esc(product.unit || '') + '</div>';
    out += '</div></div></div>';
    return out;
  }

  function pumpCardHtml(p) {
    const img = p.image_url || pumpSvg;
    const lastAmt = p.last_sale ? ('$' + parseFloat(p.last_sale.total).toFixed(2)) : '—';
    const lastDate = p.last_sale ? new Date(p.last_sale.created_at) : null;
    const dt = lastDate ? new Intl.DateTimeFormat(undefined, { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }).format(lastDate) : '';
    const lastSaleId = p.last_sale ? p.last_sale.id : '';
    let out = '';
    out += '<div class="pump-card" data-pump-id="' + p.id + '" data-last-sale-id="' + lastSaleId + '">';
    out += '<div class="pump-image"><img src="' + esc(img) + '" alt="' + esc(p.name) + '"></div>';
    out += '<div class="pump-meta"><div class="pump-name">' + esc(p.name) + '</div>';
    out += '<div class="pump-last">' + lastAmt + '</div>';
    out += '<div class="pump-datetime">' + esc(dt) + '</div>';
    out += '<div class="pump-status" style="font-size:12px;color:#777;margin-top:2px">' + esc((p.status||'IDLE').toString()) + '</div>';
    out += '</div></div>';
    return out;
  }

  /* ---------- Charts renderers ---------- */
  async function renderProductSalesChart(products, productsMap) {
    await ensureChartLoaded();
    const labels = (products || []).map(p => p.name);
    const data = (products || []).map(p => parseFloat(p.total) || 0);
    const bgColors = (products || []).map((p, i) => {
      const prod = productsMap && productsMap[p.id];
      return (prod && prod.bg_color && prod.bg_color.trim()) ? prod.bg_color : paletteColor(i);
    });
    const borderColors = bgColors.map(c => hexToRgba(c, 1));
    const canvas = document.getElementById('productSalesChart');
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    try { if (productSalesChart) productSalesChart.destroy(); } catch (e) { }
    productSalesChart = new Chart(ctx, {
      type: 'bar',
      data: { labels: labels, datasets: [{ label: 'Ventas ($)', data: data, backgroundColor: bgColors, borderColor: borderColors, borderWidth: 1, borderRadius: 8 }] },
      options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { grid: { display: false } }, y: { beginAtZero: true } } }
    });
  }

  async function renderDailySalesChart(daily, productsMap) {
    await ensureChartLoaded();
    if (!daily || !Array.isArray(daily.labels) || !Array.isArray(daily.series) || daily.series.length === 0) {
      try { if (dailySalesChart) dailySalesChart.destroy(); } catch (e) { }
      dailySalesChart = null;
      return;
    }
    const labels = daily.labels;
    const datasets = daily.series.map((s, idx) => {
      const prod = productsMap && productsMap[s.product_id];
      const color = (prod && prod.bg_color && prod.bg_color.trim()) ? prod.bg_color : paletteColor(idx);
      return {
        label: s.name,
        data: s.data.map(v => parseFloat(v) || 0),
        borderColor: color,
        backgroundColor: hexToRgba(color, 0.12),
        fill: true,
        tension: 0.36,
        pointRadius: 3,
        borderWidth: 2
      };
    });
    const canvas = document.getElementById('salesChart');
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    try { if (dailySalesChart) dailySalesChart.destroy(); } catch (e) { }
    dailySalesChart = new Chart(ctx, {
      type: 'line',
      data: { labels: labels, datasets: datasets },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        plugins: { legend: { position: 'top', labels: { usePointStyle: true } }, tooltip: { mode: 'index', intersect: false } },
        interaction: { mode: 'index', intersect: false },
        scales: { x: { grid: { color: '#f1f1f1' } }, y: { beginAtZero: true, grid: { color: '#f1f1f1' } } }
      }
    });
  }

  /* ---------- Main loader ---------- */
  async function loadTilesAndCharts(range = 7) {
    try {
      const [pJson, dJson, psJson] = await Promise.all([
        fetchJson(api + '?action=get_products'),
        fetchJson(api + '?action=get_dashboard&range=' + range),
        fetchJson(api + '?action=get_products_sales&range=' + range)
      ]);

      // Price tiles
      priceTilesEl.innerHTML = '';
      const productsMap = {};
      if (pJson && pJson.ok && Array.isArray(pJson.products)) {
        pJson.products.forEach((pr, i) => { priceTilesEl.insertAdjacentHTML('beforeend', productTileHtml(pr, i)); productsMap[pr.id] = pr; });
      }

      // Product sales bar
      if (psJson && psJson.ok && Array.isArray(psJson.products)) {
        await renderProductSalesChart(psJson.products, productsMap);
      } else {
        await renderProductSalesChart([], productsMap);
      }

      // Daily series (prefer detailed series)
      try {
        const daily = await fetchJson(api + '?action=get_products_daily&range=' + range);
        if (daily && daily.ok && Array.isArray(daily.labels) && Array.isArray(daily.series)) {
          await renderDailySalesChart(daily, productsMap);
        } else if (dJson && dJson.labels && dJson.data) {
          await renderDailySalesChart({ labels: dJson.labels, series: [{ product_id: 0, name: 'Ventas', data: dJson.data }] }, productsMap);
        }
      } catch (e) {
        if (dJson && dJson.labels && dJson.data) {
          await renderDailySalesChart({ labels: dJson.labels, series: [{ product_id: 0, name: 'Ventas', data: dJson.data }] }, productsMap);
        }
      }

      const luEl = document.getElementById('lastUpdate');
      if (luEl) luEl.innerText = (psJson && psJson.updated_at) || (dJson && dJson.updated_at) || new Date().toLocaleString();
    } catch (e) {
      console.error('loadTilesAndCharts error', e);
    }
  }

  /* ---------- Recent sales ---------- */
  async function loadRecentSales() {
    try {
      const r = await fetchJson(api + '?action=get_recent_sales');
      if (r && r.ok && Array.isArray(r.sales)) {
        let html = '<div class="list-group">';
        r.sales.forEach(s => {
          html += '<div class="list-group-item d-flex justify-content-between align-items-center">';
          html += '<div><div><strong>' + esc(s.sale_code) + '</strong></div><div class="small text-muted">' + esc(s.created_at) + ' — ' + esc(s.username || '—') + '</div></div>';
          html += '<div class="fw-semibold">' + esc(fmtCurrency(s.total)) + '</div></div>';
        });
        html += '</div>';
        document.getElementById('recentList').innerHTML = html;
      } else {
        document.getElementById('recentList').innerHTML = '<div class="no-data">No hay ventas recientes</div>';
      }
    } catch (e) {
      console.error('loadRecentSales error', e);
      document.getElementById('recentList').innerHTML = '<div class="no-data">No se pudieron cargar las ventas recientes</div>';
    }
  }

  /* ---------- Pumps ---------- */
  async function loadPumpsFull() {
    if (!pumpsGrid) return;
    pumpsGrid.innerHTML = '<div class="text-muted">Cargando islas...</div>';
    try {
      let r = null;
      try { r = await fetchJson(api + '?action=get_pumps_with_last_sale'); } catch (e) { r = null; }
      if (r && r.ok && Array.isArray(r.pumps) && r.pumps.length) {
        pumpsGrid.innerHTML = '';
        r.pumps.sort((a, b) => a.id - b.id).forEach(p => pumpsGrid.insertAdjacentHTML('beforeend', pumpCardHtml(p)));
        return;
      }
      const r2 = await fetchJson(api + '?action=get_pumps');
      if (r2 && r2.ok && Array.isArray(r2.pumps) && r2.pumps.length) {
        pumpsGrid.innerHTML = '';
        r2.pumps.sort((a, b) => a.id - b.id).forEach(p => pumpsGrid.insertAdjacentHTML('beforeend', pumpCardHtml(p)));
        return;
      }
      pumpsGrid.innerHTML = '<div class="text-muted">No hay islas registradas</div>';
    } catch (e) {
      console.error('loadPumpsFull error', e);
      pumpsGrid.innerHTML = '<div class="text-danger">Error cargando islas</div>';
    }
  }

  /* ---------- Sales modal rendering ---------- */
  function renderSalesModal(sales) {
    if (!Array.isArray(sales) || sales.length === 0) {
      psModalContent.innerHTML = '<div class="sales-no-data">No se encontraron ventas para esta bomba.</div>';
      return;
    }
    let html = '';
    html += '<div class="mb-2 small text-muted">Filtrar por ticket o producto — resultados: <span id="psMatches">' + sales.length + '</span></div>';
    sales.forEach((sale) => {
      const saleId = esc(String(sale.sale_id));
      const saleCode = esc(sale.sale_code || sale.sale_id);
      const dt = fmtDateTime(sale.created_at || '');
      const dPart = esc(dt[0]), tPart = esc(dt[1]);
      let saleTotal = 0;
      (sale.items || []).forEach(it => { saleTotal += parseFloat(it.total || 0); });

      html += '<div class="sale-card mb-3" data-sale-id="' + saleId + '">';
      html += '<div class="d-flex justify-content-between align-items-center p-2" style="border:1px solid #eee;border-radius:6px;">';
      html += '<div><strong class="sale-code">' + saleCode + '</strong><br><small class="text-muted sale-dt">' + dPart + ' ' + tPart + '</small></div>';
      html += '<div class="d-flex align-items-center gap-2">';
      html += '<div class="fw-semibold sale-total">' + esc(fmtCurrency(saleTotal)) + '</div>';
      html += '<button class="btn btn-sm btn-outline-secondary toggle-items" data-target="' + saleId + '">Ocultar</button>';
      html += '<button class="btn btn-sm btn-primary print-btn" data-sale-id="' + saleId + '">Imprimir</button>';
      html += '</div></div>';

      html += '<div class="sale-items mt-2" data-target="' + saleId + '">';
      html += '<table class="sales-table"><thead><tr><th style="width:70px">Cantidad</th><th>Producto</th><th style="width:110px">Precio unit.</th><th style="width:110px" class="text-end">Valor</th></tr></thead><tbody>';
      (sale.items || []).forEach(it => {
        const qty = esc(it.qty);
        const prod = esc(it.product_name || it.name || '');
        const up = (typeof it.unit_price !== 'undefined') ? esc(fmtCurrency(it.unit_price)) : '—';
        const val = esc(fmtCurrency(it.total));
        html += '<tr class="sale-item-row"><td>' + qty + '</td><td>' + prod + '</td><td>' + up + '</td><td class="text-end">' + val + '</td></tr>';
      });
      html += '</tbody></table></div>';
      html += '</div>';
    });

    psModalContent.innerHTML = html;

    // toggle handlers
    psModalContent.querySelectorAll('.toggle-items').forEach(btn => {
      btn.addEventListener('click', function () {
        const tid = btn.getAttribute('data-target');
        const cont = psModalContent.querySelector('.sale-items[data-target="' + tid + '"]');
        if (!cont) return;
        const isHidden = cont.style.display === 'none';
        cont.style.display = isHidden ? '' : 'none';
        btn.textContent = isHidden ? 'Ocultar' : 'Ver';
      });
    });

    psModalContent.querySelectorAll('.print-btn').forEach(btn => {
      btn.addEventListener('click', function () {
        const sid = btn.getAttribute('data-sale-id');
        const saleObj = sales.find(s => String(s.sale_id) === String(sid));
        if (saleObj) { printSaleWindow(saleObj).catch(err => { console.error('printSaleWindow error', err); alert('Error al imprimir'); }); }
        else alert('Venta no encontrada para impresión');
      });
    });

    // search filter
    if (psSearch) {
      psSearch.value = '';
      psSearch.oninput = function () {
        const q = String(psSearch.value || '').trim().toLowerCase();
        let matches = 0;
        psModalContent.querySelectorAll('.sale-card').forEach(card => {
          const saleCode = (card.querySelector('.sale-code')?.textContent || '').toLowerCase();
          const productText = Array.from(card.querySelectorAll('.sale-item-row td:nth-child(2)')).map(td => td.textContent || '').join(' ').toLowerCase();
          const show = q === '' || saleCode.indexOf(q) !== -1 || productText.indexOf(q) !== -1;
          card.style.display = show ? '' : 'none';
          if (show) matches++;
        });
        const matchesEl = document.getElementById('psMatches');
        if (matchesEl) matchesEl.textContent = matches;
      };
    }

    // expand / collapse
    if (psExpandAll) psExpandAll.onclick = function () {
      psModalContent.querySelectorAll('.sale-items').forEach(el => el.style.display = '');
      psModalContent.querySelectorAll('.toggle-items').forEach(b => b.textContent = 'Ocultar');
    };
    if (psCollapseAll) psCollapseAll.onclick = function () {
      psModalContent.querySelectorAll('.sale-items').forEach(el => el.style.display = 'none');
      psModalContent.querySelectorAll('.toggle-items').forEach(b => b.textContent = 'Ver');
    };

    if (psSearch) psSearch.focus();
  }

  /* ---------- Pump click handler ---------- */
  function attachPumpClickHandler() {
    if (!pumpsGrid) return;
    if (pumpsGrid._attached) return;
    pumpsGrid._attached = true;

    pumpsGrid.addEventListener('click', async function (ev) {
      const card = ev.target.closest('.pump-card');
      if (!card) return;
      const pumpId = card.getAttribute('data-pump-id');
      if (!pumpId) return;

      psModalTitle.textContent = 'Ventas - ' + (card.querySelector('.pump-name')?.textContent || ('Bomba ' + pumpId));
      psModalSubtitle.textContent = 'Cargando ventas...';
      psModalContent.innerHTML = '<div class="sales-no-data">Cargando...</div>';
      psModal.style.display = 'flex';

      try {
        // Try to load station info (for later printing)
        try { await getStation(); } catch (e) { /* ignore */ }

        const json = await fetchJson(api + '?action=get_pump_sales_detailed&pump_id=' + encodeURIComponent(pumpId) + '&limit=500');
        if (!json || !json.ok) {
          psModalContent.innerHTML = '<div class="sales-no-data">Error al cargar ventas</div>';
          psModalSubtitle.textContent = '';
          return;
        }
        const sales = Array.isArray(json.sales) ? json.sales : [];
        renderSalesModal(sales);
        psModalSubtitle.textContent = (sales.length + ' ventas (más recientes arriba)');
      } catch (e) {
        console.error('pump click error', e);
        psModalContent.innerHTML = '<div class="sales-no-data">Error al cargar ventas.</div>';
        psModalSubtitle.textContent = '';
      }
    });
  }

  /* ---------- Print (fetch station from DB/API and include data on ticket) ---------- */
  async function printSaleWindow(sale) {
    // Ensure we have station info
    const station = await getStation().catch(() => null);

    const dt = fmtDateTime(sale.created_at || '');
    const dPart = dt[0], tPart = dt[1];
    const name = station && (station.nombre || station.name) ? (station.nombre || station.name) : 'Estación de servicios';
    const address = station && (station.direccion || station.address) ? (station.direccion || station.address) : '';
    const phone = station && (station.telefono || station.phone) ? (station.telefono || station.phone) : '';
    const rnc = station && station.rnc ? station.rnc : '';
    const msg1 = station && station.mensaje1 ? station.mensaje1 : '';
    const msg2 = station && station.mensaje2 ? station.mensaje2 : '';

    let itemsHtml = '';
    let total = 0;
    (sale.items || []).forEach(it => {
      const qty = esc(it.qty);
      const prod = esc(it.product_name || it.name || '');
      const upVal = (typeof it.unit_price !== 'undefined' && it.unit_price !== null) ? it.unit_price : ((it.total && it.qty) ? (parseFloat(it.total) / Math.max(1, parseFloat(it.qty))) : 0);
      const up = esc(fmtCurrency(upVal));
      const lineTotal = parseFloat(it.total || 0);
      itemsHtml += '<tr><td style="width:50px">' + qty + '</td><td>' + prod + '</td><td style="text-align:right">' + up + '</td><td style="text-align:right">' + esc(fmtCurrency(lineTotal)) + '</td></tr>';
      total += lineTotal;
    });

    if ((!sale.items || sale.items.length === 0) && sale.pump_total) {
      itemsHtml += '<tr><td>-</td><td>Subtotal bomba</td><td></td><td style="text-align:right">' + esc(fmtCurrency(sale.pump_total)) + '</td></tr>';
      total = parseFloat(sale.pump_total || 0);
    }

    // Build receipt safely
    const lines = [];
    lines.push('<!doctype html>');
    lines.push('<html><head><meta charset="utf-8"><title>Ticket ' + esc(sale.sale_code || sale.sale_id) + '</title>');
    lines.push('<style>body{font-family:monospace;font-size:12px;width:320px;margin:0;padding:6mm}.center{text-align:center}table{width:100%;border-collapse:collapse}td,th{padding:3px 0;vertical-align:top}.bold{font-weight:700}hr{border:none;border-top:1px dashed #999;margin:8px 0}</style>');
    lines.push('</head><body>');

    // Station header
    lines.push('<div class="center"><div class="bold">' + esc(name) + '</div>');
    if (address) lines.push('<div>' + esc(address) + '</div>');
    if (phone) lines.push('<div>Tel: ' + esc(phone) + '</div>');
    if (rnc) lines.push('<div>RNC: ' + esc(rnc) + '</div>');
    lines.push('</div>');
    lines.push('<hr>');

    // Ticket info
    lines.push('<div>Ticket: ' + esc(sale.sale_code || sale.sale_id) + '</div>');
    lines.push('<div>Fecha: ' + esc(dPart) + '   Hora: ' + esc(tPart) + '</div>');
    lines.push('<hr>');

    // Items
    lines.push('<table><thead><tr><th>Cant</th><th>Producto</th><th style="text-align:right">Precio</th><th style="text-align:right">Importe</th></tr></thead><tbody>');
    lines.push(itemsHtml);
    lines.push('</tbody></table>');
    lines.push('<hr>');

    // Total
    lines.push('<div style="display:flex;justify-content:space-between"><div>Total:</div><div class="bold">' + esc(fmtCurrency(total)) + '</div></div>');
    lines.push('<div style="height:8px"></div>');

    // Messages centered
    if (msg1) lines.push('<div class="center">' + esc(msg1) + '</div>');
    if (msg2) lines.push('<div class="center">' + esc(msg2) + '</div>');

    // Print script (split to avoid parser issues)
    lines.push('<scr' + 'ipt>setTimeout(function(){window.print();setTimeout(function(){window.close();},300);},250);</scr' + 'ipt>');
    lines.push('</body></html>');

    const receipt = lines.join('\n');
    const w = window.open('', '_blank', 'width=420,height=640');
    if (!w) { alert('Permita ventanas emergentes para imprimir'); return; }
    w.document.open();
    w.document.write(receipt);
    w.document.close();
  }

  /* ---------- Polling for pumps ---------- */
  async function pollPumpsUpdates() {
    try {
      let r = null;
      try { r = await fetchJson(api + '?action=get_pumps_with_last_sale'); } catch (e) { r = null; }
      if (!(r && r.ok && Array.isArray(r.pumps))) {
        try { r = await fetchJson(api + '?action=get_pumps'); } catch (e) { r = null; }
      }
      if (r && r.ok && Array.isArray(r.pumps)) {
        r.pumps.forEach(p => {
          const el = pumpsGrid.querySelector('[data-pump-id="' + p.id + '"]');
          const newLastId = p.last_sale ? String(p.last_sale.id) : '';
          const newStatus = (p.status || '').toString();
          if (!el) { pumpsGrid.insertAdjacentHTML('beforeend', pumpCardHtml(p)); return; }

          // Always update image/status even if last_sale didn't change
          const imgEl = el.querySelector('.pump-image img');
          if (p.image_url && imgEl && imgEl.src !== p.image_url) {
            imgEl.src = p.image_url;
          }
          el.setAttribute('data-status', newStatus);
          const stEl = el.querySelector('.pump-status');
          if (stEl) stEl.textContent = newStatus || 'IDLE';

          const oldLastId = el.getAttribute('data-last-sale-id') || '';
          if (oldLastId !== newLastId) {
            const lastAmtEl = el.querySelector('.pump-last');
            const lastDtEl = el.querySelector('.pump-datetime');
            if (lastAmtEl) lastAmtEl.textContent = p.last_sale ? ('$' + parseFloat(p.last_sale.total).toFixed(2)) : '—';
            if (lastDtEl) lastDtEl.textContent = p.last_sale ? new Date(p.last_sale.created_at).toLocaleString() : '';
            el.setAttribute('data-last-sale-id', newLastId);
            el.classList.add('updated');
            setTimeout(() => el.classList.remove('updated'), 900);
          }
        });
        const lu = document.getElementById('lastUpdate');
        if (lu) lu.innerText = r.updated_at || new Date().toLocaleString();
      }
    } catch (e) {
      console.error('pollPumpsUpdates error', e);
    }
  }

  /* ---------- Init ---------- */
  document.addEventListener('DOMContentLoaded', function () {
    priceTilesEl = document.getElementById('priceTiles');
    pumpsGrid = document.getElementById('pumpsGrid');
    psModal = document.getElementById('psModal');
    psModalBody = document.getElementById('psModalBody');
    psModalTitle = document.getElementById('psModalTitle');
    psModalSubtitle = document.getElementById('psModalSubtitle');
    psModalContent = document.getElementById('psModalContent');
    psModalClose = document.getElementById('psModalClose');
    psModalClose2 = document.getElementById('psModalClose2');
    psSearch = document.getElementById('psSearch');
    psExpandAll = document.getElementById('psExpandAll');
    psCollapseAll = document.getElementById('psCollapseAll');

    if (psModalClose) psModalClose.addEventListener('click', () => { psModal.style.display = 'none'; });
    if (psModalClose2) psModalClose2.addEventListener('click', () => { psModal.style.display = 'none'; });
    if (psModal) psModal.addEventListener('click', (e) => { if (e.target === psModal) psModal.style.display = 'none'; });

    attachPumpClickHandler();

    // initial loads
    loadTilesAndCharts(7);
    loadRecentSales();
    loadPumpsFull();

    // polling and refresh
    setInterval(pollPumpsUpdates, 1000);
    setInterval(() => loadTilesAndCharts(parseInt(document.getElementById('chartRange')?.value || 7, 10)), 5 * 60 * 1000);
  });

  // expose for debug
  window.__FP = window.__FP || {};
  window.__FP.loadTilesAndCharts = loadTilesAndCharts;
  window.__FP.printSaleWindow = printSaleWindow;
  window.__FP.getStation = getStation;

})();