// quote-doc.jsx — printable quote document, the inline email body builder,
// and the SendQuoteModal that wires Quotes → Email (gửi trực tiếp cho khách hàng).
// Loaded after payment-request.jsx (reuses SWO_COMPANY, printDocument) and qdata.jsx.

/* ---- inline HTML used INSIDE the email body (renders in mail-html viewer) ---- */
function quoteSummaryTableHTML(q){
  const tot = quoteTotals(q);
  const rows = q.items.map((it, i) => {
    const line = it.qty * it.price;
    return `<tr>
      <td style="padding:6px 8px;border:1px solid #D8DEE8;color:#54657F">${i + 1}</td>
      <td style="padding:6px 8px;border:1px solid #D8DEE8">${it.name}${it.showDetail && it.detail ? '<div style="font-size:11px;color:#54657F;margin-top:2px">' + it.detail + '</div>' : ''}</td>
      <td style="padding:6px 8px;border:1px solid #D8DEE8;text-align:right;white-space:nowrap">${it.qty} ${it.unit}</td>
      <td style="padding:6px 8px;border:1px solid #D8DEE8;text-align:right;white-space:nowrap">${vnd(line)}</td>
    </tr>`;
  }).join('');
  const discRow = tot.discount > 0
    ? `<tr><td colspan="3" style="padding:5px 8px;border:1px solid #D8DEE8;text-align:right;color:#54657F">Chiết khấu (${tot.discPct}%)</td><td style="padding:5px 8px;border:1px solid #D8DEE8;text-align:right;color:#C0392B">- ${vnd(tot.discount)}</td></tr>`
    : '';
  return `<table style="border-collapse:collapse;width:100%;font-size:13px;margin:10px 0">
    <thead><tr style="background:#EAF1FE">
      <th style="padding:6px 8px;border:1px solid #D8DEE8;text-align:left;width:28px">#</th>
      <th style="padding:6px 8px;border:1px solid #D8DEE8;text-align:left">Hạng mục</th>
      <th style="padding:6px 8px;border:1px solid #D8DEE8;text-align:right">SL</th>
      <th style="padding:6px 8px;border:1px solid #D8DEE8;text-align:right">Thành tiền</th>
    </tr></thead>
    <tbody>${rows}
      <tr><td colspan="3" style="padding:5px 8px;border:1px solid #D8DEE8;text-align:right;color:#54657F">Cộng</td><td style="padding:5px 8px;border:1px solid #D8DEE8;text-align:right">${vnd(tot.sub)}</td></tr>
      ${discRow}
      <tr><td colspan="3" style="padding:5px 8px;border:1px solid #D8DEE8;text-align:right;color:#54657F">Thuế VAT</td><td style="padding:5px 8px;border:1px solid #D8DEE8;text-align:right">${vnd(tot.vat)}</td></tr>
      <tr style="background:#F6F9FD"><td colspan="3" style="padding:7px 8px;border:1px solid #D8DEE8;text-align:right;font-weight:700">Tổng cộng</td><td style="padding:7px 8px;border:1px solid #D8DEE8;text-align:right;font-weight:700;color:#1466E0">${vnd(tot.total)}</td></tr>
    </tbody>
  </table>`;
}

// The HTML body inserted into the email composer / logged in Sent.
function quoteEmailBody(q, cust, rep, branding, sig, onlineUrl){
  const co = docCompany(branding);
  const greet = rep && rep.name ? `Kính gửi ${rep.name}${rep.title ? ' – ' + rep.title : ''},` : `Kính gửi Quý khách hàng ${cust.name || ''},`;
  return `<p>${greet}</p>
<p>Công ty <b>${co.name}</b> trân trọng gửi đến Quý khách <b>Báo giá ${q.code}</b> cho các hạng mục theo nhu cầu trao đổi. Chi tiết như sau:</p>
${quoteSummaryTableHTML(q)}
<p>Báo giá có hiệu lực đến hết ngày <b>${fmtDate(q.validUntil)}</b>. Báo giá chi tiết (bản PDF có chữ ký &amp; con dấu) được đính kèm theo email này.</p>${onlineUrl ? `
<p style="margin:12px 0"><a href="${onlineUrl}" style="display:inline-block;background:#1466E0;color:#fff;text-decoration:none;padding:9px 16px;border-radius:6px;font-weight:600">Xem báo giá ${q.code} trực tuyến</a></p>
<p style="font-size:12px;color:#54657F;margin:2px 0">Nhấn nút trên để mở bản báo giá trực tuyến (xem &amp; tải PDF).</p>` : ''}
<p>Quý khách vui lòng phản hồi email hoặc liên hệ chúng tôi nếu cần điều chỉnh. Rất mong được hợp tác cùng Quý khách.</p>
<p>Trân trọng cảm ơn,</p>
${(sig != null && String(sig).trim()) ? sig : SIGNATURE}`;
}

