/* global React, Icon, fmt, fmtDate, useToast, exportExcel */
// ============================================================
// Unified report system — used by ProjectDetail, ContractDetail,
// HouseDetail, ReportsScreen. All reports follow the same
// hierarchical principle:
//   PROJECT → CONTRACT (by date) → HOUSE (by code) → PAYMENTS
// Font: TH Sarabun New · Color: all black · with progress popup.
// ============================================================
const { useState: useStateR } = React;

const tick = (ms = 60) => new Promise(r => setTimeout(r, ms));

// ---------- Unit labels (per-project unit_type) ----------
const UNIT_LABELS = {
  house: { singular: "บ้าน",  counter: "หลัง",  code: "รหัสบ้าน",  addOne: "เพิ่มบ้าน",  addBulk: "เพิ่มบ้านแบบกลุ่ม",  delete: "ลบบ้าน",  list: "บ้านในสัญญา",  history: "ประวัติบ้าน" },
  room:  { singular: "ห้อง",  counter: "ห้อง",  code: "รหัสห้อง",  addOne: "เพิ่มห้อง",  addBulk: "เพิ่มห้องแบบกลุ่ม",  delete: "ลบห้อง",  list: "ห้องในสัญญา",  history: "ประวัติห้อง" },
  floor: { singular: "ชั้น",  counter: "ชั้น",  code: "รหัสชั้น",  addOne: "เพิ่มชั้น",  addBulk: "เพิ่มชั้นแบบกลุ่ม",  delete: "ลบชั้น",  list: "ชั้นในสัญญา",  history: "ประวัติชั้น" },
  unit:  { singular: "ยูนิต", counter: "ยูนิต", code: "รหัสยูนิต", addOne: "เพิ่มยูนิต", addBulk: "เพิ่มยูนิตแบบกลุ่ม", delete: "ลบยูนิต", list: "ยูนิตในสัญญา", history: "ประวัติยูนิต" },
};
function unitLabel(unitType) {
  return UNIT_LABELS[unitType || "house"] || UNIT_LABELS.house;
}
const UNIT_TYPE_OPTIONS = [
  { value: "house", label: "บ้าน / หลัง" },
  { value: "room",  label: "ห้อง" },
  { value: "floor", label: "ชั้น" },
  { value: "unit",  label: "ยูนิต / อาคาร" },
];
Object.assign(window, { unitLabel, UNIT_LABELS, UNIT_TYPE_OPTIONS });

