// ระบบสำรองข้อมูล — Export เป็น CSV แยกไฟล์ตามประเภท (รวม .zip) + กู้คืน
// ใช้ ZIP แบบ store (ไม่บีบอัด) เขียน/อ่านเองในเบราว์เซอร์ ไม่ต้องพึ่ง library

/* ── CRC32 (จำเป็นสำหรับ ZIP) ── */
const _crcTable = (() => {
  let c, t = [];
  for (let n = 0; n < 256; n++) { c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 0xEDB88320 ^ (c >>> 1) : c >>> 1; t[n] = c >>> 0; }
  return t;
})();
function crc32(bytes) {
  let crc = 0 ^ (-1);
  for (let i = 0; i < bytes.length; i++) crc = (crc >>> 8) ^ _crcTable[(crc ^ bytes[i]) & 0xFF];
  return (crc ^ (-1)) >>> 0;
}

/* ── สร้าง ZIP (store, รองรับชื่อไฟล์ภาษาไทย UTF-8) ── */
function makeZip(files) {
  const enc = new TextEncoder();
  const u16 = (n) => [n & 0xFF, (n >> 8) & 0xFF];
  const u32 = (n) => [n & 0xFF, (n >> 8) & 0xFF, (n >> 16) & 0xFF, (n >> 24) & 0xFF];
  const localParts = [], centralParts = [];
  let offset = 0;
  for (const f of files) {
    const nameBytes = enc.encode(f.name);
    const data = f.data;
    const crc = crc32(data), size = data.length;
    const local = new Uint8Array([].concat(
      u32(0x04034b50), u16(20), u16(0x0800), u16(0), u16(0), u16(0),
      u32(crc), u32(size), u32(size), u16(nameBytes.length), u16(0)
    ));
    localParts.push(local, nameBytes, data);
    const cen = new Uint8Array([].concat(
      u32(0x02014b50), u16(20), u16(20), u16(0x0800), u16(0), u16(0), u16(0),
      u32(crc), u32(size), u32(size), u16(nameBytes.length), u16(0), u16(0), u16(0), u16(0), u32(0), u32(offset)
    ));
    centralParts.push(cen, nameBytes);
    offset += local.length + nameBytes.length + data.length;
  }
  let centralSize = 0; centralParts.forEach((p) => centralSize += p.length);
  const end = new Uint8Array([].concat(
    u32(0x06054b50), u16(0), u16(0), u16(files.length), u16(files.length), u32(centralSize), u32(offset), u16(0)
  ));
  const all = [...localParts, ...centralParts, end];
  let total = 0; all.forEach((p) => total += p.length);
  const out = new Uint8Array(total);
  let pos = 0; for (const p of all) { out.set(p, pos); pos += p.length; }
  return out;
}

/* ── อ่าน ZIP (store เท่านั้น) ── */
function readZip(bytes) {
  const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
  const dec = new TextDecoder();
  const files = {};
  let i = 0;
  while (i + 4 <= bytes.length) {
    const sig = dv.getUint32(i, true);
    if (sig !== 0x04034b50) break;
    const compSize = dv.getUint32(i + 18, true);
    const nameLen = dv.getUint16(i + 26, true);
    const extraLen = dv.getUint16(i + 28, true);
    const nameStart = i + 30;
    const name = dec.decode(bytes.slice(nameStart, nameStart + nameLen));
    const dataStart = nameStart + nameLen + extraLen;
    files[name] = bytes.slice(dataStart, dataStart + compSize);
    i = dataStart + compSize;
  }
  return files;
}