/* ---- full printable A4 quote document (BẢNG BÁO GIÁ) ---- */
function quoteHTML(q, cust, rep, branding){
  const tot = quoteTotals(q);
  const co = docCompany(branding);
  const S = window.AppSettings || {};
  const esc = (x) => String(x == null ? '' : x).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  const docTitle = (S.quoteDocTitle || '').trim() || 'BẢNG BÁO GIÁ';
  const docSubtitle = (S.quoteDocSubtitle != null ? S.quoteDocSubtitle : 'QUOTATION');   // tên tiếng Anh dưới tiêu đề
  const defaultTerms = '<ul><li>Đơn giá trên đã bao gồm thuế VAT theo quy định.</li></ul>';
  const rawTerms = (S.quoteTerms != null ? S.quoteTerms : defaultTerms);
  const termsIsHtml = /<[a-z][\s\S]*>/i.test(rawTerms);   // rich (RichEditor) vs plain lines (cũ)
  const footerNote = (S.quoteFooterNote || '').trim();
  const payMethod = (S.quotePaymentMethod != null ? S.quotePaymentMethod : 'Tạm ứng 50% khi ký hợp đồng, 50% sau khi nghiệm thu & bàn giao.').trim();
  const dmy = (s) => { const p = String(s || '').split('-'); return p.length === 3 ? p[2] + '/' + p[1] + '/' + p[0] : (s || ''); };
  const mark = (co.short || 'SWO').replace(/[^A-Za-z]/g, '').slice(0, 3).toUpperCase() || 'SWO';
  const markEl = co.logo ? `<img class="mark-img" src="${co.logo}" alt="logo">` : `<div class="mark">${mark}<i></i></div>`;
  const d = new Date(q.issueDate);
  const dateLine = 'TP. Hồ Chí Minh, ngày ' + String(d.getDate()).padStart(2, '0') + ' tháng ' + String(d.getMonth() + 1).padStart(2, '0') + ' năm ' + d.getFullYear();
  // Mẫu v2 (2 cột): khung chung (header 2 vùng · title/sub · điều khoản dạng mục · footer · config); thân báo giá giữ riêng.
  const V = docTpl(S, 'quote', { title:docTitle, subtitle:docSubtitle, terms:rawTerms, footer:(footerNote || DEFAULT_DOC_FOOTER) });
  const logoSz = (V.header || {}).logoSize;
  const vars = docVars({ co, party:cust, docNo:q.code, date:dateLine });
  const renderRP = (src) => /<[a-z][\s\S]*>/i.test(src || '') ? renderRichVars(src, vars, co, logoSz) : docTextToHtml(src, vars);
  const itemRows = q.items.map((it, i) => {
    const line = it.qty * it.price;
    return `<tr>
      <td style="text-align:center">${i + 1}</td>
      <td>${esc(it.name)}${it.showDetail && it.detail ? '<div class="idetail">' + esc(it.detail) + '</div>' : ''}</td>
      <td style="text-align:center">${it.unit}</td>
      <td class="num">${it.qty}</td>
      <td class="num">${vnd(it.price).replace('₫', '')}</td>
      <td class="num">${it.vat}%</td>
      <td class="num">${vnd(line).replace('₫', '')}</td>
    </tr>`;
  }).join('');
  const discRow = tot.discount > 0 ? `<tr><td colspan="6" class="num lbl">Chiết khấu (${tot.discPct}%)</td><td class="num" style="color:#C0392B">- ${vnd(tot.discount).replace('₫', '')}</td></tr>` : '';
  return `<!DOCTYPE html><html lang="vi"><head><meta charset="utf-8"><title>${q.code}</title>
<style>
/* FORMAT TRANG IN (A4) — OCD: @page A4 16mm; cỡ chữ NỀN 13px trên body, mọi cỡ chữ body THEO em
   (scale theo nền) → KHÔNG nhảy font. Tiêu đề công ty hiện 1 LẦN (trang 1); header cột bảng tự lặp
   mỗi trang (thead) → trang 2+ KHÔNG bị margin-top cao do lặp letterhead. */
@page{size:A4;margin:16mm}
${DOC_HEAD2_CSS}
*{box-sizing:border-box}
html{background:#fff;color-scheme:light}
body{font-family:${docFontStack(co)};color:#0A1730;background:#fff;font-size:13px;line-height:1.55;margin:0;color-scheme:light}
.doc{max-width:740px;margin:0 auto;padding:8px}
@media print{.doc{max-width:none;margin:0;padding:0}}
.terms,.paybox,.qline,.sign,.to,.words{break-inside:avoid}
table.items tr{break-inside:avoid}
table.items thead{display:table-header-group}
.head{display:flex;justify-content:space-between;align-items:flex-start;border-bottom:2px solid #1466E0;padding-bottom:10px;margin-bottom:6px}
.co{display:flex;gap:10px;align-items:center}
.mark{width:40px;height:40px;border-radius:11px;background:linear-gradient(135deg,#12C5C0,#1466E0);color:#fff;display:flex;flex-direction:column;align-items:center;justify-content:center;font-weight:800;letter-spacing:.5px}
.mark i{width:60%;height:2px;background:rgba(255,255,255,.9);border-radius:1px;margin-top:2px;display:block}
.mark-img{width:46px;height:46px;object-fit:contain;border-radius:8px}
.co .n{font-weight:700;font-size:1.05em}
.co .m{font-size:.82em;color:#54657F}
.meta{text-align:right;font-size:.82em;color:#54657F}
.meta b{color:#0A1730}
.meta-line{margin:2px 0 12px}
.cl-title{font-weight:700;margin:8px 0 3px;color:#0A1730}
.cl-body{margin:0}
.terms .clause{margin:6px 0}
h1{text-align:center;font-size:1.6em;letter-spacing:1px;margin:18px 0 2px;color:#1466E0}
.sub{text-align:center;font-size:.85em;color:#54657F;margin-bottom:16px}
.to{background:#F6F9FD;border:1px solid #D2E1FD;border-radius:8px;padding:11px 14px;margin-bottom:14px}
.row{display:flex;margin:2px 0}
.row .k{width:130px;color:#54657F}
.row .v{font-weight:600;flex:1}
.intro{margin:0 0 10px}
table.items{width:100%;border-collapse:collapse;margin:8px 0}
table.items th,table.items td{border:1px solid #D0D5DD;padding:7px 9px;text-align:left;vertical-align:top}
table.items th{background:#EAF1FE;font-weight:700;text-align:center}
table.items td.num,table.items th.num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
table.items td.lbl{color:#54657F;font-weight:600}
.idetail{font-size:.85em;color:#54657F;margin-top:2px;line-height:1.35}
table.items tfoot td{font-weight:600}
.totalrow td{background:#F6F9FD;font-weight:800}
.totalrow td.num{color:#1466E0;font-size:1.05em}
.words{font-style:italic;margin:8px 0 14px;color:#0A1730}
.terms{border-left:3px solid #12C5C0;background:#F6F9FD;padding:10px 12px;margin:12px 0}
.terms b{display:block;margin-bottom:4px}
.terms ul{margin:2px 0 0;padding-left:18px}
.qline{margin:10px 0;border-left:3px solid #12C5C0;background:#F6F9FD;padding:9px 12px;border-radius:4px}
.qline b,.paybox b{color:#0A1730}
.paybox{border-left:3px solid #1466E0;background:#F6F9FD;padding:10px 12px;margin:10px 0;border-radius:4px;line-height:1.75}
.paybox b{display:block;margin-bottom:3px}
.sign{display:flex;justify-content:space-between;margin-top:30px;text-align:center}
.sign>div{width:46%}
.sign .role{font-weight:700}
.sign .note{font-size:.82em;color:#54657F;font-style:italic}
.sign .space{height:60px}
.foot{margin-top:22px;border-top:1px solid #E3E6EC;padding-top:8px;font-size:.8em;color:#98A2B3;text-align:center}
</style></head>
<body><div class="doc" style="${docTplStyle(co, V)}">
  ${docHeader2(co, S, V.header, vars)}
  <div class="meta meta-line">Số: <b>${q.code}</b> · ${dateLine}</div>

  <h1>${esc(V.title || docTitle)}</h1>
  ${(V.subtitle || '').trim() ? '<div class="sub">' + esc(V.subtitle) + '</div>' : ''}

  <div class="to">
    <div class="row"><div class="k">Kính gửi:</div><div class="v">${cust.name || '—'}</div></div>
    <div class="row"><div class="k">Mã số thuế:</div><div class="v">${cust.mst || '—'}</div></div>
    <div class="row"><div class="k">Địa chỉ:</div><div class="v">${esc(cust.address || '—')}</div></div>
    <div class="row"><div class="k">Người liên hệ:</div><div class="v">${rep.name || '—'}${rep.title ? ' — ' + rep.title : ''}</div></div>
  </div>

  <p class="intro">${co.short} trân trọng gửi đến Quý khách bảng báo giá cho các hạng mục sản phẩm/dịch vụ như sau:</p>

  <table class="items">
    <thead><tr><th style="width:32px">STT</th><th>Hạng mục</th><th style="width:48px">ĐVT</th><th class="num" style="width:40px">SL</th><th class="num" style="width:96px">Đơn giá</th><th class="num" style="width:52px">VAT</th><th class="num" style="width:104px">Thành tiền</th></tr></thead>
    <tbody>${itemRows}</tbody>
    <tfoot>
      <tr><td colspan="6" class="num lbl">Cộng tiền hàng</td><td class="num">${vnd(tot.sub).replace('₫', '')}</td></tr>
      ${discRow}
      <tr><td colspan="6" class="num lbl">Thuế VAT</td><td class="num">${vnd(tot.vat).replace('₫', '')}</td></tr>
      <tr class="totalrow"><td colspan="6" class="num">TỔNG CỘNG (VNĐ)</td><td class="num">${vnd(tot.total).replace('₫', '')}</td></tr>
    </tfoot>
  </table>
  <div class="words">Bằng chữ: ${docTienChu(tot.total)}.</div>

  <!-- 1. Hiệu lực báo giá -->
  <div class="qline"><b>Hiệu lực báo giá:</b> ${dmy(q.validUntil)} (${q.validDays} ngày từ ngày phát hành)</div>

  <!-- 2. Điều khoản và ghi chú -->
  <div class="terms"><b>Điều khoản và ghi chú</b>
    ${Array.isArray(V.terms)
      ? V.terms.map(tm => `<div class="clause">${(tm.title || '').trim() ? `<div class="cl-title">${esc(tm.title)}</div>` : ''}<div class="cl-body">${renderRP(tm.content || '')}</div></div>`).join('')
      : (termsIsHtml ? '<div class="terms-body">' + renderRP(rawTerms) + '</div>' : '<ul>' + rawTerms.split('\n').map(x => x.trim()).filter(Boolean).map(t => '<li>' + esc(t) + '</li>').join('') + '</ul>')}
    ${q.notes ? '<div style="margin-top:6px">' + esc(q.notes) + '</div>' : ''}
  </div>

  <!-- 3. Hình thức thanh toán -->
  ${payMethod ? '<div class="qline"><b>Hình thức thanh toán:</b> ' + esc(payMethod) + '</div>' : ''}

  <!-- 4. Tài khoản thanh toán -->
  <div class="paybox"><b>Tài khoản thanh toán</b>Ngân hàng: ${co.bank}<br>Số tài khoản: ${co.stk}<br>Chủ tài khoản: ${co.name}</div>

  <div class="sign">
    <div><div class="role">KHÁCH HÀNG</div><div class="note">(Ký, ghi rõ họ tên)</div><div class="space"></div></div>
    <div><div class="role">ĐẠI DIỆN ${co.short}</div><div class="note">(Ký, đóng dấu)</div><div class="space"></div></div>
  </div>

  ${(V.footer || '').trim() ? '<div class="foot">' + renderRP(V.footer) + '</div>' : ''}
</div></body></html>`;
}