// ---------- helpers ----------
function _fmt(n) { return (Number(n) || 0).toLocaleString("en-US"); }
function _esc(s) { return String(s == null ? "" : s).replace(/[&<>"']/g, m => ({ "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;" }[m])); }

// ---------- load payments filtered by scope ----------
async function loadScopedPayments(scope, onProgress) {
  const state = window.FB.state;
  const projectIds = scope.type === "all"
    ? Object.keys(state.contracts)
    : [scope.projectId].filter(Boolean);

  let total = 0, done = 0;
  for (const pid of projectIds) {
    for (const c of (state.contracts[pid] || [])) {
      if (scope.type === "contract" && c.id !== scope.contractId) continue;
      const houses = state.houses[c.id] || [];
      for (const h of houses) {
        if (scope.type === "house" && h.id !== scope.houseId) continue;
        total++;
      }
    }
  }
  if (total === 0) return;

  for (const pid of projectIds) {
    for (const c of (state.contracts[pid] || [])) {
      if (scope.type === "contract" && c.id !== scope.contractId) continue;
      const houses = state.houses[c.id] || [];
      for (const h of houses) {
        if (scope.type === "house" && h.id !== scope.houseId) continue;
        try { await window.FB.loadPayments(pid, c.id, h.id); } catch(e) {}
        done++;
        if (onProgress) onProgress(done, total);
      }
    }
  }
}

// ---------- build hierarchical data from window.FB.state ----------
function buildHierarchicalData(scope) {
  const s = window.FB.state;
  const allProjects = s.projects || [];
  const allContracts = s.contracts || {};
  const allHouses = s.houses || {};
  const allPayments = s.payments || {};

  let projects = allProjects;
  if (scope.type !== "all") {
    projects = projects.filter(p => p.id === scope.projectId);
  }

  return projects.map(p => {
    let contracts = (allContracts[p.id] || []).slice();
    if (scope.type === "contract" || scope.type === "house") {
      contracts = contracts.filter(c => c.id === scope.contractId);
    }
    contracts.sort((a, b) => {
      const da = a.contract_date || "", db = b.contract_date || "";
      return da > db ? 1 : da < db ? -1 : 0;
    });
    const contractsOut = contracts.map(c => {
      let houses = (allHouses[c.id] || []).slice();
      if (scope.type === "house") houses = houses.filter(h => h.id === scope.houseId);
      houses.sort((a, b) => (a.house_code || "").localeCompare(b.house_code || "", undefined, { numeric: true, sensitivity: "base" }));
      const housesOut = houses.map(h => ({
        ...h,
        payments: (allPayments[h.id] || []).slice().sort((a, b) => (a.date > b.date ? 1 : a.date < b.date ? -1 : 0)),
      }));
      return { ...c, houses: housesOut };
    });
    return { ...p, contracts: contractsOut };
  });
}

// ---------- aggregate totals ----------
function aggregate(projects) {
  let value = 0, paid = 0, nC = 0, nH = 0, nP = 0;
  for (const p of projects) {
    value += p.value_total || 0;
    paid  += p.paid_total  || 0;
    for (const c of p.contracts) {
      nC++;
      for (const h of c.houses) {
        nH++;
        nP += h.payments.length;
      }
    }
  }
  return { value, paid, remain: value - paid, projects: projects.length, contracts: nC, houses: nH, payments: nP };
}

// ============================================================
// HTML generators
// ============================================================
const FONT_LINK = `<link rel="stylesheet" href="https://fonts.cdnfonts.com/css/th-sarabun-new">`;

const REPORT_CSS = `
:root{
  --font:'TH Sarabun New','Sarabun',sans-serif;
  --ink:#1a1a1a; --text:#1a1a1a; --text-2:#6b7280;
  --line:#e3e6ea; --line-2:#c4c9d0; --rule:#1a1a1a;
  --ok:#137a3b; --warn:#9a6411; --bad:#b22626;
}
@page{
  size:A4;
  margin:9mm 10mm 11mm 10mm;
  @bottom-right{
    content:"หน้า " counter(page) " / " counter(pages);
    font-family:var(--font); font-size:12px; color:var(--text-2);
  }
}
*{margin:0;padding:0;box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{font-family:var(--font);font-size:15.5px;line-height:1.5;color:var(--text);background:#fff;padding:18px 22px}
.num,.r{font-variant-numeric:tabular-nums}

/* ---------- masthead ---------- */
.masthead{display:flex;justify-content:space-between;align-items:flex-end;gap:18px;
  border-bottom:2px solid var(--rule);padding-bottom:7px;margin-bottom:16px}
.mast-left{min-width:0}
.eyebrow{font-size:12.5px;letter-spacing:2px;color:var(--text-2);text-transform:uppercase;margin-bottom:1px}
.mast-title{font-size:26px;font-weight:700;color:var(--ink);line-height:1.12}
.mast-sub{font-size:14.5px;color:var(--text-2);margin-top:2px}
.mast-right{text-align:right;flex-shrink:0}
.docbox-lbl{font-size:11px;letter-spacing:.5px;color:var(--text-2);text-transform:uppercase}
.docbox-ref{font-size:15px;font-weight:700;color:var(--ink);font-variant-numeric:tabular-nums;margin-bottom:4px}
.mast-date{font-size:12.5px;color:var(--text-2);line-height:1.35}

/* ---------- KPI band (เส้นบาง ไม่มีกล่อง) ---------- */
.kpi{display:flex;gap:0;margin-bottom:18px;padding-bottom:2px}
.kpi-cell{flex:1;padding:0 20px}
.kpi-cell + .kpi-cell{border-left:1px solid var(--line)}
.kpi-cell:first-child{padding-left:0}
.kpi-lbl{font-size:12.5px;color:var(--text-2);margin-bottom:2px}
.kpi-val{font-size:23px;font-weight:700;color:var(--ink);font-variant-numeric:tabular-nums;line-height:1.1}
.kpi-val.ok{color:var(--ok)} .kpi-val.warn{color:var(--warn)} .kpi-val.bad{color:var(--bad)}
.kpi-sub{font-size:12.5px;color:var(--text-2);margin-top:2px}

/* ---------- status (ตัวอักษรสี + จุด ไม่ใช่กล่อง) ---------- */
.badge{font-size:13.5px;font-weight:600;white-space:nowrap}
.badge::before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;
  margin-right:5px;vertical-align:1px}
.badge.ok{color:var(--ok)} .badge.ok::before{background:var(--ok)}
.badge.warn{color:var(--warn)} .badge.warn::before{background:var(--warn)}
.badge.bad{color:var(--bad)} .badge.bad::before{background:var(--bad)}
.badge.none{color:var(--text-2)} .badge.none::before{background:var(--text-2)}

/* ---------- project head (ไม่มีพื้นทึบ) ---------- */
.proj-head{padding:0 0 6px;margin-bottom:12px;border-bottom:2px solid var(--rule)}
.proj-name{font-size:21px;font-weight:700;color:var(--ink);line-height:1.18}
.proj-meta{font-size:13.5px;color:var(--text-2);margin-top:1px}
.proj-figs{display:flex;flex-wrap:wrap;gap:8px 28px;margin-top:7px}
.proj-figs span{font-size:15.5px;font-weight:700;color:var(--ink);font-variant-numeric:tabular-nums}
.proj-figs i{font-style:normal;font-weight:400;font-size:13px;color:var(--text-2);margin-right:5px}

/* ---------- contract head (ไม่มีพื้นทึบ) ---------- */
.contract-blk{margin-bottom:14px}
.contract-head{display:flex;justify-content:space-between;align-items:flex-end;gap:14px;
  padding:8px 0 5px;margin-top:2px;border-bottom:1px solid var(--line-2)}
.ch-num{font-size:17px;font-weight:700;color:var(--ink)}
.ch-desc{font-size:14.5px;color:var(--text)}
.ch-meta{font-size:13px;color:var(--text-2);margin-top:1px}
.ch-right{text-align:right;flex-shrink:0;display:flex;flex-wrap:wrap;justify-content:flex-end;gap:2px 16px;max-width:62%}
.ch-fig{font-size:14px;font-weight:700;color:var(--ink);font-variant-numeric:tabular-nums;white-space:nowrap}
.ch-fig i{font-style:normal;font-weight:400;font-size:12.5px;color:var(--text-2);margin-right:4px}

/* ---------- main table (เส้นแนวนอนอย่างเดียว) ---------- */
table.tbl{width:100%;border-collapse:collapse;font-size:14.5px}
table.tbl thead th{background:#fff;color:var(--ink);font-weight:700;font-size:13px;
  text-align:left;padding:5px 10px 4px;border:0;border-bottom:1.5px solid var(--rule);
  text-transform:none;letter-spacing:.2px}
table.tbl td{padding:4px 10px;border:0;vertical-align:top}
table.tbl .r{text-align:right;font-variant-numeric:tabular-nums}
table.tbl .c{text-align:center}
.neg{color:var(--bad);font-weight:700}
.pos{color:var(--ok);font-weight:700}
tr.house td{background:#fff;font-weight:700;padding-top:5px;border-top:1px solid var(--line)}
tbody.house-grp:first-of-type tr.house td{border-top:0}
tr.house .unit{font-weight:400}
.ucode{font-weight:700;font-size:15.5px;color:var(--ink);margin-right:8px}
tr.pay td{padding:0 0 3px}
.pays{padding:1px 0 2px 20px;margin-left:9px;border-left:1px solid var(--line-2)}
.pay-line{display:flex;flex-wrap:wrap;gap:10px;align-items:baseline;font-size:13.5px;padding:1px 10px 1px 0}
.pay-n{font-weight:600;color:var(--text-2);min-width:50px}
.pay-d{color:var(--text)}
.pay-inv{color:var(--text-2)}
.pay-amt{color:var(--ink);font-weight:700;font-variant-numeric:tabular-nums;margin-left:auto}
.pay-note{flex-basis:100%;color:var(--warn);font-size:13px;padding-left:50px}
.pay-empty{padding:1px 0 3px 20px;margin-left:9px;font-size:13.5px;color:var(--text-2);font-style:italic}
tr.totals td{background:#fff;color:var(--ink);font-weight:700;border-top:2px solid var(--rule);padding-top:5px}
td.empty{padding:12px 0;text-align:center;color:var(--text-2);font-style:italic}
.empty-blk{padding:14px;text-align:center;color:var(--text-2);font-style:italic;
  border:1px solid var(--line);border-radius:3px;margin-bottom:10px}
.sec-title{font-size:16px;font-weight:700;color:var(--ink);margin:16px 0 7px;
  padding-bottom:4px;border-bottom:2px solid var(--rule)}

/* ---------- house info (single house report) ---------- */
.house-info{display:flex;flex-wrap:wrap;gap:5px 28px;padding:0 0 8px;margin-bottom:4px;
  border-bottom:1px solid var(--line)}
.house-info span{font-size:14.5px;font-weight:700;color:var(--ink)}
.house-info i{font-style:normal;font-weight:400;color:var(--text-2);margin-right:5px;font-size:13px}

/* ---------- vertical timeline ---------- */
.timeline{margin:6px 0 8px 4px}
.tl-item{display:flex;gap:14px;position:relative}
.tl-marker{position:relative;width:12px;flex-shrink:0}
.tl-marker::before{content:"";position:absolute;left:5px;top:0;bottom:0;width:1.5px;background:var(--line-2)}
.tl-item:last-child .tl-marker::before{bottom:auto;height:8px}
.tl-dot{position:absolute;left:0;top:5px;width:11px;height:11px;border-radius:50%;
  background:#fff;border:2px solid var(--text-2);z-index:1}
.tl-dot.tl-dot-ok{border-color:var(--ok);background:var(--ok)}
.tl-body{flex:1;padding-bottom:12px;min-width:0}
.tl-head{display:flex;justify-content:space-between;align-items:baseline;gap:10px}
.tl-n{font-size:15.5px;font-weight:700;color:var(--ink)}
.tl-amt{font-size:15.5px;font-weight:700;color:var(--ink);font-variant-numeric:tabular-nums}
.tl-meta{font-size:13.5px;color:var(--text-2)}
.tl-note{font-size:13px;color:var(--warn);margin-top:1px}
.tl-total{font-weight:700;font-size:15px;padding:6px 0 0;border-top:2px solid var(--rule);
  text-align:right;font-variant-numeric:tabular-nums}

/* ---------- sign-off ---------- */
.signoff{margin-top:28px}
.signoff-title{font-size:14px;text-align:center;color:var(--text-2);margin-bottom:18px;letter-spacing:1px}
.sig-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:30px}
.sig-box{text-align:center}
.sig-space{height:44px}
.sig-line{border-top:1px solid var(--text-2);margin:0 4px 6px}
.sig-paren{font-size:13.5px;color:var(--text-2);margin-bottom:3px}
.sig-role{font-size:15px;font-weight:700;color:var(--ink);margin-bottom:3px}
.sig-date{font-size:12.5px;color:var(--text-2)}

/* ---------- footer ---------- */
.rpt-footer{margin-top:18px;border-top:1px solid var(--line);padding-top:5px;
  text-align:center;font-size:12.5px;color:var(--text-2)}

/* ---------- cover sheet (บีบให้พอดี A4 หน้าเดียว) ---------- */
.cover-sheet{break-inside:avoid;display:flex;flex-direction:column;min-height:274mm}
.cover-tail{margin-top:auto;padding-top:16px}
.cover-signoff{text-align:center}
.cover-signoff .signoff-title{font-size:14px;color:var(--text-2);letter-spacing:1px;margin-bottom:14px}
.cover-sig-one{width:300px;max-width:70%;margin:0 auto;text-align:center}
.cover-sheet .masthead{margin-bottom:10px;padding-bottom:6px}
.cover-num-wrap{text-align:center;margin:12px 0 2px}
.cover-num-lbl{font-size:15px;color:var(--text-2);letter-spacing:2px}
.cover-num{font-size:38px;font-weight:700;color:var(--ink);line-height:1.1;font-variant-numeric:tabular-nums}
.cover-desc{text-align:center;font-size:18px;color:var(--text);margin-bottom:10px}
.cover-info{width:100%;border-collapse:collapse;margin:6px 0 2px}
.cover-info td{padding:4px 10px;border-bottom:1px solid var(--line);font-size:16px;vertical-align:top}
.cover-info td.k{width:26%;color:var(--text-2)}
.cover-info td.v{font-weight:700;color:var(--ink)}
.baht-text{font-size:14.5px;color:var(--text-2);font-weight:400;margin-top:0}
.house-codes{font-weight:400;font-size:15px;line-height:1.55;margin-top:1px}
.cover-sheet .sec-title{margin:10px 0 4px;font-size:15px;padding-bottom:3px}
.chk-row{display:flex;flex-wrap:wrap;gap:6px 20px;padding:4px 2px 2px}
.chk{font-size:15px;display:flex;align-items:center;gap:7px;white-space:nowrap}
.chk::before{content:"";display:inline-block;width:14px;height:14px;border:1.5px solid var(--ink);border-radius:2px;flex-shrink:0}
.notes-line{margin:8px 0 2px;font-size:15px;display:flex;gap:8px;align-items:flex-end}
.notes-line .l{flex:1;border-bottom:1px dotted var(--line-2);height:20px}
/* พื้นที่ล่าง: ซ้าย=เอกสาร+หมายเหตุ · ขวา=QR */
.cover-bottom{display:flex;gap:22px;align-items:flex-start;margin-top:6px}
.cb-left{flex:1;min-width:0}
.cb-right{flex-shrink:0}
.cover-qr{display:flex;align-items:center;gap:12px;border:1px solid var(--line);border-radius:4px;padding:9px 12px}
.cover-qr .t{font-size:12.5px;color:var(--text-2);max-width:170px;line-height:1.35;word-break:break-all}
.cover-sheet .sig-space{height:44px}

/* ---------- page-break helper ---------- */
.pbreak{height:0;border:0;margin:0}

@media print{
  *{-webkit-print-color-adjust:exact;print-color-adjust:exact}
  body{padding:0}
  button{display:none}
  tr{break-inside:avoid}
  tbody.house-grp{break-inside:avoid}
  .pbreak{break-after:page}
  .contract-head,.proj-head,table.tbl thead{break-after:avoid}
  table.tbl thead{display:table-header-group}
  .signoff,.kpi,.masthead,.house-info,.tl-total{break-inside:avoid}
  .tl-item{break-inside:avoid}
}
`;

// ---------- แปลงจำนวนเงินเป็นคำอ่านภาษาไทย ----------
function bahtText(amount) {
  amount = Number(amount) || 0;
  const digits = ["", "หนึ่ง", "สอง", "สาม", "สี่", "ห้า", "หก", "เจ็ด", "แปด", "เก้า"];
  const places = ["", "สิบ", "ร้อย", "พัน", "หมื่น", "แสน"];

  // อ่านเลขไม่เกิน 6 หลัก · hasHigher = มีหลักที่สูงกว่า (ในกลุ่มล้านก่อนหน้า) เพื่อกฎ "เอ็ด"
  function readSix(numStr, hasHigher) {
    let out = "";
    const n = numStr.length;
    for (let i = 0; i < n; i++) {
      const d = +numStr[i];
      const pos = n - i - 1;
      if (!d) continue;
      if (pos === 1) {
        if (d === 1) out += "สิบ";
        else if (d === 2) out += "ยี่สิบ";
        else out += digits[d] + "สิบ";
      } else if (pos === 0) {
        out += (d === 1 && (n > 1 || hasHigher) && (out !== "" || hasHigher)) ? "เอ็ด" : digits[d];
      } else {
        out += digits[d] + places[pos];
      }
    }
    return out;
  }

  function readInt(numStr) {
    numStr = numStr.replace(/^0+/, "");
    if (!numStr) return "";
    let out = "", first = true;
    while (numStr.length > 6) {
      const head = numStr.slice(0, numStr.length - 6);
      out += readSix(head, !first) + "ล้าน";
      numStr = numStr.slice(-6).replace(/^0+/, "");
      first = false;
    }
    if (numStr) out += readSix(numStr, !first);
    return out;
  }

  const baht = Math.floor(Math.abs(amount));
  const satang = Math.round((Math.abs(amount) - baht) * 100);
  let out = "";
  if (baht === 0 && satang === 0) return "ศูนย์บาทถ้วน";
  if (baht > 0) out += readInt(String(baht)) + "บาท";
  if (satang > 0) out += readSix(String(satang), false) + "สตางค์";
  else out += "ถ้วน";
  return (amount < 0 ? "ลบ" : "") + out;
}

// ---------- shared design-system components ----------

// เลขที่เอกสารอัตโนมัติ: RPT-YYYYMMDD-HHMM
function autoRef() {
  const d = new Date();
  const p = (n) => String(n).padStart(2, "0");
  return `RPT-${d.getFullYear()}${p(d.getMonth()+1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}`;
}

// หัวรายงาน — noDate:true จะไม่แสดงวันที่จัดทำ (ใช้กับใบปะหน้าสัญญา)
function masthead({ eyebrow, title, sub, ref, noDate }) {
  const now = new Date();
  const dateStr = now.toLocaleDateString("th-TH", { year:"numeric", month:"long", day:"numeric" });
  const timeStr = now.toLocaleTimeString("th-TH", { hour:"2-digit", minute:"2-digit" });
  return `
  <div class="masthead">
    <div class="mast-left">
      <div class="eyebrow">${_esc(eyebrow || "ระบบบริหารสัญญาก่อสร้าง")}</div>
      <div class="mast-title">${_esc(title)}</div>
      ${sub ? `<div class="mast-sub">${_esc(sub)}</div>` : ""}
    </div>
    <div class="mast-right">
      <div class="docbox">
        <div class="docbox-lbl">เลขที่เอกสาร</div>
        <div class="docbox-ref">${_esc(ref || autoRef())}</div>
      </div>
      ${noDate ? "" : `<div class="mast-date">จัดทำ ${dateStr}<br>เวลา ${timeStr} น.</div>`}
    </div>
  </div>`;
}

// แถบ KPI — items: [{label, value, sub?, tone?('ok'|'warn'|'bad')}]
function kpiBand(items) {
  return `<div class="kpi">${items.map(it => `
    <div class="kpi-cell">
      <div class="kpi-lbl">${_esc(it.label)}</div>
      <div class="kpi-val ${it.tone || ""}">${it.value}</div>
      ${it.sub ? `<div class="kpi-sub">${_esc(it.sub)}</div>` : ""}
    </div>`).join("")}</div>`;
}

// ตรรกะสถานะจาก ราคา vs เบิกแล้ว
function statusOf(price, paid) {
  price = Number(price) || 0; paid = Number(paid) || 0;
  // ราคา 0 = ยังไม่ได้ตั้งมูลค่า ไม่ใช่ "ครบ" — ถ้าโชว์ว่าครบบนเอกสารที่เซ็นกัน
  // จะกลายเป็นหลักฐานว่าจ่ายจบแล้วทั้งที่ยังไม่เคยตั้งราคา
  if (price <= 0) return paid > 0
    ? { k: "bad",  label: "เบิกเกิน", extra: "฿" + _fmt(paid) }
    : { k: "none", label: "ยังไม่ตั้งมูลค่า" };
  if (paid > price)  return { k: "bad",  label: "เบิกเกิน", extra: "฿" + _fmt(paid - price) };
  if (paid >= price) return { k: "ok",   label: "ครบ" };
  return                    { k: "warn", label: "ค้างจ่าย" };
}
function badge(st) {
  return `<span class="badge ${st.k}">${_esc(st.label)}${st.extra ? ` ${_esc(st.extra)}` : ""}</span>`;
}

// ช่องลงนาม 3 ช่อง
function signoff() {
  const box = (role) => `
    <div class="sig-box">
      <div class="sig-space"></div>
      <div class="sig-line"></div>
      <div class="sig-paren">(.............................................)</div>
      <div class="sig-role">${role}</div>
      <div class="sig-date">วันที่ ........./........./.........</div>
    </div>`;
  return `<div class="signoff">
    <div class="signoff-title">ลงนามรับรองรายงาน</div>
    <div class="sig-grid">${box("ผู้จัดทำ")}${box("ผู้ตรวจสอบ")}${box("ผู้อนุมัติ")}</div>
  </div>`;
}

function footer() {
  return `<div class="rpt-footer">JS Contract System · ระบบบริหารสัญญาก่อสร้าง — สิ้นสุดรายงาน</div>`;
}

// หัวโครงการ (แถบกรมท่า)
function projHead(p) {
  const remain = (p.value_total||0) - (p.paid_total||0);
  const pct = (p.value_total||0) > 0 ? (((p.paid_total||0)/(p.value_total||0))*100).toFixed(1) : "0.0";
  const lbl = unitLabel(p.unit_type);
  return `<div class="proj-head">
    <div class="proj-name">โครงการ ${_esc(p.name)}</div>
    <div class="proj-meta">ลูกค้า ${_esc(p.client || "-")} · เริ่มโครงการ ${_esc(p.start_date || "-")} · ประเภทหน่วย: ${_esc(lbl.singular)}</div>
    <div class="proj-figs">
      <span><i>มูลค่ารวม</i> ฿${_fmt(p.value_total)}</span>
      <span><i>เบิกแล้ว</i> ฿${_fmt(p.paid_total)}</span>
      <span><i>คงเหลือ</i> ฿${_fmt(remain)}</span>
      <span><i>คืบหน้า</i> ${pct}%</span>
    </div>
  </div>`;
}

// บล็อกสัญญา 1 อัน (หัวสัญญา + ตารางหน่วย + งวด + แถวรวม) — ใช้ทั้งรายงานรวมและรายสัญญา
function contractBlock(c, lbl) {
  const cRemain = (c.value_total||0) - (c.paid_total||0);
  const cPct = (c.value_total||0) > 0 ? (((c.paid_total||0)/(c.value_total||0))*100).toFixed(1) : "0.0";

  let groups = "";
  if (!c.houses.length) {
    groups = `<tbody><tr><td colspan="5" class="empty">— ยังไม่มี${lbl.singular}ในสัญญานี้ —</td></tr></tbody>`;
  } else {
    let totH = 0, totHPaid = 0, totHPays = 0;
    for (const h of c.houses) {
      const hRemain = (h.price||0) - (h.paid_total||0);
      const st = statusOf(h.price, h.paid_total);
      totH += (h.price||0); totHPaid += (h.paid_total||0); totHPays += h.payments.length;

      const remainCell = hRemain > 0
        ? `<span class="neg">${_fmt(hRemain)}</span>`
        : hRemain < 0
          ? `<span class="neg">(${_fmt(-hRemain)})</span>`
          : `<span class="pos">0</span>`;

      let pays;
      if (!h.payments.length) {
        pays = `<tr class="pay"><td colspan="5"><div class="pay-empty">— ยังไม่มีการเบิกจ่าย —</div></td></tr>`;
      } else {
        const lines = h.payments.map((py, i) => {
          const note = py.notes ? `<span class="pay-note">⚠ ${_esc(py.notes)}</span>` : "";
          return `<div class="pay-line"><span class="pay-n">งวด ${i+1}</span><span class="pay-d">${_esc(py.date||"-")}</span><span class="pay-inv">เลขที่ ${_esc(py.inv||"-")}</span><span class="pay-amt">฿${_fmt(py.amount)}</span>${note}</div>`;
        }).join("");
        pays = `<tr class="pay"><td colspan="5"><div class="pays">${lines}</div></td></tr>`;
      }

      groups += `<tbody class="house-grp">
        <tr class="house">
          <td class="unit"><span class="ucode">${_esc(h.house_code)}</span>${badge(st)}</td>
          <td class="r">${_fmt(h.price)}</td>
          <td class="r">${_fmt(h.paid_total)}</td>
          <td class="r">${remainCell}</td>
          <td class="c">${h.payments.length}</td>
        </tr>
        ${pays}
      </tbody>`;
    }
    groups += `<tbody><tr class="totals">
      <td>รวม ${c.houses.length} ${lbl.counter}</td>
      <td class="r">${_fmt(totH)}</td>
      <td class="r">${_fmt(totHPaid)}</td>
      <td class="r">${_fmt(totH - totHPaid)}</td>
      <td class="c">${totHPays} งวด</td>
    </tr></tbody>`;
  }

  return `<div class="contract-blk">
    <div class="contract-head">
      <div class="ch-left">
        <div class="ch-num">สัญญาเลขที่ ${_esc(c.contract_number)}</div>
        <div class="ch-desc">${_esc(c.description || "")}</div>
        <div class="ch-meta">วันที่สัญญา ${_esc(c.contract_date || "-")}</div>
      </div>
      <div class="ch-right">
        <span class="ch-fig"><i>มูลค่า</i>฿${_fmt(c.value_total)}</span>
        <span class="ch-fig"><i>เบิกแล้ว</i>฿${_fmt(c.paid_total)}</span>
        <span class="ch-fig"><i>คงเหลือ</i>฿${_fmt(cRemain)}</span>
        <span class="ch-fig"><i>คืบหน้า</i>${cPct}%</span>
      </div>
    </div>
    <table class="tbl">
      <thead><tr>
        <th>${lbl.code} / สถานะ</th>
        <th class="r">ราคา (บาท)</th>
        <th class="r">เบิกแล้ว (บาท)</th>
        <th class="r">คงเหลือ (บาท)</th>
        <th class="c">งวด</th>
      </tr></thead>
      ${groups}
    </table>
  </div>`;
}

// ============================================================
// ใบปะหน้าสัญญา (Contract Cover Sheet) — A4 หน้าเดียว
// ============================================================
function buildCoverSheetHTML({ project, contract: c, houses, qrUrl }) {
  const lbl = unitLabel(project && project.unit_type);
  const sorted = (houses || []).slice().sort((a, b) =>
    (a.house_code || "").localeCompare(b.house_code || "", undefined, { numeric: true, sensitivity: "base" }));

  // รายการรหัสหน่วย: ราคาเท่ากันหมด → สรุปแบบ "N หลัง × ฿ราคา" / ต่างกัน → แจงรายหลัง
  let houseLine = "—";
  if (sorted.length) {
    const prices = [...new Set(sorted.map(h => h.price || 0))];
    const codes = sorted.map(h => _esc(h.house_code)).join(", ");
    if (prices.length === 1) {
      houseLine = `${sorted.length} ${lbl.counter} × ฿${_fmt(prices[0])}<div class="house-codes">${codes}</div>`;
    } else {
      houseLine = `${sorted.length} ${lbl.counter}<div class="house-codes">${sorted.map(h => `${_esc(h.house_code)} (฿${_fmt(h.price)})`).join(" · ")}</div>`;
    }
  }

  // QR ใช้ลิงก์สั้นของเรา (erp.jselectrichdy.com/d/xxx ~35 ตัวอักษร)
  // → QR โปร่ง สแกนติดง่าย · วาด 600px แล้วย่อแสดง/พิมพ์ 132px ให้คมชัด
  const qrTarget = qrUrl || c.attachment_short || null;
  const qrBlock = qrTarget ? `
    <div class="cover-qr">
      <div id="jsc-qr"></div>
      <div class="t"><b>สแกนเพื่อเปิดไฟล์สัญญา</b><br>${_esc(qrTarget.replace(/^https?:\/\//, ""))}</div>
    </div>
    <style>
      #jsc-qr{width:132px;height:132px;background:#fff}
      #jsc-qr img,#jsc-qr canvas{width:132px !important;height:132px !important;image-rendering:pixelated;display:block}
    </style>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"><\/script>
    <script>
      try {
        new QRCode(document.getElementById("jsc-qr"), {
          text: ${JSON.stringify(qrTarget)},
          width: 600, height: 600,
          correctLevel: QRCode.CorrectLevel.M
        });
      } catch (e) {
        document.getElementById("jsc-qr").innerHTML = '<div style="font-size:12px;color:#999;padding-top:60px;text-align:center">สร้าง QR ไม่สำเร็จ</div>';
      }
    <\/script>` : "";

  const inner = `<div class="cover-sheet">` + masthead({
    eyebrow: "เอกสารประกอบแฟ้มสัญญา",
    title: "ใบปะหน้าสัญญา",
    sub: project ? `โครงการ ${project.name}` : "",
    ref: "COV-" + (c.contract_number || ""),
    noDate: true,
  }) + `
  <div class="cover-num-wrap">
    <div class="cover-num-lbl">สัญญาเลขที่</div>
    <div class="cover-num">${_esc(c.contract_number || "-")}</div>
  </div>
  <div class="cover-desc">${_esc(c.description || "")}</div>

  <table class="cover-info">
    <tr><td class="k">โครงการ</td><td class="v">${_esc(project ? project.name : "-")}</td></tr>
    <tr><td class="k">ลูกค้า / ผู้ว่าจ้าง</td><td class="v">${_esc((project && project.client) || "-")}</td></tr>
    <tr><td class="k">วันที่สัญญา</td><td class="v">${_esc(c.contract_date || "-")}</td></tr>
    <tr><td class="k">มูลค่าสัญญา</td><td class="v">฿${_fmt(c.value_total)}
      <div class="baht-text">( ${_esc(bahtText(c.value_total))} )</div></td></tr>
    <tr><td class="k">ขอบเขตงาน</td><td class="v">${houseLine}</td></tr>
  </table>

  <div class="cover-bottom">
    <div class="cb-left">
      <div class="sec-title">เอกสารในแฟ้ม</div>
      <div class="chk-row">
        <span class="chk">สัญญาต้นฉบับ</span>
        <span class="chk">สำเนาสัญญา</span>
        <span class="chk">ใบเสนอราคา</span>
        <span class="chk">แบบก่อสร้าง</span>
        <span class="chk">BOQ</span>
        <span class="chk">อื่นๆ ....................</span>
      </div>
    </div>
    ${qrTarget ? `<div class="cb-right">${qrBlock}</div>` : ""}
  </div>

  <div class="notes-line"><span>หมายเหตุ</span><span class="l"></span></div>
  <div class="notes-line"><span class="l"></span></div>
  <div class="notes-line"><span class="l"></span></div>
  <div class="notes-line"><span class="l"></span></div>

  <div class="cover-tail">
    <div class="cover-signoff">
      <div class="signoff-title">ลงนามรับรองรายงาน</div>
      <div class="cover-sig-one">
        <div class="sig-space"></div>
        <div class="sig-line"></div>
        <div class="sig-paren">(.............................................)</div>
        <div class="sig-role">ผู้จัดทำ</div>
        <div class="sig-date">วันที่ ........./........./.........</div>
      </div>
    </div>
    ${footer()}
  </div>
  </div>`;

  return htmlShell(`ใบปะหน้าสัญญา ${c.contract_number || ""}`, inner);
}

// เปิดหน้าต่างพิมพ์ใบปะหน้า — เปิด window ทันที (กัน popup blocker)
// แล้วสร้าง/ดึงลิงก์สั้นสำหรับ QR ก่อนเขียนเนื้อหาจริง
async function printCoverSheet(args) {
  const w = window.open("", "_blank");
  if (!w) { alert("กรุณาอนุญาต Pop-up สำหรับเว็บไซต์นี้"); return; }
  w.document.write(`<!DOCTYPE html><html lang="th"><head><meta charset="UTF-8"><title>กำลังเตรียมใบปะหน้า...</title>
    ${FONT_LINK}
    <style>body{font-family:'TH Sarabun New','Sarabun',sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;flex-direction:column;gap:16px;color:#000;background:#fff}
    .sp{width:40px;height:40px;border:3px solid #ddd;border-top-color:#000;border-radius:50%;animation:s .8s linear infinite}@keyframes s{to{transform:rotate(360deg)}}</style></head>
    <body><div class="sp"></div><div style="font-size:17px">กำลังเตรียมใบปะหน้าสัญญา...</div></body></html>`);

  let qrUrl = null;
  const c = args.contract;
  if (c && c.attachment_url) {
    try {
      qrUrl = await window.FB.ensureContractShortLink(args.projectId, c.id, c);
    } catch (e) {
      qrUrl = c.attachment_short || null;  // สร้างไม่ได้ → ใช้ของเดิมถ้ามี / ไม่มีก็ไม่แสดง QR
    }
  }

  const html = buildCoverSheetHTML({ ...args, qrUrl });
  if (w.closed) return;
  w.document.open();
  w.document.write(html);
  w.document.close();
}

// โครงร่างเอกสาร — inner ต้องประกอบ masthead → ... → signoff → footer เองครบแล้ว
function htmlShell(title, inner, autoPrint = true) {
  return `<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<title>${_esc(title)}</title>
${FONT_LINK}
<style>${REPORT_CSS}</style>
${autoPrint ? `<script>window.onload=function(){setTimeout(function(){try{window.print()}catch(e){}},900)}</script>` : ""}
</head>
<body>
${inner}
</body>
</html>`;
}

// ---------- hierarchical HTML (scope = all / project / contract) ----------
function buildHierarchicalHTML(title, projects, opts = {}) {
  const ag = aggregate(projects);
  const allTypes = new Set(projects.map(p => p.unit_type || "house"));
  const overallLbl = allTypes.size > 1 ? { counter: "หน่วย", singular: "หน่วย" } : unitLabel([...allTypes][0]);

  // KPI band — มูลค่ารวม · เบิกแล้ว · คงเหลือ · %คืบหน้า
  const pct = ag.value > 0 ? ((ag.paid / ag.value) * 100).toFixed(1) : "0.0";
  const kpis = kpiBand([
    { label: "มูลค่ารวม (บาท)", value: "฿" + _fmt(ag.value), sub: `${ag.contracts} สัญญา · ${ag.houses} ${overallLbl.singular}` },
    { label: "เบิกแล้ว (บาท)", value: "฿" + _fmt(ag.paid), tone: "ok", sub: `${ag.payments} งวด` },
    { label: "คงเหลือ (บาท)", value: "฿" + _fmt(ag.remain), tone: ag.remain > 0 ? "warn" : ag.remain < 0 ? "bad" : "ok" },
    { label: "ความคืบหน้า", value: pct + "%" },
  ]);

  const head = masthead({ eyebrow: opts.eyebrow || "รายงานสรุปสัญญาและการเบิกจ่าย", title, sub: opts.sub });

  if (projects.length === 0) {
    return htmlShell(title, head + kpis + `<div class="empty-blk">ไม่มีข้อมูลในขอบเขตที่เลือก</div>` + signoff() + footer());
  }

  // สร้าง "หน่วยหน้า" — 1 สัญญา = 1 หน้า (กฎข้อ 1)
  const pageUnits = [];
  for (const p of projects) {
    const lbl = unitLabel(p.unit_type);
    const ph = projHead(p);
    if (!p.contracts.length) {
      pageUnits.push(ph + `<div class="empty-blk">ยังไม่มีสัญญาในโครงการนี้</div>`);
    } else {
      p.contracts.forEach((c, ci) => {
        pageUnits.push((ci === 0 ? ph : "") + contractBlock(c, lbl));
      });
    }
  }
  const content = pageUnits.join(`<div class="pbreak"></div>`);

  return htmlShell(title, head + kpis + content + signoff() + footer());
}

// ---------- single house report (scope = house) — vertical timeline ----------
function buildHouseHTML(title, projects) {
  let p = null, c = null, h = null;
  outer:
  for (const pp of projects) for (const cc of pp.contracts) for (const hh of cc.houses) { p = pp; c = cc; h = hh; break outer; }

  if (!h) {
    return htmlShell(title, masthead({ eyebrow: "รายงานรายหน่วย", title }) + `<div class="empty-blk">ไม่พบข้อมูลหน่วยที่เลือก</div>` + signoff() + footer());
  }

  const lbl = unitLabel(p.unit_type);
  const st = statusOf(h.price, h.paid_total);
  const remain = (h.price||0) - (h.paid_total||0);
  const pct = (h.price||0) > 0 ? (((h.paid_total||0)/(h.price||0))*100).toFixed(1) : "0.0";

  const kpis = kpiBand([
    { label: `ราคา${lbl.singular} (บาท)`, value: "฿" + _fmt(h.price) },
    { label: "เบิกแล้ว (บาท)", value: "฿" + _fmt(h.paid_total), tone: "ok", sub: pct + "% ของราคา" },
    { label: "สถานะ", value: st.label + (st.extra ? ` ${st.extra}` : ""), tone: st.k,
      sub: remain > 0 ? `คงเหลือ ฿${_fmt(remain)}` : remain < 0 ? `เกิน ฿${_fmt(-remain)}` : "เบิกครบพอดี" },
  ]);

  const info = `<div class="house-info">
    <span><i>${_esc(lbl.code)}</i>${_esc(h.house_code)}</span>
    <span><i>โครงการ</i>${_esc(p.name)}</span>
    <span><i>สัญญา</i>${_esc(c.contract_number)}</span>
    ${h.notes ? `<span><i>หมายเหตุ</i>${_esc(h.notes)}</span>` : ""}
  </div>`;

  let timeline;
  if (!h.payments.length) {
    timeline = `<div class="empty-blk">ยังไม่มีการเบิกจ่ายสำหรับ${lbl.singular}นี้</div>`;
  } else {
    const last = h.payments.length - 1;
    const items = h.payments.map((py, i) => `
      <div class="tl-item">
        <div class="tl-marker"><span class="tl-dot ${i === last ? "tl-dot-ok" : ""}"></span></div>
        <div class="tl-body">
          <div class="tl-head"><span class="tl-n">งวดที่ ${i+1}</span><span class="tl-amt">฿${_fmt(py.amount)}</span></div>
          <div class="tl-meta">วันที่ ${_esc(py.date||"-")} · เลขที่ใบกำกับ ${_esc(py.inv||"-")}</div>
          ${py.notes ? `<div class="tl-note">⚠ ${_esc(py.notes)}</div>` : ""}
        </div>
      </div>`).join("");
    const sum = h.payments.reduce((s, x) => s + (x.amount||0), 0);
    timeline = `<div class="timeline">${items}</div>
      <div class="tl-total">รวมเบิกแล้ว ${h.payments.length} งวด · ฿${_fmt(sum)}</div>`;
  }

  const inner = masthead({ eyebrow: "รายงานรายหน่วย", title, sub: `${lbl.code} ${h.house_code} · ${p.name}` })
    + kpis + info
    + `<div class="sec-title">ประวัติการเบิกจ่าย</div>` + timeline
    + signoff() + footer();
  return htmlShell(title, inner);
}

// ---------- flat payment list HTML (layout = payment-list) ----------
function buildPaymentListHTML(title, projects) {
  const all = [];
  for (const p of projects) {
    for (const c of p.contracts) {
      for (const h of c.houses) {
        h.payments.forEach((py, i) => all.push({ p, c, h, py, idx: i+1 }));
      }
    }
  }
  all.sort((a, b) => (a.py.date > b.py.date ? -1 : a.py.date < b.py.date ? 1 : 0));
  const total = all.reduce((s, r) => s + (r.py.amount || 0), 0);
  const avg = Math.round(total / Math.max(1, all.length));

  const head = masthead({ eyebrow: "รายงานการเบิกจ่าย", title });
  const kpis = kpiBand([
    { label: "จำนวนรายการ", value: _fmt(all.length), sub: "ทุกงวดในระบบ" },
    { label: "ยอดรวม (บาท)", value: "฿" + _fmt(total), tone: "ok" },
    { label: "เฉลี่ย/รายการ (บาท)", value: "฿" + _fmt(avg) },
  ]);

  if (all.length === 0) {
    return htmlShell(title, head + kpis + `<div class="empty-blk">ยังไม่มีรายการเบิกจ่าย</div>` + signoff() + footer());
  }

  const rows = all.map(r => {
    const _l = unitLabel(r.p.unit_type);
    const note = r.py.notes ? `<span class="pay-note" style="margin-left:6px">⚠ ${_esc(r.py.notes)}</span>` : "";
    return `<tr>
      <td>${_esc(r.py.date||"-")}</td>
      <td>${_esc(r.p.name)}</td>
      <td>${_esc(r.c.contract_number)}</td>
      <td><b>${_esc(r.h.house_code)}</b> <span style="color:var(--text-2);font-size:13px">${_esc(_l.singular)}</span></td>
      <td class="c">งวด ${r.idx}</td>
      <td>${_esc(r.py.inv||"-")}</td>
      <td class="r"><b>฿${_fmt(r.py.amount)}</b>${note}</td>
    </tr>`;
  }).join("");

  const body = `
    <table class="tbl">
      <thead><tr>
        <th>วันที่</th><th>โครงการ</th><th>สัญญา</th><th>หน่วย</th>
        <th class="c">งวดที่</th><th>ใบกำกับ</th><th class="r">ยอด (บาท)</th>
      </tr></thead>
      <tbody>${rows}</tbody>
      <tbody><tr class="totals">
        <td colspan="6">รวมทั้งสิ้น ${all.length} รายการ</td>
        <td class="r">฿${_fmt(total)}</td>
      </tr></tbody>
    </table>`;

  return htmlShell(title, head + kpis + body + signoff() + footer());
}

// ---------- owing houses HTML (layout = owing-houses) ----------
function buildOwingHousesHTML(title, projects) {
  const owing = [];
  let totValue = 0, totPaid = 0;
  for (const p of projects) {
    for (const c of p.contracts) {
      for (const h of c.houses) {
        totValue += (h.price||0); totPaid += (h.paid_total||0);
        const r = (h.price||0) - (h.paid_total||0);
        if (r > 0) {
          const last = h.payments.length ? h.payments[h.payments.length-1] : null;
          owing.push({ p, c, h, remain: r, last });
        }
      }
    }
  }
  owing.sort((a, b) => b.remain - a.remain);
  const totRemain = owing.reduce((s, x) => s + x.remain, 0);
  const overallPct = totValue > 0 ? ((totPaid / totValue) * 100).toFixed(1) : "0.0";

  const head = masthead({ eyebrow: "รายงานติดตามหนี้ค้างชำระ", title });
  const kpis = kpiBand([
    { label: "หน่วยที่ค้างจ่าย", value: _fmt(owing.length) + " หน่วย", tone: owing.length ? "warn" : "ok" },
    { label: "ยอดคงเหลือรวม (บาท)", value: "฿" + _fmt(totRemain), tone: totRemain > 0 ? "warn" : "ok" },
    { label: "ความคืบหน้ารวม", value: overallPct + "%", sub: `เบิกแล้ว ฿${_fmt(totPaid)} / ฿${_fmt(totValue)}` },
  ]);

  if (owing.length === 0) {
    return htmlShell(title, head + kpis + `<div class="empty-blk">ไม่มีหน่วยที่ค้างจ่าย — ทุกหน่วยเบิกครบแล้ว ✓</div>` + signoff() + footer());
  }

  const rows = owing.map(x => {
    const _l = unitLabel(x.p.unit_type);
    const hPct = (x.h.price||0) > 0 ? (((x.h.paid_total||0)/(x.h.price||0))*100).toFixed(0) : "0";
    return `<tr>
      <td>${_esc(x.p.name)}</td>
      <td>${_esc(x.c.contract_number)}</td>
      <td><b>${_esc(x.h.house_code)}</b> <span style="color:var(--text-2);font-size:13px">${_esc(_l.singular)}</span></td>
      <td class="r">${_fmt(x.h.price)}</td>
      <td class="r">${_fmt(x.h.paid_total)}</td>
      <td class="r"><span class="neg">${_fmt(x.remain)}</span></td>
      <td class="c">${hPct}%</td>
      <td class="c">${x.h.payments.length}</td>
      <td>${x.last ? `${_esc(x.last.date)} · ฿${_fmt(x.last.amount)}` : "—"}</td>
    </tr>`;
  }).join("");

  const body = `
    <table class="tbl">
      <thead><tr>
        <th>โครงการ</th><th>สัญญา</th><th>หน่วย</th>
        <th class="r">ราคา (บาท)</th><th class="r">เบิกแล้ว (บาท)</th><th class="r">คงเหลือ (บาท)</th>
        <th class="c">คืบหน้า</th><th class="c">งวด</th><th>เบิกล่าสุด</th>
      </tr></thead>
      <tbody>${rows}</tbody>
      <tbody><tr class="totals">
        <td colspan="5">รวม ${owing.length} หน่วยค้างจ่าย</td>
        <td class="r">฿${_fmt(totRemain)}</td>
        <td colspan="3"></td>
      </tr></tbody>
    </table>`;

  return htmlShell(title, head + kpis + body + signoff() + footer());
}

// ============================================================
// Excel sheet generators
// ============================================================
function buildHierarchicalSheets(projects) {
  // 1) Hierarchical "all-in-one" sheet
  const hier = [];
  for (const p of projects) {
    const pLbl = unitLabel(p.unit_type);
    hier.push({
      "ระดับ": "โครงการ", "ชื่อ/รหัส": p.name, "ลูกค้า/รายละเอียด": p.client || "",
      "วันที่": p.start_date || "", "ใบกำกับ": "",
      "ราคา/มูลค่า (บาท)": p.value_total || 0,
      "เบิกแล้ว (บาท)": p.paid_total || 0,
      "คงเหลือ (บาท)": (p.value_total||0) - (p.paid_total||0),
      "งวด": p.contracts.reduce((s,c)=>s+c.houses.reduce((s2,h)=>s2+h.payments.length,0),0),
      "หมายเหตุ": "ประเภทหน่วย: " + pLbl.singular,
    });
    for (const c of p.contracts) {
      hier.push({
        "ระดับ": "  สัญญา", "ชื่อ/รหัส": c.contract_number, "ลูกค้า/รายละเอียด": c.description || "",
        "วันที่": c.contract_date || "", "ใบกำกับ": "",
        "ราคา/มูลค่า (บาท)": c.value_total || 0,
        "เบิกแล้ว (บาท)": c.paid_total || 0,
        "คงเหลือ (บาท)": (c.value_total||0) - (c.paid_total||0),
        "งวด": c.houses.reduce((s,h)=>s+h.payments.length,0),
        "หมายเหตุ": "",
      });
      for (const h of c.houses) {
        hier.push({
          "ระดับ": `    ${pLbl.singular}`, "ชื่อ/รหัส": h.house_code, "ลูกค้า/รายละเอียด": "",
          "วันที่": "", "ใบกำกับ": "",
          "ราคา/มูลค่า (บาท)": h.price || 0,
          "เบิกแล้ว (บาท)": h.paid_total || 0,
          "คงเหลือ (บาท)": (h.price||0) - (h.paid_total||0),
          "งวด": h.payments.length,
          "หมายเหตุ": h.notes || "",
        });
        h.payments.forEach((py, i) => {
          hier.push({
            "ระดับ": `      งวดที่ ${i+1}`, "ชื่อ/รหัส": "", "ลูกค้า/รายละเอียด": "",
            "วันที่": py.date || "", "ใบกำกับ": py.inv || "",
            "ราคา/มูลค่า (บาท)": "",
            "เบิกแล้ว (บาท)": py.amount || 0,
            "คงเหลือ (บาท)": "", "งวด": "",
            "หมายเหตุ": py.notes || "",
          });
        });
      }
    }
  }

  // 2) Projects summary
  const projRows = projects.map(p => {
    const _l = unitLabel(p.unit_type);
    return {
      "โครงการ": p.name, "ลูกค้า": p.client || "",
      "ประเภทหน่วย": _l.singular,
      "วันที่เริ่ม": p.start_date || "",
      "มูลค่ารวม (บาท)": p.value_total || 0,
      "เบิกแล้ว (บาท)": p.paid_total || 0,
      "คงเหลือ (บาท)": (p.value_total||0) - (p.paid_total||0),
      "% คืบหน้า": (p.value_total||0) > 0 ? ((p.paid_total||0)/(p.value_total||0)*100).toFixed(1)+"%" : "0%",
      "จำนวนสัญญา": p.contracts.length,
      [`จำนวน${_l.singular}`]: p.contracts.reduce((s,c)=>s+c.houses.length,0),
      "จำนวนงวดเบิก": p.contracts.reduce((s,c)=>s+c.houses.reduce((s2,h)=>s2+h.payments.length,0),0),
    };
  });

  // 3) Contracts
  const contractRows = [];
  for (const p of projects) {
    const _l = unitLabel(p.unit_type);
    for (const c of p.contracts) {
      contractRows.push({
        "โครงการ": p.name, "เลขที่สัญญา": c.contract_number,
        "รายละเอียด": c.description || "", "วันที่สัญญา": c.contract_date || "",
        "มูลค่า (บาท)": c.value_total || 0,
        "เบิกแล้ว (บาท)": c.paid_total || 0,
        "คงเหลือ (บาท)": (c.value_total||0) - (c.paid_total||0),
        "% คืบหน้า": (c.value_total||0) > 0 ? ((c.paid_total||0)/(c.value_total||0)*100).toFixed(1)+"%" : "0%",
        [`จำนวน${_l.singular}`]: c.houses.length,
        "งวดทั้งหมด": c.houses.reduce((s,h)=>s+h.payments.length,0),
      });
    }
  }

  // 4) Houses/Units
  const houseRows = [];
  for (const p of projects) {
    const _l = unitLabel(p.unit_type);
    for (const c of p.contracts) {
      for (const h of c.houses) {
        houseRows.push({
          "โครงการ": p.name, "สัญญา": c.contract_number,
          "ประเภท": _l.singular,
          [_l.code]: h.house_code,
          "ราคา (บาท)": h.price || 0,
          "เบิกแล้ว (บาท)": h.paid_total || 0,
          "คงเหลือ (บาท)": (h.price||0) - (h.paid_total||0),
          "% คืบหน้า": (h.price||0) > 0 ? ((h.paid_total||0)/(h.price||0)*100).toFixed(1)+"%" : "0%",
          "จำนวนงวด": h.payments.length,
          "หมายเหตุ": h.notes || "",
        });
      }
    }
  }

  // 5) Payments
  const payRows = [];
  for (const p of projects) {
    const _l = unitLabel(p.unit_type);
    for (const c of p.contracts) {
      for (const h of c.houses) {
        h.payments.forEach((py, i) => {
          payRows.push({
            "โครงการ": p.name, "สัญญา": c.contract_number,
            "ประเภท": _l.singular,
            [_l.code]: h.house_code,
            "งวดที่": i+1, "วันที่": py.date || "",
            "เลขใบกำกับ": py.inv || "",
            "ยอด (บาท)": py.amount || 0,
            "หมายเหตุ": py.notes || "",
          });
        });
      }
    }
  }

  return [
    { name: "รายงานรวม", rows: hier },
    { name: "โครงการ", rows: projRows },
    { name: "สัญญา", rows: contractRows },
    { name: "หน่วย", rows: houseRows },
    { name: "รายการเบิก", rows: payRows },
  ];
}

function buildPaymentListSheets(projects) {
  const rows = [];
  for (const p of projects) {
    const _l = unitLabel(p.unit_type);
    for (const c of p.contracts) {
      for (const h of c.houses) {
        h.payments.forEach((py, i) => {
          rows.push({
            "วันที่": py.date || "", "โครงการ": p.name,
            "สัญญา": c.contract_number,
            "ประเภท": _l.singular,
            "รหัส": h.house_code,
            "งวดที่": i+1, "เลขใบกำกับ": py.inv || "",
            "ยอด (บาท)": py.amount || 0,
            "หมายเหตุ": py.notes || "",
          });
        });
      }
    }
  }
  rows.sort((a,b) => a["วันที่"] > b["วันที่"] ? -1 : a["วันที่"] < b["วันที่"] ? 1 : 0);
  return [{ name: "ประวัติเบิกจ่าย", rows }];
}

function buildOwingHousesSheets(projects) {
  const rows = [];
  for (const p of projects) {
    const _l = unitLabel(p.unit_type);
    for (const c of p.contracts) {
      for (const h of c.houses) {
        const r = (h.price||0) - (h.paid_total||0);
        if (r > 0) {
          const last = h.payments.length ? h.payments[h.payments.length-1] : null;
          rows.push({
            "โครงการ": p.name, "สัญญา": c.contract_number,
            "ประเภท": _l.singular,
            "รหัส": h.house_code,
            "ราคา (บาท)": h.price || 0,
            "เบิกแล้ว (บาท)": h.paid_total || 0,
            "คงเหลือ (บาท)": r,
            "จำนวนงวด": h.payments.length,
            "เบิกล่าสุดวันที่": last ? last.date : "",
            "ยอดเบิกล่าสุด (บาท)": last ? last.amount : 0,
            "หมายเหตุ": h.notes || "",
          });
        }
      }
    }
  }
  return [{ name: "หน่วยค้างจ่าย", rows }];
}

// ============================================================
// Main runner — called by every report button
// ============================================================
async function runReport({ scope, format, title, layout = "hierarchical", setProgress, toast }) {
  // 1) Pre-open window (in user-gesture context) for PDF
  let targetWindow = null;
  if (format !== "xlsx") {
    targetWindow = window.open("", "_blank");
    if (targetWindow) {
      targetWindow.document.write(`<!DOCTYPE html><html lang="th"><head><meta charset="UTF-8"><title>กำลังสร้างรายงาน...</title>
        ${FONT_LINK}
        <style>body{font-family:'TH Sarabun New','Sarabun',sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;flex-direction:column;gap:18px;color:#000;background:#fff}
        .sp{width:46px;height:46px;border:4px solid #ddd;border-top-color:#000;border-radius:50%;animation:s 0.8s linear infinite}
        @keyframes s{to{transform:rotate(360deg)}}</style></head>
        <body><div class="sp"></div><div style="font-size:18px">กำลังสร้างรายงาน <b>${_esc(title)}</b>...</div>
        <div style="font-size:14px;color:#555">ระบบกำลังดึงข้อมูลล่าสุดจาก Server</div></body></html>`);
    }
  }

  setProgress && setProgress({ step: 1, message: "กำลังเตรียมข้อมูลและตรวจสอบขอบเขต...", title, format });
  await tick(120);

  try {
    setProgress && setProgress({ step: 2, message: "กำลังดึงข้อมูลการเบิกจ่ายจาก Server...", title, format, payProgress: "0/0" });
    await loadScopedPayments(scope, (done, total) => {
      setProgress && setProgress(s => s ? { ...s, payProgress: `${done}/${total}` } : s);
    });
    await tick(80);

    setProgress && setProgress({ step: 3, message: "กำลังรวบรวมและจัดเรียงข้อมูล...", title, format });
    await tick(100);
    const projects = buildHierarchicalData(scope);

    setProgress && setProgress({ step: 4, message: "กำลังจัดรูปแบบรายงาน...", title, format });
    await tick(150);

    let html = "", sheets = [];
    if (scope.type === "house") {
      // รายงานรายหน่วย — ไทม์ไลน์การเบิก (Excel ใช้ชุด hierarchical เดิม)
      html = buildHouseHTML(title, projects);
      sheets = buildHierarchicalSheets(projects);
    } else if (layout === "payment-list") {
      html = buildPaymentListHTML(title, projects);
      sheets = buildPaymentListSheets(projects);
    } else if (layout === "owing-houses") {
      html = buildOwingHousesHTML(title, projects);
      sheets = buildOwingHousesSheets(projects);
    } else {
      html = buildHierarchicalHTML(title, projects, {
        eyebrow: scope.type === "contract" ? "รายงานรายสัญญา" : scope.type === "project" ? "รายงานรายโครงการ" : "รายงานสรุปสัญญาและการเบิกจ่าย",
      });
      sheets = buildHierarchicalSheets(projects);
    }

    setProgress && setProgress({ step: 5, message: `กำลังสร้างไฟล์ ${format === "xlsx" ? "Excel" : "PDF"}...`, title, format });
    await tick(120);

    if (format === "xlsx") {
      if (targetWindow && !targetWindow.closed) targetWindow.close();
      window.exportExcel(sheets, title);
      toast && toast({ t: "สร้าง Excel สำเร็จ", s: `${title}.xlsx (${sheets.length} sheet)` });
    } else {
      if (targetWindow && !targetWindow.closed) {
        targetWindow.document.open();
        targetWindow.document.write(html);
        targetWindow.document.close();
      } else {
        const w = window.open("", "_blank");
        if (!w) throw new Error("กรุณาอนุญาต Pop-up สำหรับเว็บไซต์นี้");
        w.document.write(html); w.document.close();
      }
      toast && toast({ t: "สร้าง PDF สำเร็จ", s: "เปิดในแท็บใหม่ — กด Ctrl+P เพื่อพิมพ์" });
    }

    setProgress && setProgress(null);
  } catch (e) {
    if (targetWindow && !targetWindow.closed) targetWindow.close();
    setProgress && setProgress(null);
    toast && toast({ t: "เกิดข้อผิดพลาด", s: e.message });
  }
}

// ============================================================
// Progress modal — used by every report button
// ============================================================
function ReportProgressModal({ progress }) {
  if (!progress) return null;
  const { step, message, title, format, payProgress } = progress;
  const totalSteps = 5;
  const pct = Math.min(100, ((step || 0) / totalSteps) * 100);
  const labels = [
    "เตรียมข้อมูล",
    "ดึงข้อมูลจาก Server",
    "รวบรวมข้อมูล",
    "จัดรูปแบบรายงาน",
    `สร้างไฟล์ ${format === "xlsx" ? "Excel" : "PDF"}`,
  ];
  return (
    <div className="modal-overlay" style={{ zIndex: 9999 }}>
      <div className="modal" style={{ maxWidth: 500 }}>
        <div className="modal-head">
          <div className="ico"><Icon name={format === "xlsx" ? "excel" : "print"} size={18} /></div>
          <h2>กำลังสร้างรายงาน</h2>
        </div>
        <div className="modal-body">
          <div style={{ fontSize: 13.5, color: "var(--text-2)", marginBottom: 4 }}>
            กำลังสร้าง <b style={{ color: "var(--text)" }}>{title}.{format === "xlsx" ? "xlsx" : "pdf"}</b>
          </div>
          <div className="prog-modal-bar"><div className="prog-modal-fill" style={{ width: pct + "%" }}></div></div>
          <div className="hstack" style={{ justifyContent: "space-between", fontSize: 12, color: "var(--text-2)" }}>
            <span>{Math.round(pct)}%</span>
            <span className="num">ขั้นตอน {Math.min(step || 1, totalSteps)} / {totalSteps}</span>
          </div>
          <div style={{ background: "var(--surface-2)", border: "1px solid var(--border)", borderRadius: 8, padding: "10px 12px", marginTop: 10, fontSize: 13 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
              <span className="spinner" style={{ width: 14, height: 14, borderWidth: 2 }}></span>
              <span><b>{message}</b></span>
            </div>
            {payProgress && payProgress !== "0/0" && (
              <div style={{ marginTop: 4, fontSize: 12, color: "var(--text-2)" }}>
                หน่วยที่โหลดข้อมูลแล้ว: {payProgress}
              </div>
            )}
          </div>
          <div className="prog-steps" style={{ marginTop: 12 }}>
            {labels.map((s, i) => {
              const idx = i + 1;
              const done = idx < (step || 0);
              const active = idx === (step || 0);
              return (
                <div key={i} className={`prog-step ${done ? "done" : active ? "active" : ""}`}>
                  <div className="dot">{done ? <Icon name="check" size={9} /> : active ? <span className="spinner" style={{ width: 9, height: 9, borderWidth: 1.5 }}></span> : ""}</div>
                  <span>{s}</span>
                </div>
              );
            })}
          </div>
          <div className="dim" style={{ fontSize: 11.5, marginTop: 12, padding: "8px 10px", background: "var(--surface-2)", borderRadius: 6 }}>
            <Icon name="info" size={11} /> ระบบกำลังประมวลผลในเบื้องหลัง — โปรดอย่าปิดหน้าจอนี้
          </div>
        </div>
      </div>
    </div>
  );
}

// ---------- React hook for report buttons ----------
function useReportRunner() {
  const [progress, setProgress] = useStateR(null);
  const toast = window.useToast();
  const run = (opts) => runReport({ ...opts, setProgress, toast });
  const ui = React.createElement(ReportProgressModal, { progress });
  return { run, ui, isRunning: !!progress };
}

// expose globally
Object.assign(window, {
  runReport,
  ReportProgressModal,
  useReportRunner,
  bahtText,
  buildCoverSheetHTML,
  printCoverSheet,
  buildHierarchicalHTML,
  buildHouseHTML,
  buildHierarchicalSheets,
  buildPaymentListHTML,
  buildPaymentListSheets,
  buildOwingHousesHTML,
  buildOwingHousesSheets,
  buildHierarchicalData,
  loadScopedPayments,   // ใช้ร่วมกับระบบพิมพ์ dot matrix
  aggregate,
});