/* ── แปลง array ของ object → CSV (escape + nested JSON) ── */
function toCSV(rows) {
  if (!rows || !rows.length) return "";
  const cols = [...new Set(rows.flatMap((r) => Object.keys(r)))];
  const esc = (v) => {
    if (v == null) return "";
    if (typeof v === "object") v = JSON.stringify(v);
    v = String(v);
    return /[",\n\r]/.test(v) ? '"' + v.replace(/"/g, '""') + '"' : v;
  };
  const head = cols.join(",");
  const body = rows.map((r) => cols.map((c) => esc(r[c])).join(",")).join("\r\n");
  return head + "\r\n" + body;
}

/* ── ตารางที่จะ export (ชื่อไฟล์ไทย) ── */
const BACKUP_TABLES = [
  ["products", "สินค้า"],
  ["sales", "การขาย"],
  ["repairs", "งานซ่อม"],
  ["pawns", "จำนำ"],
  ["buyins", "รับซื้อ-รับเทิร์น"],
  ["moves", "ความเคลื่อนไหวสต็อก"],
  ["dailyClears", "เคลียร์ยอดรายวัน"],
  ["users", "พนักงาน"],
  ["branches", "สาขา"],
  ["locations", "คลัง-Location"],
  ["cats", "หมวดหมู่"],
];

function pad2(n) { return String(n).padStart(2, "0"); }
function stamp() {
  const d = new Date();
  return `${d.getFullYear()}${pad2(d.getMonth() + 1)}${pad2(d.getDate())}-${pad2(d.getHours())}${pad2(d.getMinutes())}`;
}

/* ── แปลงข้อมูลดิบ → ข้อความอ่านง่าย (วันที่/ชื่อสินค้า/ชื่อสาขา/ชื่อพนักงาน) ── */
function fmtDateTH(ts) {
  if (ts == null || ts === "") return "";
  const n = Number(ts);
  const d = new Date(isNaN(n) ? ts : n);
  if (isNaN(d.getTime())) return String(ts);
  return `${pad2(d.getDate())}/${pad2(d.getMonth() + 1)}/${d.getFullYear() + 543} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
}
function humanizeRows(key, rows, maps) {
  if (!Array.isArray(rows) || !rows.length) return rows;
  const pName = (id) => maps.products[id] || id;
  const bName = (id) => maps.branches[id] || id;
  const uName = (id) => maps.users[id] || id;
  const lName = (id) => maps.locations[id] || id;
  // แปลงรายการสินค้า [{productId,qty,price}] → "ชื่อ x2 (฿190); ..."
  const itemsText = (items) => {
    let arr = items;
    if (typeof items === "string") { try { arr = JSON.parse(items); } catch (e) { return items; } }
    if (!Array.isArray(arr)) return items;
    return arr.map((it) => {
      const nm = pName(it.productId || it.id) || it.name || it.desc || it.description || "รายการ";
      const qty = it.qty != null ? it.qty : 1;
      const price = Number(it.price != null ? it.price : (it.unitPrice != null ? it.unitPrice : 0));
      return `${nm} x${qty} (฿${price.toLocaleString("th-TH")})`;
    }).join("; ");
  };
  // แปลง array ข้อความ → "a, b, c"
  const listText = (v) => Array.isArray(v) ? v.map((x) => typeof x === "object" ? (pName(x.productId || x.id) + (x.qty ? ` x${x.qty}` : "")) : x).join(", ") : v;
  // แปลง object สต็อก {locId: qty} → "A1 หน้าร้าน: 2; ..."
  const stockText = (obj) => {
    if (!obj || typeof obj !== "object") return obj;
    const parts = Object.entries(obj).filter(([, q]) => q).map(([loc, q]) => `${lName(loc)}: ${q}`);
    return parts.join("; ");
  };
  const TS_KEYS = ["ts", "addedAt", "start", "end", "due", "createdAt", "updatedAt", "paidAt", "redeemedAt", "deliveredAt", "closedAt"];
  return rows.map((r) => {
    const o = { ...r };
    TS_KEYS.forEach((k) => { if (k in o && typeof o[k] === "number") o[k] = fmtDateTH(o[k]); });
    if ("date" in o && typeof o.date === "number") o.date = fmtDateTH(o.date);
    if ("branch" in o) o.branch = bName(o.branch);
    if ("by" in o) o.by = uName(o.by);
    if ("tech" in o) o.tech = uName(o.tech);
    if ("createdBy" in o) o.createdBy = uName(o.createdBy);
    if ("seller" in o && maps.users[o.seller]) o.seller = uName(o.seller);
    if ("items" in o) o.items = itemsText(o.items);
    if ("parts" in o) o.parts = itemsText(o.parts);
    if ("accessories" in o) o.accessories = listText(o.accessories);
    if ("stock" in o) o.stock = stockText(o.stock);
    if ("locId" in o) o.locId = lName(o.locId);
    if ("loc" in o) o.loc = lName(o.loc);
    if ("productId" in o) o.productId = pName(o.productId);
    return o;
  });
}
function buildMaps(st) {
  const m = { products: {}, branches: {}, users: {}, locations: {} };
  (st.products || []).forEach((p) => { m.products[p.id] = p.name + (p.imei ? ` (${p.imei})` : ""); });
  (st.branches || []).forEach((b) => { m.branches[b.id] = b.name; });
  (st.users || []).forEach((u) => { m.users[u.id] = u.name; });
  (st.locations || []).forEach((l) => { m.locations[l.id] = `${l.code} ${l.name}`; });
  return m;
}

function downloadBlob(bytes, filename, mime) {
  const blob = new Blob([bytes], { type: mime || "application/octet-stream" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url; a.download = filename;
  document.body.appendChild(a); a.click();
  setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 200);
}

/* ── สร้างไฟล์ Excel (.xlsx) รวมหลายชีตในไฟล์เดียว ── */
function xlsxEsc(s) {
  return String(s == null ? "" : s)
    .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;");
}
function colRef(n) {
  let s = "";
  n++;
  while (n > 0) { const r = (n - 1) % 26; s = String.fromCharCode(65 + r) + s; n = Math.floor((n - 1) / 26); }
  return s;
}
function sheetXml(rows) {
  const cols = rows && rows.length ? [...new Set(rows.flatMap((r) => Object.keys(r)))] : [];
  const cellXml = (v, ci, ri) => {
    const ref = colRef(ci) + (ri + 1);
    if (typeof v === "number" && isFinite(v)) return `<c r="${ref}"><v>${v}</v></c>`;
    let s = v == null ? "" : (typeof v === "object" ? JSON.stringify(v) : String(v));
    if (s === "") return `<c r="${ref}"/>`;
    return `<c r="${ref}" t="inlineStr"><is><t xml:space="preserve">${xlsxEsc(s)}</t></is></c>`;
  };
  let body = `<row r="1">` + cols.map((c, ci) => cellXml(c, ci, 0)).join("") + `</row>`;
  (rows || []).forEach((r, i) => {
    body += `<row r="${i + 2}">` + cols.map((c, ci) => cellXml(r[c], ci, i + 1)).join("") + `</row>`;
  });
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>${body}</sheetData></worksheet>`;
}
function buildXlsx(sheets) {
  const enc = new TextEncoder();
  const files = [];
  const safeName = (n, i) => { let s = String(n).replace(/[\\\/\?\*\[\]:]/g, " ").slice(0, 31).trim(); return s || `Sheet${i + 1}`; };
  const names = sheets.map((s, i) => safeName(s.name, i));
  files.push({ name: "[Content_Types].xml", data: enc.encode(
    `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>${sheets.map((s, i) => `<Override PartName="/xl/worksheets/sheet${i + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`).join("")}</Types>`) });
  files.push({ name: "_rels/.rels", data: enc.encode(
    `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>`) });
  files.push({ name: "xl/workbook.xml", data: enc.encode(
    `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets>${names.map((n, i) => `<sheet name="${xlsxEsc(n)}" sheetId="${i + 1}" r:id="rId${i + 1}"/>`).join("")}</sheets></workbook>`) });
  files.push({ name: "xl/_rels/workbook.xml.rels", data: enc.encode(
    `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">${sheets.map((s, i) => `<Relationship Id="rId${i + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet${i + 1}.xml"/>`).join("")}</Relationships>`) });
  sheets.forEach((s, i) => { files.push({ name: `xl/worksheets/sheet${i + 1}.xml`, data: enc.encode(sheetXml(s.rows)) }); });
  return makeZip(files);
}
function buildBackupXlsx(st, invoices) {
  const maps = buildMaps(st);
  const sheets = [];
  for (const [key, label] of BACKUP_TABLES) {
    const rows = Array.isArray(st[key]) ? st[key] : [];
    sheets.push({ name: label, rows: humanizeRows(key, rows, maps) });
  }
  sheets.push({ name: "ใบกำกับภาษี", rows: humanizeRows("invoices", invoices || [], maps) });
  return buildXlsx(sheets);
}

/* ── สร้างไฟล์สำรอง .zip ── */
function buildBackupZip(st, invoices) {
  const enc = new TextEncoder();
  const BOM = "\uFEFF"; // ให้ Excel เปิดภาษาไทยถูก
  const maps = buildMaps(st);
  const files = [];
  for (const [key, label] of BACKUP_TABLES) {
    const rows = Array.isArray(st[key]) ? st[key] : [];
    const readable = humanizeRows(key, rows, maps);
    files.push({ name: `${label}.csv`, data: enc.encode(BOM + toCSV(readable)) });
  }
  // ใบกำกับภาษี (เก็บแยก localStorage)
  files.push({ name: "ใบกำกับภาษี.csv", data: enc.encode(BOM + toCSV(humanizeRows("invoices", invoices || [], maps))) });
  // ไฟล์กู้คืนแบบเต็ม (ใช้ตอน Restore — เชื่อถือได้ 100%)
  const full = { _ssphone_backup: true, version: 3, ts: Date.now(), store: st, invoices: invoices || [] };
  files.push({ name: "_full_backup.json", data: enc.encode(JSON.stringify(full)) });
  return makeZip(files);
}

/* ── แท็บสำรองข้อมูลในหน้าตั้งค่า ── */
function BackupTab() {
  const { st, dispatch } = useStore();
  const toast = useToast();
  const fileRef = React.useRef(null);
  const [confirmData, setConfirmData] = React.useState(null);

  const getInvoices = () => { try { return JSON.parse(localStorage.getItem("ssphone-invoices")) || []; } catch (e) { return []; } };

  const doExport = () => {
    try {
      const zip = buildBackupZip(st, getInvoices());
      downloadBlob(zip, `SSPHONE-สำรองข้อมูล-${stamp()}.zip`, "application/zip");
      toast("ดาวน์โหลดไฟล์สำรองข้อมูลเรียบร้อย");
    } catch (e) { toast("เกิดข้อผิดพลาดในการสำรอง: " + e.message, "danger"); }
  };

  const doExportXlsx = () => {
    try {
      const xlsx = buildBackupXlsx(st, getInvoices());
      downloadBlob(xlsx, `SSPHONE-ข้อมูล-${stamp()}.xlsx`, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
      toast("ดาวน์โหลด Excel (รวมชีต) เรียบร้อย");
    } catch (e) { toast("สร้าง Excel ไม่สำเร็จ: " + e.message, "danger"); }
  };

  const onPick = (e) => {
    const file = e.target.files[0];
    e.target.value = "";
    if (!file) return;
    const reader = new FileReader();
    reader.onload = () => {
      try {
        const bytes = new Uint8Array(reader.result);
        let full = null;
        if (file.name.toLowerCase().endsWith(".json")) {
          full = JSON.parse(new TextDecoder().decode(bytes));
        } else {
          const entries = readZip(bytes);
          const jsonEntry = entries["_full_backup.json"];
          if (!jsonEntry) throw new Error("ไม่พบไฟล์กู้คืนใน .zip (ต้องเป็นไฟล์ที่สำรองจากระบบนี้)");
          full = JSON.parse(new TextDecoder().decode(jsonEntry));
        }
        if (!full || !full._ssphone_backup || !full.store) throw new Error("ไฟล์ไม่ถูกต้อง");
        setConfirmData(full);
      } catch (err) { toast("อ่านไฟล์ไม่สำเร็จ: " + err.message, "danger"); }
    };
    reader.readAsArrayBuffer(file);
  };

  const doRestore = () => {
    if (!confirmData) return;
    try {
      dispatch({ type: "RESTORE_ALL", data: confirmData.store });
      try { localStorage.setItem("ssphone-invoices", JSON.stringify(confirmData.invoices || [])); } catch (e) {}
      setConfirmData(null);
      toast("กู้คืนข้อมูลเรียบร้อย");
    } catch (e) { toast("กู้คืนไม่สำเร็จ: " + e.message, "danger"); }
  };

  const counts = BACKUP_TABLES.map(([k, label]) => ({ label, n: Array.isArray(st[k]) ? st[k].length : 0 }));
  counts.push({ label: "ใบกำกับภาษี", n: getInvoices().length });

  return (
    <Card style={{ maxWidth: 720, padding: 24 }}>
      <div style={{ fontFamily: T.headFont, fontWeight: 700, fontSize: 17, marginBottom: 6 }}>สำรอง & กู้คืนข้อมูล</div>
      <div style={{ fontSize: 12.5, color: T.sub, marginBottom: 18, lineHeight: 1.6 }}>
        ดาวน์โหลดข้อมูลทั้งหมดเป็น Excel ไฟล์เดียว (แยกชีตตามประเภท) หรือ CSV แยกไฟล์ (รวมใน .zip) — เปิดด้วย Excel หรืออัปโหลดเข้า Google Sheets ได้
        <br />แนะนำให้สำรองทุกวันตอนปิดร้าน เพื่อกันข้อมูลสูญหาย
      </div>

      {/* สรุปจำนวน */}
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(150px, 1fr))", gap: 8, marginBottom: 20 }}>
        {counts.map((c) => (
          <div key={c.label} style={{ background: T.chipBg, borderRadius: 10, padding: "9px 12px", display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8 }}>
            <span style={{ fontSize: 12, color: T.sub, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{c.label}</span>
            <b style={{ fontFamily: T.headFont, fontSize: 14, color: T.text }}>{c.n}</b>
          </div>
        ))}
      </div>

      <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
        <Btn onClick={doExportXlsx}>⤓ ดาวน์โหลด Excel (รวมชีต .xlsx)</Btn>
        <Btn kind="ghost" onClick={doExport}>⤓ ดาวน์โหลด CSV แยกไฟล์ (.zip)</Btn>
        <Btn kind="ghost" onClick={() => fileRef.current && fileRef.current.click()}>⤒ กู้คืนจากไฟล์สำรอง</Btn>
        <input ref={fileRef} type="file" accept=".zip,.json" style={{ display: "none" }} onChange={onPick} />
      </div>

      <div style={{ marginTop: 18, padding: "11px 14px", background: T.warnBg, color: T.warnFg, borderRadius: 10, fontSize: 12, lineHeight: 1.6 }}>
        ⚠️ การกู้คืนจะ<b>เขียนทับข้อมูลปัจจุบันทั้งหมด</b> — ใช้เมื่อย้ายเครื่องหรือกู้ข้อมูลที่หายเท่านั้น
      </div>

      <Modal open={!!confirmData} onClose={() => setConfirmData(null)} title="ยืนยันการกู้คืนข้อมูล" width={460}
        footer={<React.Fragment>
          <Btn kind="ghost" onClick={() => setConfirmData(null)}>ยกเลิก</Btn>
          <Btn kind="danger" onClick={doRestore}>ยืนยันกู้คืน (เขียนทับ)</Btn>
        </React.Fragment>}>
        {confirmData && (
          <div style={{ fontSize: 13.5, lineHeight: 1.7 }}>
            ไฟล์สำรองเมื่อ: <b>{new Date(confirmData.ts).toLocaleString("th-TH")}</b>
            <div style={{ marginTop: 10, color: T.dangerFg }}>ข้อมูลปัจจุบันทั้งหมดจะถูกแทนที่ด้วยข้อมูลในไฟล์นี้ ดำเนินการต่อหรือไม่?</div>
          </div>
        )}
      </Modal>
    </Card>
  );
}
window.BackupTab = BackupTab;