/* ---- preview / print modal ---- */
function QuoteDocModal({ quote, onClose }){
  const p = usePortal();
  const { t, lang } = useLang();
  const toast = useToast();
  const cust = (window.CustomerStore && CustomerStore.byCode(quote.customer)) || {};
  const rep = quote.rep && window.ContactStore ? (ContactStore.byCode(quote.rep) || {}) : {};
  const html = quoteHTML(quote, cust, rep, p.branding);
  const doPrint = () => { if (!printDocument(html)) toast(lang === 'vi' ? 'Trình duyệt chặn cửa sổ in — hãy cho phép pop-up' : 'Pop-up blocked — allow pop-ups', 'error'); };
  return (
    <Modal title={t('bg.preview') + ' · ' + quote.code} width={800} onClose={onClose}
      footer={<><div className="grow flex g8 items-center"><span className="subtle">{t('hd.total')}: </span><span className="mono" style={{ fontWeight:700, color:'var(--primary)' }}>{vnd(quoteTotals(quote).total)}</span></div>
        <button className="btn" onClick={onClose}>{t('c.close')}</button>
        <button className="btn btn-primary" onClick={doPrint}><i className="bi bi-filetype-pdf"></i>{t('hd.genPdf')}</button></>}>
      <div style={{ marginBottom:8 }} className="flex g8 items-center subtle"><i className="bi bi-eye"></i>{t('hd.preview')}</div>
      <iframe title="quote-preview" srcDoc={html} style={{ width:'100%', height:'58vh', border:'1px solid var(--border)', borderRadius:'var(--r-md)', background:'#fff' }}></iframe>
    </Modal>
  );
}

/* ---- Sinh PDF báo giá (client-side, html2pdf) → { blob, b64, name }. Render trong iframe ẩn để cách ly CSS. ---- */
async function quoteToPdf(quote, cust, rep, branding){
  if (!window.html2pdf) throw new Error('Chưa tải được trình tạo PDF (kiểm tra mạng)');
  const html = quoteHTML(quote, cust, rep, branding);
  const name = 'BaoGia_' + quote.code + '.pdf';
  const ifr = document.createElement('iframe');
  ifr.style.cssText = 'position:fixed;left:-9999px;top:0;width:794px;height:1123px;border:0';
  document.body.appendChild(ifr);
  ifr.srcdoc = html;
  await new Promise(res => { ifr.onload = res; setTimeout(res, 1000); });
  try {
    const doc = ifr.contentDocument;
    // FIX khớp báo giá hệ thống: html2canvas chỉ clone subtree <body> nên CSS trong <head> bị BỎ
    // khi rasterize → PDF đính kèm mất viền/nền/màu + logo phình to (khác hẳn bản xem/in của hệ thống).
    // → Nhúng bản sao <style>/<link> của <head> vào ĐẦU <body> để CSS đi theo phần được chụp.
    doc.querySelectorAll('head style, head link[rel="stylesheet"]').forEach(s => doc.body.insertBefore(s.cloneNode(true), doc.body.firstChild));
    // Chốt cứng kích thước logo (phòng trường hợp CSS chưa kịp áp) — tránh logo to choán cả trang.
    doc.querySelectorAll('.mark-img').forEach(im => { im.setAttribute('width', '46'); im.setAttribute('height', '46'); });
    // Đợi ảnh (logo) tải xong để bản chụp không bị trắng/lệch.
    await Promise.all(Array.from(doc.images || []).map(im => im.complete ? null : new Promise(r => { im.onload = im.onerror = r; })));
    const opt = { margin:0, image:{ type:'jpeg', quality:0.95 }, html2canvas:{ scale:2, backgroundColor:'#ffffff', useCORS:true }, jsPDF:{ unit:'mm', format:'a4', orientation:'portrait' } };
    const blob = await window.html2pdf().set(opt).from(doc.body).outputPdf('blob');
    const b64 = await new Promise((res, rej) => { const r = new FileReader(); r.onload = () => res(String(r.result).split(',')[1] || ''); r.onerror = rej; r.readAsDataURL(blob); });
    return { blob, b64, name };
  } finally { ifr.remove(); }
}
/* ---- Link "xem trực tuyến" do CHÍNH HỆ THỐNG tự sinh (KHÔNG dùng Supabase) ----
   Nhúng HTML báo giá đã render vào hash của trang xem (base64url, UTF-8) → link self-contained,
   mở được ở MỌI nơi mà KHÔNG cần backend/đăng nhập (trang quote-view.html tự hiển thị + cho tải PDF).
   Logo dạng data-URI (nặng) được thay bằng logo hệ thống (/favicon-v2.svg) để link gọn — PDF đính
   kèm vẫn giữ logo gốc. */
function quoteOnlineUrl(quote, cust, rep, branding, opts){
  try {
    opts = opts || {};
    const origin = (window.location && location.origin && location.origin !== 'null') ? location.origin : 'https://my.swo.vn';
    const html = quoteHTML(quote, cust, rep, branding)
      .replace(/(<img class="mark-img"[^>]*\bsrc=")data:[^"]*(")/i, '$1' + origin + '/favicon-v2.svg$2')
      .replace(/>\s+</g, '><');
    const b64 = btoa(unescape(encodeURIComponent(html))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
    let url = origin + '/quote-view.html#q=' + b64;
    // Kèm token bí mật → trang xem online hiện nút "Xác nhận báo giá" (ghi nhận qua RPC quote_confirm).
    if (opts.token) url += '&t=' + encodeURIComponent(opts.token) + '&c=1' + (opts.scan ? '&s=1' : '');
    return url;
  } catch (e) { console.warn('[quoteOnlineUrl]', e && e.message); return null; }
}

/* ---- the Quotes → Email bridge: send the quote straight to the customer ---- */
function SendQuoteModal({ quote, onClose }){
  const p = usePortal();
  const { t, lang } = useLang();
  const toast = useToast();
  const cust = (window.CustomerStore && CustomerStore.byCode(quote.customer)) || {};
  const rep = quote.rep && window.ContactStore ? (ContactStore.byCode(quote.rep) || {}) : {};
  const [to, setTo] = React.useState(rep.email ? [rep.email] : []);
  const [cc, setCc] = React.useState([]);
  const [subject, setSubject] = React.useState('Báo giá ' + quote.code + ' — ' + (cust.shortName || cust.name || '') + ' | S.W.O Technology');
  const suggestions = CONTACTS.map(c => c.email);
  const validEmail = s => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
  // Chữ ký = namecard người gửi (kèm QR vCard); fallback chữ ký HTML mặc định nếu chưa có encoder.
  const sig = window.namecardEmailHTML ? namecardEmailHTML(loadNamecard(p.me, p.branding), p.branding) : p.mailSettings.signature;
  const body = quoteEmailBody(quote, cust, rep, p.branding, sig);   // preview (link online thêm khi gửi)
  const attachName = 'BaoGia_' + quote.code + '.pdf';
  const realSend = !!(window.GMail && GMail.configured());
  const [sending, setSending] = React.useState(false);

  const check = () => {
    if (!to.length) { toast(lang === 'vi' ? 'Chưa có người nhận' : 'No recipient', 'error'); return false; }
    if (!to.every(validEmail)) { toast(lang === 'vi' ? 'Email người nhận không hợp lệ' : 'Invalid recipient email', 'error'); return false; }
    return true;
  };
  // Gói gửi: sinh PDF báo giá (đính kèm thật) + link xem trực tuyến do hệ thống tự sinh (không Supabase).
  const buildPackage = async () => {
    const token = p.ensureQuoteToken ? p.ensureQuoteToken(quote.code) : (quote.confirmToken || '');   // mã xác nhận online
    const sFlag = (p.appSettings || {}).quoteConfirmAskScan;
    const scan = sFlag === true || sFlag === '1' || sFlag === 1;
    const pdf = await quoteToPdf(quote, cust, rep, p.branding);
    const url = quoteOnlineUrl(quote, cust, rep, p.branding, { token, scan });
    const finalBody = quoteEmailBody(quote, cust, rep, p.branding, sig, url);
    return { url, finalBody, att:{ name:pdf.name, type:'application/pdf', size:pdf.blob.size, b64:pdf.b64 } };
  };
  // Send directly: sinh PDF + host + gửi thật qua Gmail (nếu cấu hình) + log + flip "Đã gửi".
  const sendNow = async () => {
    if (!check()) return;
    setSending(true);
    let pkg;
    try { pkg = await buildPackage(); }
    catch (e) { setSending(false); toast((lang === 'vi' ? 'Lỗi tạo PDF báo giá: ' : 'PDF error: ') + (e.message || e), 'error'); return; }
    if (realSend) {
      try { await GMail.send({ to, cc, subject, html:pkg.finalBody, from:p.me.email, hint:p.me.email, attachments:[pkg.att] }); }
      catch (e) { setSending(false); toast((lang === 'vi' ? 'Gửi thất bại: ' : 'Send failed: ') + (e.message || e), 'error'); return; }
    }
    setSending(false);
    p.addSentEmail({ id:'e' + Date.now(), to, cc, subject, date:new Date().toISOString().slice(0, 16).replace('T', ' '), status:'sent', html:pkg.finalBody, attach:[pkg.att.name] });
    p.markQuoteSent(quote.code, to);
    toast(lang === 'vi' ? 'Đã gửi báo giá ' + quote.code + ' (kèm PDF) tới khách hàng' : 'Quote ' + quote.code + ' sent (with PDF)', 'success');
    onClose();
    p.navigate({ name:'module', module:'quotes', sub:'detail', id:quote.code });
  };
  // Or hand off to the Email composer (To/Cc/subject/body + PDF thật + link điền sẵn).
  const openComposer = async () => {
    if (!check()) return;
    setSending(true);
    let pkg;
    try { pkg = await buildPackage(); }
    catch (e) { setSending(false); toast((lang === 'vi' ? 'Lỗi tạo PDF báo giá: ' : 'PDF error: ') + (e.message || e), 'error'); return; }
    setSending(false);
    p.markQuoteSent(quote.code, to);
    p.quick('mail', { to, cc, subject, body:pkg.finalBody, attach:[pkg.att], quoteCode:quote.code });
    onClose();
  };

  return (
    <Modal title={t('bg.send') + ' · ' + quote.code} width={680} onClose={onClose}
      footer={<><span className="grow subtle flex g8 items-center" style={{ fontSize:'var(--fs-sm)' }}><i className={'bi ' + (realSend ? 'bi-shield-check' : 'bi-shield-lock')} style={realSend ? { color:'var(--success)' } : null}></i>{realSend ? (lang === 'vi' ? 'Gửi thật từ ' : 'Sends from ') + p.me.email : (lang === 'vi' ? 'Mô phỏng (chưa cấu hình Gmail)' : 'Mock (Gmail not configured)')}</span>
        <button className="btn" onClick={onClose} disabled={sending}>{t('c.cancel')}</button>
        <button className="btn" onClick={openComposer} disabled={sending}><i className="bi bi-pencil-square"></i>{t('bg.openComposer')}</button>
        <button className="btn btn-primary" onClick={sendNow} disabled={sending}>{sending ? <><i className="bi bi-arrow-repeat spin"></i>{lang === 'vi' ? 'Đang gửi…' : 'Sending…'}</> : <><i className="bi bi-send"></i>{t('bg.sendNow')}</>}</button></>}>
      <div className="col g14">
        <div className="flex g12 items-center" style={{ background:'var(--bg-subtle)', padding:'8px 12px', borderRadius:'var(--r-md)' }}>
          <Avatar name={cust.shortName || cust.name} size={34} square color="var(--primary)" />
          <div className="grow"><div style={{ fontWeight:600 }}>{cust.name}</div><div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{rep.name ? rep.name + (rep.title ? ' · ' + rep.title : '') : (lang === 'vi' ? 'Chưa có người liên hệ' : 'No contact')}</div></div>
          <span className="mono" style={{ fontWeight:700, color:'var(--primary)' }}>{vnd(quoteTotals(quote).total)}</span>
        </div>
        <Field label={t('mail.from')}><input className="input" value={p.me.email} disabled style={{ background:'var(--bg-sunken)' }} /></Field>
        <Field label={t('mail.to')}><ChipInput value={to} onChange={setTo} suggestions={suggestions} listId="bg-to-sug" placeholder={lang === 'vi' ? 'Nhập email khách hàng' : 'Customer email'} /></Field>
        <Field label={t('mail.cc')}><ChipInput value={cc} onChange={setCc} suggestions={suggestions} listId="bg-cc-sug" placeholder="Cc…" /></Field>
        <Field label={t('mail.subject')}><input className="input" value={subject} onChange={e => setSubject(e.target.value)} /></Field>
        <div className="flex g8 items-center wrap"><span className="badge badge-neutral"><i className="bi bi-paperclip"></i>{attachName}</span><span className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{lang === 'vi' ? 'tự tạo PDF + đính kèm + link xem online (khi gửi)' : 'auto PDF + attach + online link (on send)'}</span></div>
        <Field label={t('bg.emailPreview')}>
          <div className="mail-html" style={{ maxHeight:200, overflow:'auto', border:'1px solid var(--border)', borderRadius:'var(--r-md)', padding:'10px 12px', background:'var(--bg)' }} dangerouslySetInnerHTML={{ __html:body }} />
        </Field>
      </div>
    </Modal>
  );
}

Object.assign(window, { quoteHTML, quoteEmailBody, quoteSummaryTableHTML, quoteToPdf, quoteOnlineUrl, QuoteDocModal, SendQuoteModal });
