/* global React, I, UI, SSSData */
// ═══ คำนวณราคางานตัด/พับโลหะ — DXF + วาดเส้นพับ + ค่าวัสดุ/ค่าตัด/ค่าพับ + Extras ═══
// สูตร: ค่าวัสดุ = กรอบ กว้าง×ยาว×หนา×ความหนาแน่น (kg) × ฿/kg
//       ค่าตัด = ความยาวตัดรวม (ม.) × [อัตราฐาน ฿/ม./มม. × ความหนา (มม.)]
//       ค่าพับ = จำนวนเส้นพับ × ค่าพับ/จุด + ความยาวเส้นพับรวม (ม.) × ค่าพับ/เมตร
const { useState: useStatePC, useEffect: useEffectPC, useRef: useRefPC, useMemo: useMemoPC } = React;

const PC_HIST = "sss-price-calcs";
const PC_EXTRAS = "sss-price-extras";
const PC_RATES = "sss-price-rates";   // เรตราคา & ตั้งค่าเครื่อง — บันทึกครั้งเดียวใช้ทุกงานทุกเครื่อง
const PC_RATE_KEYS = ["pricePerKg", "matPrices", "cutBase", "machineRate", "laserW", "bendBase", "bendPerM", "setupBase", "partHandle", "scrapPct", "overheadPct", "margin", "minCharge", "minChargeOn", "priceMode"];
function pcGet(key, def) {
  const v = window.lsSyncGet ? window.lsSyncGet(key) : null;
  if (v != null) return v;
  try { return JSON.parse(localStorage.getItem(key) || "null") || def; } catch { return def; }
}
function pcSet(key, val) {
  if (window.lsSyncSet) window.lsSyncSet(key, val);
  else { try { localStorage.setItem(key, JSON.stringify(val)); } catch {} }
}
const PC_MATS = [
  { name: "เหล็ก (Steel)", density: 7.85, price: 60 },
  { name: "สแตนเลส 304/316", density: 7.93, price: 120 },
  { name: "อลูมิเนียม", density: 2.70, price: 120 },
  { name: "ทองเหลือง", density: 8.50, price: 280 },
  { name: "ทองแดง", density: 8.96, price: 320 },
];
const PC_MAT_DEF_PRICES = {}; PC_MATS.forEach((m, i) => { PC_MAT_DEF_PRICES[i] = m.price; });
// เติมราคารายวัสดุที่ขาด/เพี้ยนด้วยค่าเริ่มต้น — กันบล็อบที่เซฟไว้รุ่นเก่าทำราคาวัสดุอื่นค้างเป็นราคาเหล็ก
function pcHealMatPrices(mp) {
  const out = { ...PC_MAT_DEF_PRICES };
  if (mp && typeof mp === "object") {
    const vals = Object.keys(out).map(k => Number(mp[k])).filter(v => isFinite(v) && v > 0);
    // ทุกวัสดุราคาเดียวกันหมด = ร่องรอยค่า flat รุ่นเก่าทับทุกช่อง (ไม่มีใครตั้งสแตนเลส=ทองแดง=เหล็กจริง) → คงเฉพาะเหล็ก
    if (vals.length >= 2 && vals.every(v => v === vals[0])) { out[0] = vals[0]; return out; }
    Object.keys(out).forEach(k => { const v = Number(mp[k]); if (isFinite(v) && v > 0) out[k] = v; });
  }
  return out;
}
const pcF = (n, d = 2) => (Number(n) || 0).toLocaleString("th-TH", { maximumFractionDigits: d });

// ไฮไลต์ตัวเลขสำคัญ (ขนาด/น้ำหนัก/ความยาว/จุด/แผ่น) ให้เด่น — ผู้ใช้กวาดตาตรวจได้เร็ว
const PC_HL_RE = /(\d[\d.,]*(?:×\d[\d.,]*)*(?:\s?(?:มม\.|ตร\.ม\.|ม\.\/นาที|ม\.|kg|จุด|พาร์ท|แผ่น|นาที|%))?)/g;
const pcHL = (txt) => String(txt).split(PC_HL_RE).map((seg, i) =>
  /^\d/.test(seg) ? <b key={i} style={{color: "var(--warning)", fontSize: "1.08em"}}>{seg}</b> : seg);
const pcHLHtml = (txt) => String(txt).replace(PC_HL_RE, (m) => /^\d/.test(m) ? `<b style="color:#d97706;font-size:1.08em">${m}</b>` : m);

// ความเร็วตัด (ม./นาที) ตามความหนา — ตารางกลางเครื่องไฟเบอร์ ~1.5-2kW (override ได้ในหน้าจอ)
const PC_SPEEDS = {
  steel:  [[1, 9], [2, 5.5], [3, 3.5], [4, 2.8], [5, 2.2], [6, 1.8], [8, 1.2], [10, 0.9], [12, 0.6], [16, 0.35]],
  ss:     [[1, 8], [2, 4.5], [3, 2.5], [4, 1.6], [5, 1.2], [6, 0.9], [8, 0.5], [10, 0.3], [12, 0.2]],
  alu:    [[1, 7], [2, 4], [3, 2.2], [4, 1.4], [5, 1.0], [6, 0.8], [8, 0.45]],
};
const pcSpeedGroup = (matIdx) => matIdx === 0 ? "steel" : matIdx === 2 ? "alu" : "ss";
// ตัวคูณกำลังเครื่อง — ตารางฐาน = ไฟเบอร์ 1500W (อ้างอิงสเปคผู้ผลิตทั่วไป: Bodor/HSG/Raycus 1.5kW)
const PC_POWER_FAC = { 1000: 0.72, 1500: 1, 2000: 1.25, 3000: 1.7, 4000: 2.0, 6000: 2.6 };
function pcSpeedOf(matIdx, T) {
  const tb = PC_SPEEDS[pcSpeedGroup(matIdx)] || PC_SPEEDS.ss;
  const t = Number(T) || 1;
  if (t <= tb[0][0]) return tb[0][1];
  for (let i = 0; i < tb.length - 1; i++) {
    const [t1, v1] = tb[i], [t2, v2] = tb[i + 1];
    if (t <= t2) return v1 + (v2 - v1) * ((t - t1) / (t2 - t1));   // interpolate
  }
  return tb[tb.length - 1][1] * Math.pow(0.8, t - tb[tb.length - 1][0]);   // หนากว่าตาราง — ลดต่อ
}

// ─────────── DXF parser (ASCII) — LINE/LWPOLYLINE(+bulge)/POLYLINE/CIRCLE/ARC/SPLINE ───────────
// คืน { polylines: [[{x,y},...]], cutLen (mm), bbox {minX,minY,maxX,maxY} }
function pcParseDXF(text) {
  const lines = text.split(/\r\n|\r|\n/);
  const pairs = [];
  for (let i = 0; i + 1 < lines.length; i += 2) {
    const code = parseInt(lines[i].trim(), 10);
    if (!isNaN(code)) pairs.push([code, lines[i + 1]]);
  }
  const polylines = [];
  const arcPts = (cx, cy, r, a0, a1, ccw = true) => {
    let sweep = a1 - a0;
    if (ccw && sweep <= 0) sweep += Math.PI * 2;
    if (!ccw && sweep >= 0) sweep -= Math.PI * 2;
    const n = Math.max(8, Math.ceil(Math.abs(sweep) / (Math.PI / 16)));
    const pts = [];
    for (let k = 0; k <= n; k++) { const a = a0 + sweep * (k / n); pts.push({ x: cx + r * Math.cos(a), y: cy + r * Math.sin(a) }); }
    return pts;
  };
  // แปลง bulge ระหว่างจุด 2 จุดเป็นส่วนโค้ง
  const bulgeSeg = (p1, p2, b) => {
    if (!b) return [p1, p2];
    const th = 4 * Math.atan(b);
    const chord = Math.hypot(p2.x - p1.x, p2.y - p1.y);
    if (!chord) return [p1, p2];
    const r = chord / (2 * Math.sin(Math.abs(th) / 2));
    const mx = (p1.x + p2.x) / 2, my = (p1.y + p2.y) / 2;
    const d = Math.sqrt(Math.max(0, r * r - chord * chord / 4)) * (b > 0 ? 1 : -1);
    const nx = -(p2.y - p1.y) / chord, ny = (p2.x - p1.x) / chord;
    const cx = mx + nx * d * (Math.abs(th) > Math.PI ? -1 : 1), cy = my + ny * d * (Math.abs(th) > Math.PI ? -1 : 1);
    const a0 = Math.atan2(p1.y - cy, p1.x - cx), a1 = Math.atan2(p2.y - cy, p2.x - cx);
    return arcPts(cx, cy, r, a0, a1, b > 0);
  };
  let i = 0;
  const findEntities = () => { while (i < pairs.length && !(pairs[i][0] === 2 && pairs[i][1].trim() === "ENTITIES")) i++; };
  findEntities();
  let cur = null;   // entity ปัจจุบัน
  const flush = () => {
    if (!cur) return;
    try {
      if (cur.type === "LINE" && cur.x1 != null) polylines.push([{ x: cur.x1, y: cur.y1 }, { x: cur.x2, y: cur.y2 }]);
      else if (cur.type === "CIRCLE" && cur.r) polylines.push(arcPts(cur.cx, cur.cy, cur.r, 0, Math.PI * 2 - 1e-9));
      else if (cur.type === "ARC" && cur.r) polylines.push(arcPts(cur.cx, cur.cy, cur.r, (cur.a0 || 0) * Math.PI / 180, (cur.a1 || 0) * Math.PI / 180));
      else if ((cur.type === "LWPOLYLINE" || cur.type === "POLYLINE") && cur.verts && cur.verts.length > 1) {
        let pts = [];
        const vs = cur.verts;
        for (let k = 0; k < vs.length - 1; k++) pts = pts.concat(bulgeSeg(vs[k], vs[k + 1], vs[k].b || 0).slice(k ? 1 : 0));
        if (cur.closed) pts = pts.concat(bulgeSeg(vs[vs.length - 1], vs[0], vs[vs.length - 1].b || 0).slice(1));
        polylines.push(pts);
      } else if (cur.type === "SPLINE" && cur.fit && cur.fit.length > 1) polylines.push(cur.fit);
    } catch {}
    cur = null;
  };
  for (; i < pairs.length; i++) {
    const [code, raw] = pairs[i];
    const v = raw.trim();
    if (code === 0) {
      flush();
      if (v === "ENDSEC") break;
      if (["LINE", "CIRCLE", "ARC", "LWPOLYLINE", "SPLINE"].includes(v)) cur = { type: v, verts: [], fit: [] };
      else if (v === "POLYLINE") cur = { type: "POLYLINE", verts: [] };
      else if (v === "VERTEX" && cur && cur.type === "POLYLINE") cur._vx = true;
      else if (v === "SEQEND") { /* จบ POLYLINE — flush ตอน 0 ถัดไป */ }
      else if (cur && cur.type === "POLYLINE") { /* คง cur ไว้ */ }
      continue;
    }
    if (!cur) continue;
    const f = parseFloat(v);
    if (cur.type === "LINE") {
      if (code === 10) cur.x1 = f; else if (code === 20) cur.y1 = f;
      else if (code === 11) cur.x2 = f; else if (code === 21) cur.y2 = f;
    } else if (cur.type === "CIRCLE" || cur.type === "ARC") {
      if (code === 10) cur.cx = f; else if (code === 20) cur.cy = f;
      else if (code === 40) cur.r = f; else if (code === 50) cur.a0 = f; else if (code === 51) cur.a1 = f;
    } else if (cur.type === "LWPOLYLINE") {
      if (code === 10) cur.verts.push({ x: f, y: 0 });
      else if (code === 20 && cur.verts.length) cur.verts[cur.verts.length - 1].y = f;
      else if (code === 42 && cur.verts.length) cur.verts[cur.verts.length - 1].b = f;
      else if (code === 70) cur.closed = (parseInt(v, 10) & 1) === 1;
    } else if (cur.type === "POLYLINE") {
      if (cur._vx) {
        if (code === 10) cur.verts.push({ x: f, y: 0 });
        else if (code === 20 && cur.verts.length) cur.verts[cur.verts.length - 1].y = f;
        else if (code === 42 && cur.verts.length) cur.verts[cur.verts.length - 1].b = f;
      } else if (code === 70) cur.closed = (parseInt(v, 10) & 1) === 1;
    } else if (cur.type === "SPLINE") {
      if (code === 11) cur.fit.push({ x: f, y: 0 });
      else if (code === 21 && cur.fit.length) cur.fit[cur.fit.length - 1].y = f;
      else if (code === 10 && !cur._hasFit) cur.fit.push({ x: f, y: 0 });   // ไม่มี fit points → ใช้ control points โดยประมาณ
      else if (code === 20 && cur.fit.length) cur.fit[cur.fit.length - 1].y = f;
    }
  }
  flush();
  let cutLen = 0;
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
  polylines.forEach(pl => pl.forEach((p, k) => {
    if (k) cutLen += Math.hypot(p.x - pl[k - 1].x, p.y - pl[k - 1].y);
    if (p.x < minX) minX = p.x; if (p.x > maxX) maxX = p.x;
    if (p.y < minY) minY = p.y; if (p.y > maxY) maxY = p.y;
  }));
  if (!polylines.length) return null;
  // ── จับกลุ่มเป็น "พาร์ท" แบบ containment: contour ที่อยู่ "ข้างใน" contour อื่น (รู/ลายแกะ)
  //    รวมเข้าพาร์ทแม่เท่านั้น — ชิ้นที่วางชิด/ติดกัน (จัด nesting) ยังนับแยกพาร์ทเสมอ ──
  const tol = 2;   // มม. เผื่อเส้นทับขอบ
  const boxes = polylines.map(pl => {
    let a = Infinity, b = Infinity, c = -Infinity, d = -Infinity;
    pl.forEach(p => { if (p.x < a) a = p.x; if (p.x > c) c = p.x; if (p.y < b) b = p.y; if (p.y > d) d = p.y; });
    return { minX: a, minY: b, maxX: c, maxY: d, area: Math.max(0, c - a) * Math.max(0, d - b) };
  }).sort((x, y) => y.area - x.area);   // ใหญ่ก่อน — ตัวแม่มาก่อนรู
  const parts = [];
  boxes.forEach(bx => {
    const parent = parts.find(p =>
      bx.minX >= p.minX - tol && bx.maxX <= p.maxX + tol &&
      bx.minY >= p.minY - tol && bx.maxY <= p.maxY + tol);
    if (!parent) parts.push({ minX: bx.minX, minY: bx.minY, maxX: bx.maxX, maxY: bx.maxY });
    // อยู่ข้างในพาร์ทที่มีอยู่ = รู/ลายแกะ — ไม่ขยายกรอบ ไม่นับเพิ่ม
  });
  const partsArea = parts.reduce((sum, p) => sum + Math.max(0, p.maxX - p.minX) * Math.max(0, p.maxY - p.minY), 0);   // ตร.มม.
  return { polylines, cutLen, bbox: { minX, minY, maxX, maxY }, pierces: polylines.length, parts, partsArea };
}

// ─────────── หน้า คำนวณราคา ───────────
function PriceCalcPage({ navigate }) {
  const D = window.SSSData;
  const { Btn, Badge, Card, PageHead } = window.UI;
  const [dxf, setDxf] = useStatePC(null);          // ผลพาร์ส DXF
  const [dxfName, setDxfName] = useStatePC("");
  const [drawn, setDrawn] = useStatePC([]);         // เส้นที่ user วาด [{id,x1,y1,x2,y2,bend}]
  const [drawMode, setDrawMode] = useStatePC(false);
  const [snapOrtho, setSnapOrtho] = useStatePC(true);
  const [pending, setPending] = useStatePC(null);   // จุดแรกที่คลิก
  const [hover, setHover] = useStatePC(null);
  const svgRef = useRefPC(null);
  const [zoomV, setZoomV] = useStatePC(null);        // หน้าต่างซูม {x,y,w,h} — null = พอดีแบบ
  const panRef = useRefPC(null);

  const [f, setF] = useStatePC(() => ({
    name: "", customer: "", qty: 1,
    W: "", L: "", T: 3, mat: 0, pricePerKg: 60,
    matPrices: { ...PC_MAT_DEF_PRICES },   // ราคา ฿/kg รายวัสดุ — เปลี่ยนวัสดุแล้วสลับตาม
    cutLenM: "",           // ความยาวตัดรวม (ม.) — เติมอัตโนมัติจาก DXF แก้เองได้
    cutBase: 15,           // อัตราฐานค่าตัด ฿/ม./มม.
    bendPerHit: "", bendPerM: 0,  // ค่าพับ/จุด (แนะนำอัตโนมัติ = ฐาน×หนา) และ ค่าพับ/เมตร
    bendBase: 12,          // ฐานค่าพับ ฿/จุด/มม.
    margin: 20,            // กำไร %
    priceMode: "time",     // "time" = คิดตามเวลาเครื่อง (แนะนำ) · "meter" = แบบ ฿/ม.×หนา เดิม
    laserW: 1500,          // กำลังเลเซอร์ (W) — สเกลตารางความเร็วอัตโนมัติ
    bendAuto: true,        // ค่าพับอัตโนมัติจากความยาวเส้น + ความยากตามความหนา
    machineRate: 1200,     // เรตเครื่องรวมต้นทุน+กำไรเครื่อง (฿/ชม.)
    speedOv: "",           // override ความเร็วตัด (ม./นาที) — ว่าง = ใช้ตาราง
    pierceN: "",           // จำนวนจุดเจาะนำ — เติมจาก DXF อัตโนมัติ แก้ได้
    partsN: "",            // จำนวนพาร์ท (ชิ้นย่อยในแบบ) — ใช้คิดค่าเก็บ/setup · เติมจาก DXF แก้ได้
    scrapPct: 20,          // เศษวัสดุเผื่อ % (เศษรอบชิ้นงานขายคืนไม่ได้)
    setupBase: 80,         // ค่าโหลดแผ่นขึ้น-ลงเครื่อง (฿/แผ่น) — 0 = ไม่คิด
    partHandle: 2,         // ค่าเก็บ/คัดแยกชิ้นงานหลังตัด (฿/พาร์ท)
    overheadPct: 15,       // โสหุ้ยโรงงาน % (ไฟ/เช่า/เสื่อม)
    minCharge: 300,        // ราคาขั้นต่ำต่องาน
    minChargeOn: true,     // เปิด/ปิดการใช้ขั้นต่ำ
    ...(() => { const saved = pcGet(PC_RATES, {}) || {}; const out = {}; PC_RATE_KEYS.forEach(k => { if (saved[k] !== undefined) out[k] = saved[k]; }); return out; })(),   // เรตที่บันทึกไว้ทับค่าเริ่มต้น
    // ราคาวัสดุยึดชุดรายวัสดุเสมอ — ค่า flat ที่เซฟค้างจากวัสดุอื่นจะไม่หลุดมาทับ (บั๊ก: บันทึกที่เหล็กแล้วสลับวัสดุ ราคาไม่เปลี่ยน)
    ...(() => {
      const saved = pcGet(PC_RATES, {}) || {};
      const mp = pcHealMatPrices(saved.matPrices);
      if (!saved.matPrices && Number(saved.pricePerKg) > 0) mp[0] = Number(saved.pricePerKg);   // บล็อบรุ่นเก่ามีค่าเดียว = ถือเป็นราคาเหล็ก
      return { matPrices: mp, pricePerKg: mp[0] };
    })(),
  }));
  const upd = (k, v) => setF(p => ({ ...p, [k]: v }));
  const [extras, setExtras] = useStatePC(() => pcGet(PC_EXTRAS, []));
  const [extraSel, setExtraSel] = useStatePC(() => new Set());
  const [showExtras, setShowExtras] = useStatePC(false);
  const [photos, setPhotos] = useStatePC([]);
  const [hist, setHist] = useStatePC(() => pcGet(PC_HIST, []));
  const [tab, setTab] = useStatePC("calc");
  const [showRates, setShowRates] = useStatePC(false);   // โซนเรต — พับเก็บ (ตั้งครั้งเดียว)
  const saveRates = () => {
    const out = {};
    PC_RATE_KEYS.forEach(k => { out[k] = f[k]; });
    out.matPrices = pcHealMatPrices(f.matPrices);   // เก็บเป็นตัวเลขครบทุกวัสดุ (ช่องว่าง/0 = กลับค่าเริ่มต้น)
    out.pricePerKg = out.matPrices[f.mat] != null ? out.matPrices[f.mat] : (Number(f.pricePerKg) || 0);
    pcSet(PC_RATES, out);
    setF(p => ({ ...p, matPrices: out.matPrices, pricePerKg: out.matPrices[p.mat] != null ? out.matPrices[p.mat] : p.pricePerKg }));
    window.toast && window.toast("💾 บันทึกเรตราคา & ตั้งค่าเครื่องแล้ว — ใช้กับทุกงาน ซิงค์ทุกเครื่อง");
  };
  const resetRates = () => {
    setF(p => ({ ...p, pricePerKg: PC_MAT_DEF_PRICES[p.mat] != null ? PC_MAT_DEF_PRICES[p.mat] : 60, matPrices: { ...PC_MAT_DEF_PRICES }, cutBase: 15, machineRate: 1200, laserW: 1500, bendBase: 12, bendPerM: 0, setupBase: 80, partHandle: 2, scrapPct: 20, overheadPct: 15, margin: 20, minCharge: 300, priceMode: "time" }));
    pcSet(PC_RATES, {});
    window.toast && window.toast("คืนค่าเริ่มต้นระบบแล้ว");
  };

  useEffectPC(() => {
    const h = () => { setExtras(pcGet(PC_EXTRAS, [])); setHist(pcGet(PC_HIST, [])); };
    const evts = ["sss-localstore-polled", "sss-db-loaded", "sss-data-synced"];
    evts.forEach(e => window.addEventListener(e, h));
    h();   // เผื่อโหลดเสร็จก่อนหน้านี้แล้ว
    return () => evts.forEach(e => window.removeEventListener(e, h));
  }, []);

  // ── อัพโหลด DXF ──
  const onDXF = (e) => {
    const file = e.target.files[0]; e.target.value = "";
    if (!file) return;
    const r = new FileReader();
    r.onload = () => {
      const parsed = pcParseDXF(String(r.result || ""));
      if (!parsed) { window.toast && window.toast("อ่านไฟล์ DXF ไม่ได้ — รองรับ DXF แบบ ASCII (R12+)"); return; }
      setDxf(parsed); setDxfName(file.name); setDrawn([]); setPending(null);
      const W = parsed.bbox.maxX - parsed.bbox.minX, L = parsed.bbox.maxY - parsed.bbox.minY;
      setF(p => ({ ...p, W: Math.round(W * 100) / 100, L: Math.round(L * 100) / 100, cutLenM: Math.round(parsed.cutLen / 10) / 100, pierceN: parsed.pierces, partsN: (parsed.parts || []).length || 1, name: p.name || file.name.replace(/\.dxf$/i, "") }));
      window.toast && window.toast(`อ่าน DXF สำเร็จ — ตัดรวม ${pcF(parsed.cutLen / 1000)} ม. · กรอบ ${pcF(W, 0)}×${pcF(L, 0)} มม.`);
    };
    r.readAsText(file);
  };

  // ── กระดานวาด (SVG viewBox = มม. จริง) ──
  const CANVAS_MM = 600;   // กระดานเปล่าเริ่มต้น 600×600 มม.
  const vb = dxf
    ? (() => { const b = dxf.bbox, pad = Math.max(20, (b.maxX - b.minX) * 0.06); return { x: b.minX - pad, y: b.minY - pad, w: (b.maxX - b.minX) + pad * 2, h: (b.maxY - b.minY) + pad * 2 }; })()
    : { x: 0, y: 0, w: CANVAS_MM, h: CANVAS_MM };
  const Y = (y) => (vb.y + vb.h) - (y - vb.y);   // พลิกแกน Y (DXF ขึ้น = จอลง)
  const vbe = zoomV || vb;                            // viewBox ที่แสดงจริง (ซูม/แพน)
  const zoomAt = (factor, cx, cy) => {
    setZoomV(prev => {
      const cur = prev || vb;
      const w = Math.max(vb.w / 40, Math.min(vb.w * 3, cur.w * factor));
      const h = w * (vb.h / vb.w);
      const fx = cx != null ? (cx - cur.x) / cur.w : 0.5;
      const fy = cy != null ? (cy - cur.y) / cur.h : 0.5;
      return { x: (cx != null ? cx : cur.x + cur.w / 2) - w * fx, y: (cy != null ? cy : cur.y + cur.h / 2) - h * fy, w, h };
    });
  };
  // wheel zoom รอบเคอร์เซอร์ (ต้อง addEventListener เองเพื่อ preventDefault)
  useEffectPC(() => {
    const svg = svgRef.current; if (!svg) return;
    const onWheel = (e) => {
      e.preventDefault();
      const pt = svg.createSVGPoint(); pt.x = e.clientX; pt.y = e.clientY;
      const m = svg.getScreenCTM(); if (!m) return;
      const p = pt.matrixTransform(m.inverse());
      zoomAt(e.deltaY > 0 ? 1.2 : 1 / 1.2, p.x, p.y);
    };
    svg.addEventListener("wheel", onWheel, { passive: false });
    return () => svg.removeEventListener("wheel", onWheel);
  });
  const svgPt = (e) => {
    const svg = svgRef.current; if (!svg) return null;
    const pt = svg.createSVGPoint(); pt.x = e.clientX; pt.y = e.clientY;
    const m = svg.getScreenCTM(); if (!m) return null;
    const p = pt.matrixTransform(m.inverse());
    return { x: p.x, y: (vb.y + vb.h) - (p.y - vb.y) };   // กลับเป็นพิกัด DXF
  };
  const snapPt = (p0, p) => {
    if (!snapOrtho || !p0) return p;
    const dx = Math.abs(p.x - p0.x), dy = Math.abs(p.y - p0.y);
    if (dx > dy * 3) return { x: p.x, y: p0.y };
    if (dy > dx * 3) return { x: p0.x, y: p.y };
    return p;
  };
  const onSvgClick = (e) => {
    if (!drawMode) return;
    const p = svgPt(e); if (!p) return;
    if (!pending) { setPending(p); return; }
    const p2 = snapPt(pending, p);
    const len = Math.hypot(p2.x - pending.x, p2.y - pending.y);
    if (len > 1) setDrawn(list => [...list, { id: "d" + Date.now().toString(36), x1: pending.x, y1: pending.y, x2: p2.x, y2: p2.y, bend: true }]);
    setPending(null); setHover(null);
  };
  const onSvgMove = (e) => {
    if (drawMode && pending) { const p = svgPt(e); if (p) setHover(snapPt(pending, p)); return; }
    if (panRef.current) {
      const svg = svgRef.current; if (!svg) return;
      const m = svg.getScreenCTM(); if (!m) return;
      const dx = (e.clientX - panRef.current.sx) / m.a, dy = (e.clientY - panRef.current.sy) / m.d;
      setZoomV({ ...panRef.current.v, x: panRef.current.v.x - dx, y: panRef.current.v.y - dy });
    }
  };
  const onSvgDown = (e) => { if (!drawMode) panRef.current = { sx: e.clientX, sy: e.clientY, v: zoomV || vb }; };
  const onSvgUp = () => { panRef.current = null; };
  useEffectPC(() => {
    const onKey = (e) => { if (e.key === "Escape") { setPending(null); setHover(null); } };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  // ── คำนวณ ──
  const mat = PC_MATS[f.mat] || PC_MATS[0];
  const W = Number(f.W) || 0, L = Number(f.L) || 0, T = Number(f.T) || 0, qty = Math.max(1, Number(f.qty) || 1);
  // วัสดุ: มี DXF หลายพาร์ท → ใช้ผลรวมกรอบรายพาร์ท (กรอบแดง) แทนกรอบทั้งไฟล์ที่กินพื้นที่ว่าง
  const usePartFrames = !!(f.framePart !== false && dxf && dxf.parts && dxf.parts.length > 1);
  const areaMM2 = usePartFrames ? dxf.partsArea : (W * L);
  const kg = areaMM2 * T * mat.density / 1e6;                      // น้ำหนัก/ชิ้น
  const scrapPct = Number(f.scrapPct) || 0;
  const matCost = kg * (Number(f.pricePerKg) || 0) * (1 + scrapPct / 100);   // + เศษเผื่อรอบชิ้นที่ขายคืนไม่ได้
  const cutLenM = Number(f.cutLenM) || 0;
  // ── โหมดเวลาเครื่อง (แนะนำ): จุดวัดแท้จริงของงานเลเซอร์คือ "นาทีเครื่อง" ──
  const powerFac = PC_POWER_FAC[Number(f.laserW)] || 1;
  // ×0.35 = ประสิทธิภาพจริง (จูนตามหน้างาน — เวลาเครื่อง ≈ 2 เท่าของตารางสเปค) — เผื่อช่วงเร่ง/ชะลอ หัวเดินเปล่า และเวลาขึ้น-ลงชิ้นงาน
  const PC_EFF = 0.35;
  const speedAuto = pcSpeedOf(f.mat, T) * powerFac * PC_EFF;
  const speed = f.speedOv !== "" ? (Number(f.speedOv) || speedAuto) : speedAuto;
  const pierceN = f.pierceN !== "" ? (Number(f.pierceN) || 0) : 0;
  const pierceSec = 0.3 + 0.25 * T;                                         // วินาที/จุด (หนาขึ้นเจาะนานขึ้น)
  const cutMin = speed > 0 ? (cutLenM / speed) + (pierceN * pierceSec / 60) : 0;   // นาทีเครื่อง/ชิ้น
  const cutRateMeter = (Number(f.cutBase) || 0) * T;                        // โหมดเดิม ฿/ม.
  const cutCost = f.priceMode === "meter"
    ? cutLenM * cutRateMeter
    : cutMin * ((Number(f.machineRate) || 0) / 60);
  const bendLines = drawn.filter(d => d.bend);
  const bendLenM = bendLines.reduce((s, d) => s + Math.hypot(d.x2 - d.x1, d.y2 - d.y1), 0) / 1000;
  // ── ค่าพับอัตโนมัติ: ราคา/จุด = ฐาน × ความยาก(ความหนา) × ตัวคูณความยาวเส้นพับ ──
  // บาง (≤1.5มม.) พับง่าย ×0.8 · 2-3มม. ปกติ ×1 · 4มม. ×1.4 · 6มม. ×2 · หนากว่านั้น ×3 (ต้องเครื่องใหญ่/หลายคน)
  // เส้นยาว ≤50ซม. ×1 · ≤120ซม. ×1.5 (จับสองคน) · ยาวกว่านั้น ×2.2
  const bendDiff = T <= 1.5 ? 0.8 : T <= 3 ? 1 : T <= 4 ? 1.4 : T <= 6 ? 2 : 3;
  const bendLenFac = (mm) => mm <= 500 ? 1 : mm <= 1200 ? 1.5 : 2.2;
  const bendAutoEach = bendLines.map(d => {
    const mm = Math.hypot(d.x2 - d.x1, d.y2 - d.y1);
    return { mm, cost: (Number(f.bendBase) || 0) * bendDiff * bendLenFac(mm) };
  });
  const bendAutoCost = bendAutoEach.reduce((s, b) => s + b.cost, 0);
  const bendPerHit = f.bendPerHit === "" ? (Number(f.bendBase) || 0) * T : Number(f.bendPerHit) || 0;
  const bendCost = f.bendAuto ? bendAutoCost : (bendLines.length * bendPerHit + bendLenM * (Number(f.bendPerM) || 0));
  const extrasSum = extras.filter(x => extraSel.has(x.id)).reduce((s, x) => s + (Number(x.price) || 0), 0);
  // ── ค่า setup ขึ้น-ลงชิ้นงาน (ต่อรอบงาน): โหลดแผ่น × จำนวนแผ่นโดยประมาณ + เก็บชิ้น × พาร์ททั้งหมด ──
  // จำนวนแผ่น ≈ พื้นที่รวมทุกชิ้น (รวมเศษเผื่อ) ÷ แผ่นมาตรฐาน 1220×2440 มม. (~2.98 ตร.ม.)
  const SHEET_M2 = 1.22 * 2.44;
  const totalAreaM2 = (areaMM2 / 1e6) * qty * (1 + scrapPct / 100);
  const sheetsEst = Math.max(1, Math.ceil(totalAreaM2 / SHEET_M2));
  // จำนวนพาร์ทสำหรับค่าเก็บ/คัดแยก — ใช้ช่องที่แก้ได้เสมอ (ไม่เกี่ยวกับสวิตช์กรอบวัสดุ)
  const partsPerPiece = f.partsN !== "" ? Math.max(1, Number(f.partsN) || 1) : ((dxf && dxf.parts && dxf.parts.length) || 1);
  const totalParts = partsPerPiece * qty;
  const setupCost = (Number(f.setupBase) || 0) * sheetsEst + (Number(f.partHandle) || 0) * totalParts;
  const perPiece = matCost + cutCost + bendCost;
  const subtotal = perPiece * qty + extrasSum + setupCost;
  const withOverhead = subtotal * (1 + (Number(f.overheadPct) || 0) / 100);   // โสหุ้ย = ต้นทุนแฝง (ไฟ/เช่า/เสื่อม)
  const grandRaw = withOverhead * (1 + (Number(f.margin) || 0) / 100);        // กำไรจริง
  const grand = f.minChargeOn ? Math.max(grandRaw, Number(f.minCharge) || 0) : grandRaw;   // ขั้นต่ำ (เมื่อเปิดใช้)
  const perPieceFinal = qty ? grand / qty : grand;
  // ── sanity check ฿/kg — ให้ user เทียบตลาดได้ทันที ──
  const totalKg = kg * qty;
  const pricePerKgFinal = totalKg > 0 ? grand / totalKg : 0;
  const kgLow = (Number(f.pricePerKg) || 0) * 1.4, kgHigh = (Number(f.pricePerKg) || 0) * 8;
  const kgVerdict = totalKg <= 0 ? null
    : pricePerKgFinal < kgLow ? { tone: "var(--danger)", msg: `ถูกผิดปกติ — ต่ำกว่า ${pcF(kgLow, 0)} ฿/kg อาจขาดทุน (ลืมค่าแรง/ของเสีย?)` }
    : pricePerKgFinal > kgHigh ? { tone: "var(--warning)", msg: `สูงกว่าช่วงตลาดทั่วไป — งานละเอียด/ชิ้นเล็กอาจสมเหตุผล แต่ตรวจตัวเลขอีกรอบ` }
    : { tone: "var(--success)", msg: "อยู่ในช่วงราคาตลาดงานตัดทั่วไป" };

  const breakdown = [
    { label: usePartFrames
      ? `ค่าวัสดุ — ${mat.name} กรอบรายพาร์ท ${dxf.parts.length} พาร์ท รวม ${pcF(areaMM2 / 1e6, 3)} ตร.ม. × ${T} มม. = ${pcF(kg, 2)} kg × ฿${pcF(f.pricePerKg)}${scrapPct ? ` + เศษเผื่อ ${scrapPct}%` : ""}`
      : `ค่าวัสดุ — ${mat.name} ${pcF(W, 0)}×${pcF(L, 0)}×${T} มม. = ${pcF(kg, 2)} kg × ฿${pcF(f.pricePerKg)}${scrapPct ? ` + เศษเผื่อ ${scrapPct}%` : ""}`, amt: matCost, per: true },
    f.priceMode === "meter"
      ? { label: `ค่าตัด — ${pcF(cutLenM, 2)} ม. × (฿${pcF(f.cutBase)}/ม./มม. × ${T} มม. = ฿${pcF(cutRateMeter)}/ม.)`, amt: cutCost, per: true }
      : { label: `ค่าตัด — เวลาเครื่อง ${pcF(cutMin, 1)} นาที (ตัด ${pcF(cutLenM, 2)} ม. @ ${pcF(speed, 1)} ม./นาที${pierceN ? ` + เจาะ ${pierceN} จุด` : ""}) × ฿${pcF(f.machineRate, 0)}/ชม.`, amt: cutCost, per: true },
    ...(bendLines.length ? [{
      label: f.bendAuto
        ? `ค่าพับอัตโนมัติ — ${bendLines.length} จุด · หนา ${T} มม. (ความยาก ×${pcF(bendDiff, 1)}) · ยาวรวม ${pcF(bendLenM, 2)} ม.`
        : `ค่าพับ — ${bendLines.length} จุด × ฿${pcF(bendPerHit)}${Number(f.bendPerM) ? ` + ${pcF(bendLenM, 2)} ม. × ฿${pcF(f.bendPerM)}` : ""}`,
      amt: bendCost, per: true }] : []),
    ...(setupCost > 0 ? [{ label: `ค่า setup ขึ้น-ลงชิ้นงาน — ~${sheetsEst} แผ่น × ฿${pcF(f.setupBase, 0)} + ${totalParts} พาร์ท × ฿${pcF(f.partHandle, 0)}`, amt: setupCost, per: false }] : []),
    ...extras.filter(x => extraSel.has(x.id)).map(x => ({ label: `Extras — ${x.name}`, amt: Number(x.price) || 0, per: false })),
  ];

  // ── รูปแนบ ──
  const resize = (file) => window.DeliveryStore && window.DeliveryStore.resizeImage
    ? window.DeliveryStore.resizeImage(file, 700)
    : new Promise(res => { const r = new FileReader(); r.onload = () => res(r.result); r.readAsDataURL(file); });
  const onPhotos = async (e) => {
    const files = [...e.target.files].slice(0, 6); e.target.value = "";
    const out = [];
    for (const file of files) { const url = await resize(file); if (url) out.push({ name: file.name, url }); }
    setPhotos(p => [...p, ...out].slice(0, 6));
  };

  // ── บันทึกประวัติ ──
  const saveHist = () => {
    if (!f.name.trim()) { window.toast && window.toast("ตั้งชื่องานก่อนบันทึก"); return; }
    const rec = {
      id: "PC-" + Date.now().toString(36), date: new Date().toISOString().slice(0, 10),
      name: f.name.trim(), customer: f.customer, dxfName,
      inputs: { ...f }, drawn, extrasSel: [...extraSel],
      results: { kg, matCost, cutCost, bendCost, bends: bendLines.length, extrasSum, perPiece: perPieceFinal, grand, qty },
      photos: [...photos],
      by: (window.Session && window.Session.get() && window.Session.get().name) || "",
    };
    const next = [rec, ...pcGet(PC_HIST, [])];
    setHist(next); pcSet(PC_HIST, next);
    window.toast && window.toast("บันทึกประวัติการคำนวณแล้ว");
  };
  const loadHist = (r) => {
    setF(p => ({ ...p, ...(r.inputs || {}), matPrices: pcHealMatPrices(((r.inputs || {}).matPrices) || p.matPrices) }));
    setDrawn(r.drawn || []); setDxf(null); setDxfName(r.dxfName || "");
    setExtraSel(new Set(r.extrasSel || [])); setPhotos(r.photos || []);
    setTab("calc");
    window.toast && window.toast("โหลด " + r.name + " กลับมาแก้แล้ว (ไฟล์ DXF ต้องอัพโหลดใหม่ถ้าจะดูแบบ)");
  };
  const delHist = async (r) => {
    const ok = await (window.confirmDelete ? window.confirmDelete("ประวัติ " + r.name, "฿" + pcF(r.results && r.results.grand)) : Promise.resolve(confirm("ลบ?")));
    if (!ok) return;
    const next = pcGet(PC_HIST, []).filter(x => x.id !== r.id);
    setHist(next); pcSet(PC_HIST, next);
  };

  // ── พิมพ์สรุปราคา ──
  const printSummary = () => {
    const C = D.Company;
    const svgEl = svgRef.current;
    let svgHTML = "";
    if (svgEl) {
      // ภาพพิมพ์: ไม่มีกริด · เส้นตัดสีดำ · ป้ายความยาวอ่านง่าย (Arial ขอบขาว) · fit ทั้งแบบเสมอ
      const clone = svgEl.cloneNode(true);
      clone.querySelectorAll('[data-grid]').forEach(el => el.remove());
      clone.setAttribute("viewBox", `${vb.x} ${vb.y} ${vb.w} ${vb.h}`);
      // ใส่ขนาดจริงให้ svg รู้สัดส่วน แล้วบีบด้วย max — แบบสูงๆ จะไม่ล้นเกิน 1 หน้ากระดาษ (ไม่โดนหั่นข้ามหน้า)
      clone.setAttribute("width", vb.w);
      clone.setAttribute("height", vb.h);
      clone.setAttribute("style", "display:block;width:auto;height:auto;max-width:100%;max-height:150mm;margin:0 auto;background:#ffffff");
      clone.querySelectorAll("polyline").forEach(el => el.setAttribute("stroke", "#000000"));
      clone.querySelectorAll("line").forEach(el => el.setAttribute("stroke", "#d97706"));
      clone.querySelectorAll("circle").forEach(el => el.setAttribute("fill", "#d97706"));
      clone.querySelectorAll("text").forEach(el => {
        el.setAttribute("fill", "#b45309");
        el.setAttribute("stroke", "#ffffff");
        el.setAttribute("font-family", "Arial, sans-serif");
      });
      svgHTML = `<div style="border:1px solid #e7e5e4;border-radius:8px;padding:8px;margin:10px 0;background:#ffffff;page-break-inside:avoid;break-inside:avoid">${clone.outerHTML}</div>`;
    }
    const html = `
      <div style="background:white;color:#0c0a09;padding:10mm 12mm;box-sizing:border-box;font-size:12px">
        <div style="display:flex;justify-content:space-between;border-bottom:2.5px solid #0c0a09;padding-bottom:9px;margin-bottom:12px">
          <div style="display:flex;gap:10px;align-items:center">
            <div style="width:40px;height:40px;background:#0c0a09;color:white;display:grid;place-items:center;font-weight:700;font-size:13px;border-radius:6px">SSS</div>
            <div><div style="font-weight:700;font-size:14px">สรุปราคางาน — ${(f.name || "ไม่ระบุชื่องาน").replace(/</g, "&lt;")}</div>
            <div style="font-size:11px;color:#57534e">${C.name}${f.customer ? " · ลูกค้า: " + f.customer : ""} · ${new Date().toLocaleDateString("th-TH", { day: "numeric", month: "short", year: "numeric" })}</div></div>
          </div>
          <div style="text-align:right;font-size:11px;color:#57534e">${dxfName ? "ไฟล์: " + dxfName : ""}</div>
        </div>
        <table style="width:100%;border-collapse:collapse;font-size:11.5px;margin-bottom:10px">
          <tr style="background:#f7f6f4"><th style="border:1px solid #d6d3d1;padding:5px 9px;text-align:left">รายการ</th><th style="border:1px solid #d6d3d1;padding:5px 9px;text-align:right;width:110px">บาท/ชิ้น</th></tr>
          ${breakdown.map(b => `<tr><td style="border:1px solid #d6d3d1;padding:5px 9px">${pcHLHtml(b.label)}${b.per ? "" : ' <span style="color:#78716c">(ต่อรอบงาน)</span>'}</td><td style="border:1px solid #d6d3d1;padding:5px 9px;text-align:right;font-family:monospace">${pcF(b.amt)}</td></tr>`).join("")}
          <tr><td style="border:1px solid #d6d3d1;padding:5px 9px"><b>ราคาต่อชิ้น (วัสดุ+ตัด+พับ)</b></td><td style="border:1px solid #d6d3d1;padding:5px 9px;text-align:right;font-family:monospace"><b>${pcF(perPiece)}</b></td></tr>
        </table>
        <div style="display:flex;justify-content:flex-end;page-break-inside:avoid;break-inside:avoid">
          <table style="font-size:12px;min-width:280px">
            <tr><td style="padding:3px 10px;color:#57534e">จำนวน</td><td style="text-align:right;font-family:monospace">${qty} ชิ้น</td></tr>
            <tr><td style="padding:3px 10px;color:#57534e">รวม (${qty} ชิ้น + setup + Extras)</td><td style="text-align:right;font-family:monospace">${pcF(subtotal)}</td></tr>
            ${Number(f.overheadPct) ? `<tr><td style="padding:3px 10px;color:#57534e">โสหุ้ยโรงงาน ${f.overheadPct}%</td><td style="text-align:right;font-family:monospace">${pcF(withOverhead - subtotal)}</td></tr>` : ""}
            ${Number(f.margin) ? `<tr><td style="padding:3px 10px;color:#57534e">กำไร ${f.margin}%</td><td style="text-align:right;font-family:monospace">${pcF(grandRaw - withOverhead)}</td></tr>` : ""}
            ${grand > grandRaw ? `<tr><td style="padding:3px 10px;color:#b45309">ปรับเป็นขั้นต่ำต่องาน</td><td style="text-align:right;font-family:monospace">${pcF(f.minCharge, 0)}</td></tr>` : ""}
            <tr><td style="padding:6px 10px;font-weight:700;font-size:14px;border-top:2px solid #0c0a09">ราคารวม</td><td style="text-align:right;font-family:monospace;font-weight:700;font-size:15px;border-top:2px solid #0c0a09">฿ ${pcF(grand)}</td></tr>
            <tr><td style="padding:3px 10px;color:#57534e">เฉลี่ยต่อชิ้น</td><td style="text-align:right;font-family:monospace">฿ ${pcF(perPieceFinal)}</td></tr>
          </table>
        </div>
        ${svgHTML}
        ${photos.length ? `<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:10px">${photos.map(p => `<img src="${p.url}" style="width:${photos.length === 1 ? "100%" : "calc(50% - 4px)"};height:auto;max-height:120mm;object-fit:contain;border:1px solid #e7e5e4;border-radius:6px;background:#fafaf9;page-break-inside:avoid;break-inside:avoid"/>`).join("")}</div>` : ""}
        <div style="margin-top:8px;padding:7px 10px;background:#f7f6f4;border-radius:6px;font-size:10.5px;color:#44403c;line-height:1.7">
          ${f.priceMode === "time" && cutMin > 0 ? `⏱ เวลาเครื่องประมาณ <b>${pcF(cutMin, 1)} นาที/ชิ้น</b> (ความหนา ${T} มม. · ${pcF(speed, 1)} ม./นาที${pierceN ? ` · เจาะนำ ${pierceN} จุด` : ""}) · ` : ""}
          ${totalKg > 0 ? `⚖️ น้ำหนักรวม <b>${pcF(totalKg, 1)} kg</b> · ราคาขายเฉลี่ย <b>${pcF(pricePerKgFinal, 0)} ฿/kg</b>` : ""}
        </div>
        <div style="margin-top:6px;font-size:10px;color:#78716c">สูตร: วัสดุ = กรอบ กว้าง×ยาว×หนา×ความหนาแน่น×฿/kg + เศษเผื่อ · ค่าตัด = ${f.priceMode === "time" ? "เวลาเครื่อง (ยาวตัด÷ความเร็วตามความหนา + เจาะนำ) × ฿/ชม." : "ยาวตัดรวม × อัตราตามความหนา"} · ออกโดยระบบ SSS</div>
      </div>`;
    document.getElementById("__print_modal")?.remove();
    document.body.classList.remove("printing-modal");
    const modal = document.createElement("div");
    modal.id = "__print_modal"; modal.className = "print-modal";
    modal.innerHTML = `
      <div class="print-modal-bar no-print">
        <div style="font-weight:600;font-size:14px;flex:1">สรุปราคางาน ${(f.name || "").replace(/</g, "&lt;")}</div>
        <button id="__print_close" class="pm-btn pm-btn-ghost">ปิด (Esc)</button>
        <button id="__print_btn" class="pm-btn pm-btn-primary">🖨 พิมพ์ / บันทึก PDF</button>
      </div>
      <div class="print-modal-paper">${html}</div>`;
    document.body.appendChild(modal);
    document.body.classList.add("printing-modal");
    const cleanup = () => { modal.remove(); document.body.classList.remove("printing-modal"); document.removeEventListener("keydown", onKey); };
    const onKey = (e) => { if (e.key === "Escape") cleanup(); };
    document.addEventListener("keydown", onKey);
    modal.addEventListener("click", e => { if (e.target === modal) cleanup(); });
    modal.querySelector("#__print_btn").addEventListener("click", () => window.print());
    modal.querySelector("#__print_close").addEventListener("click", cleanup);
  };

  // ── ออกใบเสนอราคา ──
  const makeQuotation = () => {
    if (!window.openNewDoc) { window.toast && window.toast("ระบบเอกสารยังไม่พร้อม"); return; }
    const spec = `${f.name || "งานตัด/พับ"} — ${mat.name} ${pcF(W, 0)}×${pcF(L, 0)}×${T} มม.${cutLenM ? ` ตัด ${pcF(cutLenM, 2)} ม.` : ""}${bendLines.length ? ` พับ ${bendLines.length} จุด` : ""}`;
    const lines = [
      { name: spec, qty, unit: "ชิ้น", price: Math.ceil(perPiece * (1 + (Number(f.margin) || 0) / 100)) },
      ...extras.filter(x => extraSel.has(x.id)).map(x => ({ name: x.name, qty: 1, unit: "งาน", price: Math.ceil((Number(x.price) || 0) * (1 + (Number(f.margin) || 0) / 100)) })),
    ];
    window.openNewDoc("quotation", { partyName: f.customer || undefined, lines });
  };

  // ── Extras manager ──
  const addExtra = (name, price) => {
    if (!name.trim()) return;
    const next = [...pcGet(PC_EXTRAS, []), { id: "x" + Date.now().toString(36), name: name.trim(), price: Number(price) || 0 }];
    setExtras(next); pcSet(PC_EXTRAS, next);
  };
  const delExtra = (id) => {
    const next = pcGet(PC_EXTRAS, []).filter(x => x.id !== id);
    setExtras(next); pcSet(PC_EXTRAS, next);
    setExtraSel(s => { const n = new Set(s); n.delete(id); return n; });
  };

  const inBtn = { display: "inline-flex", alignItems: "center", gap: 6, padding: "7px 12px", borderRadius: 8, border: "1px solid var(--line-strong)", background: "var(--surface)", color: "var(--ink-2)", fontSize: 12.5, fontWeight: 600, cursor: "pointer", fontFamily: "inherit" };

  return (
    <>
      <PageHead title="คำนวณราคางานตัด/พับ" sub="อัพโหลด DXF วัดความยาวตัดอัตโนมัติ · วาดเส้นพับ · ค่าวัสดุจากน้ำหนัก · Extras"
        right={<div style={{display: "flex", gap: 6}}>
          <Btn size="sm" kind="ghost" onClick={() => setTab(tab === "calc" ? "history" : "calc")}>{tab === "calc" ? `📜 ประวัติ (${hist.length})` : "← กลับไปคำนวณ"}</Btn>
        </div>}/>

      {tab === "history" ? (
        <Card title="ประวัติการคำนวณ" sub={`${hist.length} รายการ · ซิงค์ทุกเครื่อง`}>
          {hist.length === 0 && <div style={{padding: 18, textAlign: "center", color: "var(--ink-3)", fontSize: 12.5}}>ยังไม่มีประวัติ — คำนวณแล้วกด "บันทึกประวัติ"</div>}
          <div style={{display: "flex", flexDirection: "column", gap: 8}}>
            {hist.map(r => (
              <div key={r.id} style={{border: "1px solid var(--line)", borderRadius: 10, padding: "10px 12px", display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap"}}>
                {(r.photos && r.photos[0]) && <img src={r.photos[0].url} alt="" style={{width: 52, height: 52, borderRadius: 8, objectFit: "cover", border: "1px solid var(--line)"}}/>}
                <div style={{flex: 1, minWidth: 200}}>
                  <b style={{fontSize: 13.5}}>{r.name}</b> {r.customer && <span style={{fontSize: 11.5, color: "var(--ink-3)"}}>· {r.customer}</span>}
                  <div style={{fontSize: 11, color: "var(--ink-3)", marginTop: 2}}>
                    {r.date} · {r.results ? `${r.results.qty} ชิ้น · เฉลี่ย ฿${pcF(r.results.perPiece)}/ชิ้น` : ""}{r.by ? ` · โดย ${r.by}` : ""}
                  </div>
                </div>
                <b style={{fontSize: 15, color: "var(--success)"}}>฿ {pcF(r.results && r.results.grand)}</b>
                <div style={{display: "flex", gap: 6}}>
                  <button style={inBtn} onClick={() => loadHist(r)}><I.edit size={14}/> เปิดแก้</button>
                  <button style={{...inBtn, color: "var(--danger)"}} onClick={() => delHist(r)}><I.trash size={14}/></button>
                </div>
              </div>
            ))}
          </div>
        </Card>
      ) : (
      <div className="grid" style={{gridTemplateColumns: "minmax(380px, 1.2fr) minmax(340px, 1fr)", gap: 14, alignItems: "start"}}>
        {/* ── ซ้าย: แบบ + วาด ── */}
        <Card title="แบบชิ้นงาน · DXF / วาดเส้นพับ" sub={dxfName ? `ไฟล์: ${dxfName}` : "อัพโหลด DXF หรือวาดบนกระดานเปล่า (หน่วย มม.)"}
          right={<div style={{display: "flex", gap: 6}}>
            <label className="btn sm" style={{cursor: "pointer"}}>
              <input type="file" accept=".dxf" style={{display: "none"}} onChange={onDXF}></input>
              📐 อัพโหลด DXF
            </label>
            <Btn size="sm" kind={drawMode ? "primary" : "ghost"} onClick={() => { setDrawMode(m => !m); setPending(null); }}>{drawMode ? "✏️ กำลังวาด (คลิก 2 จุด)" : "✏️ วาดเส้นพับ"}</Btn>
          </div>}>
          <div style={{position: "relative"}}>
          <svg ref={svgRef} viewBox={`${vbe.x} ${vbe.y} ${vbe.w} ${vbe.h}`}
            onClick={onSvgClick} onMouseMove={onSvgMove} onMouseDown={onSvgDown} onMouseUp={onSvgUp} onMouseLeave={onSvgUp}
            preserveAspectRatio="xMidYMid meet" style={{width: "100%", height: "clamp(460px, calc(100vh - 320px), 860px)", background: "var(--surface-2)", borderRadius: 10, border: "1px solid var(--line)", cursor: drawMode ? "crosshair" : (panRef.current ? "grabbing" : "grab"), display: "block"}}>
            {/* กริดทุก 50 มม. */}
            {Array.from({ length: Math.floor(vb.w / 50) + 1 }, (_, k) => Math.ceil(vb.x / 50) * 50 + k * 50).map(gx => gx <= vb.x + vb.w &&
              <line key={"gx" + gx} data-grid="1" x1={gx} y1={vb.y} x2={gx} y2={vb.y + vb.h} stroke="var(--line)" strokeWidth={vbe.w / 900}/>)}
            {Array.from({ length: Math.floor(vb.h / 50) + 1 }, (_, k) => Math.ceil(vb.y / 50) * 50 + k * 50).map(gy => gy <= vb.y + vb.h &&
              <line key={"gy" + gy} data-grid="1" x1={vb.x} y1={gy} x2={vb.x + vb.w} y2={gy} stroke="var(--line)" strokeWidth={vbe.w / 900}/>)}
            {/* กรอบรายพาร์ท (สีแดง) — พื้นที่วัสดุที่คิดเงินจริง */}
            {dxf && f.framePart !== false && (dxf.parts || []).length > 1 && dxf.parts.map((p, k) => (
              <rect key={"pf" + k} x={p.minX} y={Y(p.maxY)} width={p.maxX - p.minX} height={p.maxY - p.minY}
                fill="none" stroke="#ef4444" strokeWidth={Math.max(0.5, vbe.w / 600)} strokeDasharray={`${vbe.w / 120} ${vbe.w / 200}`} opacity={0.85}/>
            ))}
            {/* เส้นตัดจาก DXF */}
            {dxf && dxf.polylines.map((pl, k) => (
              <polyline key={k} points={pl.map(p => `${p.x},${Y(p.y)}`).join(" ")} fill="none" stroke="var(--info)" strokeWidth={Math.max(0.4, vbe.w / 500)}/>
            ))}
            {/* เส้นที่วาด (พับ = ส้มเส้นประ) */}
            {drawn.map(d => (
              <g key={d.id}>
                <line x1={d.x1} y1={Y(d.y1)} x2={d.x2} y2={Y(d.y2)}
                  stroke={d.bend ? "var(--warning)" : "var(--ink-3)"} strokeWidth={Math.max(0.8, vbe.w / 300)} strokeDasharray={d.bend ? `${vbe.w / 60} ${vbe.w / 120}` : ""}/>
                <text x={(d.x1 + d.x2) / 2} y={Y((d.y1 + d.y2) / 2) - vbe.w / 100} fontSize={Math.max(7, vbe.w / 70)} fill={d.bend ? "var(--warning)" : "var(--ink-3)"} textAnchor="middle"
                  fontFamily="Arial, sans-serif" stroke="var(--surface-2)" strokeWidth={vbe.w / 400} style={{paintOrder: "stroke"}}>
                  {pcF(Math.hypot(d.x2 - d.x1, d.y2 - d.y1), 0)} mm
                </text>
              </g>
            ))}
            {pending && hover && <line x1={pending.x} y1={Y(pending.y)} x2={hover.x} y2={Y(hover.y)} stroke="var(--warning)" strokeWidth={Math.max(0.8, vbe.w / 300)} strokeDasharray={`${vbe.w / 100} ${vbe.w / 150}`} opacity={0.6}/>}
            {pending && <circle cx={pending.x} cy={Y(pending.y)} r={Math.max(2, vbe.w / 150)} fill="var(--warning)"/>}
          </svg>
          <div style={{position: "absolute", top: 8, right: 8, display: "flex", flexDirection: "column", gap: 4}}>
            {[["＋", () => zoomAt(1 / 1.3)], ["－", () => zoomAt(1.3)], ["⤢", () => setZoomV(null)]].map(([label, fn], i) => (
              <button key={i} onClick={fn} title={i === 2 ? "พอดีแบบ" : i === 0 ? "ซูมเข้า" : "ซูมออก"}
                style={{width: 30, height: 30, borderRadius: 7, border: "1px solid var(--line-strong)", background: "var(--surface)", color: "var(--ink-1)", fontSize: 15, cursor: "pointer", boxShadow: "var(--shadow-sm)"}}>{label}</button>
            ))}
          </div>
          </div>
          <div style={{display: "flex", gap: 10, alignItems: "center", marginTop: 8, flexWrap: "wrap", fontSize: 11.5, color: "var(--ink-3)"}}>
            <label style={{display: "inline-flex", gap: 5, alignItems: "center", cursor: "pointer"}}>
              <input type="checkbox" checked={snapOrtho} onChange={e => setSnapOrtho(e.target.checked)}/> ล็อกแนวตรง (แนวนอน/ตั้ง)
            </label>
            <span>เส้นวาด {drawn.length} เส้น · เส้นพับ {bendLines.length} จุด</span>
            {drawn.length > 0 && <Btn size="sm" kind="ghost" onClick={() => setDrawn([])}>ล้างเส้นวาด</Btn>}
          </div>
          {drawn.length > 0 && (
            <div style={{display: "flex", flexDirection: "column", gap: 4, marginTop: 8}}>
              {drawn.map((d, k) => (
                <div key={d.id} style={{display: "flex", gap: 8, alignItems: "center", fontSize: 12, padding: "4px 8px", background: "var(--surface-2)", borderRadius: 7}}>
                  <span style={{color: d.bend ? "var(--warning)" : "var(--ink-3)", fontWeight: 700}}>เส้น {k + 1}</span>
                  <span className="mono">{pcF(Math.hypot(d.x2 - d.x1, d.y2 - d.y1), 0)} มม.</span>
                  <label style={{display: "inline-flex", gap: 4, alignItems: "center", cursor: "pointer", marginLeft: "auto"}}>
                    <input type="checkbox" checked={!!d.bend} onChange={() => setDrawn(list => list.map(x => x.id === d.id ? { ...x, bend: !x.bend } : x))}/>
                    <span style={{fontSize: 11}}>เส้นพับ</span>
                  </label>
                  <button className="icon-btn" onClick={() => setDrawn(list => list.filter(x => x.id !== d.id))}><I.trash size={12}/></button>
                </div>
              ))}
            </div>
          )}
        </Card>

        {/* ── ขวา: ตัวเลข + ราคา ── */}
        <div style={{display: "flex", flexDirection: "column", gap: 14}}>
          <Card title="ข้อมูลงาน">
            <div style={{display: "flex", flexDirection: "column", gap: 10}}>
              {/* ═══ โซนกรอกประจำ (ทุกงาน) — ช่องใหญ่ เน้นชัด ═══ */}
              <div style={{border: "1.5px solid var(--accent)", borderRadius: 12, padding: "12px 14px", background: "color-mix(in oklab, var(--accent) 5%, var(--surface))", display: "flex", flexDirection: "column", gap: 10}}>
                <div className="field-row cols-2">
                  <div className="field"><label style={{fontSize: 13, fontWeight: 700}}>ชื่องาน</label>
                    <input className="input" style={{height: 44, fontSize: 16, fontWeight: 600}} value={f.name} onChange={e => upd("name", e.target.value)} placeholder="เช่น ถาดสแตนเลสพับขอบ"/></div>
                  <div className="field"><label style={{fontSize: 13, fontWeight: 700}}>ลูกค้า</label>
                    <input className="input" style={{height: 44, fontSize: 16, fontWeight: 600}} list="pc-customers" value={f.customer} onChange={e => upd("customer", e.target.value)} placeholder="พิมพ์/เลือก"/>
                    <datalist id="pc-customers">{(D.Customers || []).map(c => <option key={c.id} value={c.name}/>)}</datalist>
                  </div>
                </div>
                <div className="field-row" style={{display: "grid", gridTemplateColumns: "1fr 1.6fr 1fr", gap: 10}}>
                  <div className="field"><label style={{fontSize: 13, fontWeight: 700, color: "var(--accent)"}}>ความหนา (มม.) ★</label>
                    <input className="input" type="number" step="0.1" style={{height: 48, fontSize: 20, fontWeight: 800, textAlign: "center", borderWidth: 2, borderColor: "var(--accent)"}} value={f.T} onChange={e => upd("T", e.target.value)}/></div>
                  <div className="field"><label style={{fontSize: 13, fontWeight: 700}}>วัสดุ</label>
                    <select className="input" style={{height: 48, fontSize: 15.5, fontWeight: 700}} value={f.mat} onChange={e => { const mi = Number(e.target.value); setF(p => ({ ...p, mat: mi, pricePerKg: (p.matPrices && Number(p.matPrices[mi]) > 0) ? Number(p.matPrices[mi]) : (PC_MATS[mi] || {}).price || 60 })); }}>
                      {PC_MATS.map((m, k) => <option key={k} value={k}>{m.name} · {m.density}</option>)}
                    </select></div>
                  <div className="field"><label style={{fontSize: 13, fontWeight: 700}}>จำนวน (ชิ้น)</label>
                    <input className="input" type="number" min="1" style={{height: 48, fontSize: 20, fontWeight: 800, textAlign: "center"}} value={f.qty} onChange={e => upd("qty", e.target.value)}/></div>
                </div>
              </div>

              {/* ═══ ห้องค่าอัตโนมัติ — ระบบอ่านจากแบบให้แล้ว ผู้ใช้แค่เช็ค/แก้ ═══ */}
              <div style={{border: "1px dashed var(--info)", borderRadius: 12, overflow: "hidden"}}>
                <div style={{padding: "8px 14px", background: "color-mix(in oklab, var(--info) 10%, var(--surface))", display: "flex", alignItems: "center", gap: 8}}>
                  <span style={{fontSize: 15}}>🤖</span>
                  <b style={{fontSize: 12.5, color: "var(--info)"}}>ระบบคำนวณจากแบบให้แล้ว — ตรวจสอบ/แก้ตัวเลขได้เลย</b>
                  {dxf && <span style={{marginLeft: "auto", fontSize: 10.5, color: "var(--ink-4)"}}>อ่านจาก {dxfName || "DXF"}</span>}
                </div>
                <div style={{padding: "10px 14px", display: "flex", flexDirection: "column", gap: 10, background: "var(--surface-2)"}}>
                  <div style={{display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(118px, 1fr))", gap: 8}}>
                    {[["กว้าง (มม.)", "W"], ["ยาว (มม.)", "L"], ["ยาวตัดรวม (ม.)", "cutLenM"], ["จุดเจาะนำ (จุด)", "pierceN"], ["จำนวนพาร์ท (ชิ้น)", "partsN"]].map(([lb, key]) => (
                      <div key={key} style={{background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 9, padding: "7px 9px"}}>
                        <div style={{fontSize: 10, color: "var(--ink-4)", fontWeight: 700, display: "flex", justifyContent: "space-between"}}>
                          <span>{lb}</span>{dxf && <span style={{color: "var(--info)"}}>DXF</span>}
                        </div>
                        <input className="input" type="number" step="0.01" value={f[key]} onChange={e => upd(key, e.target.value)}
                          style={{border: 0, background: "transparent", padding: "2px 0 0", height: 28, fontSize: 15.5, fontWeight: 700, width: "100%"}}/>
                      </div>
                    ))}
                  </div>
                  {dxf && (dxf.parts || []).length > 1 && (
                    <label style={{display: "flex", alignItems: "center", gap: 8, fontSize: 12.5, cursor: "pointer", padding: "8px 11px", borderRadius: 9, background: f.framePart !== false ? "color-mix(in oklab, #ef4444 8%, var(--surface))" : "var(--surface)", border: f.framePart !== false ? "1.5px solid #ef4444" : "1px solid var(--line)"}}>
                      <input type="checkbox" checked={f.framePart !== false} onChange={e => upd("framePart", e.target.checked)}/>
                      <span>🟥 วัสดุตาม<b>กรอบรายพาร์ท</b> — {dxf.parts.length} พาร์ท รวม <b>{pcF(dxf.partsArea / 1e6, 3)} ตร.ม.</b>
                        <span style={{color: "var(--ink-4)", fontSize: 11}}> (ไม่คิดพื้นที่ว่าง · ปิด = กรอบทั้งไฟล์ {pcF(W, 0)}×{pcF(L, 0)})</span></span>
                    </label>
                  )}
                  <label style={{display: "flex", alignItems: "center", gap: 8, fontSize: 12.5, cursor: "pointer", padding: "8px 11px", borderRadius: 9, background: f.bendAuto ? "color-mix(in oklab, var(--warning) 9%, var(--surface))" : "var(--surface)", border: f.bendAuto ? "1.5px solid var(--warning)" : "1px solid var(--line)"}}>
                    <input type="checkbox" checked={!!f.bendAuto} onChange={e => upd("bendAuto", e.target.checked)}/>
                    <span>📐 ค่าพับอัตโนมัติจากเส้นพับที่วาด — {bendLines.length} จุด{bendLines.length ? ` = ฿${pcF(bendCost, 0)}` : ""}
                      <span style={{color: "var(--ink-4)", fontSize: 11}}> (หนา {T} มม. ความยาก ×{pcF(bendDiff, 1)} · คิดตามความยาวเส้น)</span></span>
                  </label>
                  <div style={{display: "flex", gap: 8, flexWrap: "wrap", fontSize: 11.5}}>
                    <span style={{background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 999, padding: "4px 11px"}}>⚡ ความเร็วตัด <b>{pcF(speed, 2)}</b> ม./นาที <span style={{color: "var(--ink-4)"}}>({f.speedOv === "" ? `ตาราง ${f.laserW}W ×35%` : "กำหนดเอง"})</span></span>
                    <span style={{background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 999, padding: "4px 11px"}}>⏱ เวลาเครื่อง <b>{pcF(cutMin, 1)}</b> นาที/ชิ้น</span>
                    <span style={{background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 999, padding: "4px 11px"}}>🏗 ~<b>{sheetsEst}</b> แผ่น · <b>{totalParts}</b> พาร์ท</span>
                  </div>
                </div>
              </div>
              {!f.bendAuto && (
                <div className="field-row cols-2">
                  <div className="field"><label>ค่าพับ/จุด (ว่าง = ฐาน {f.bendBase}×หนา = ฿{pcF((Number(f.bendBase) || 0) * T)})</label><input className="input" type="number" value={f.bendPerHit} onChange={e => upd("bendPerHit", e.target.value)} placeholder={"อัตโนมัติ ฿" + pcF((Number(f.bendBase) || 0) * T)}/></div>
                  <div className="field"><label>ค่าพับ/เมตร (ถ้ามี)</label><input className="input" type="number" value={f.bendPerM} onChange={e => upd("bendPerM", e.target.value)}/></div>
                </div>
              )}

              {/* ═══ เรต & ตั้งค่า — นาน ๆ ปรับที (พับเก็บ) ═══ */}
              <button type="button" onClick={() => setShowRates(v => !v)}
                style={{display: "flex", alignItems: "center", gap: 8, border: "1px dashed var(--line-strong)", borderRadius: 10, padding: "9px 13px", background: "var(--surface-2)", color: "var(--ink-2)", fontSize: 13, fontWeight: 700, cursor: "pointer", fontFamily: "inherit", width: "100%"}}>
                ⚙️ เรตราคา & ตั้งค่าเครื่อง <span style={{fontWeight: 400, color: "var(--ink-4)", fontSize: 11.5}}>(ตั้งครั้งเดียวใช้ทุกงาน — {mat.name} ฿{pcF(f.pricePerKg, 0)}/kg · เครื่อง ฿{pcF(f.machineRate, 0)}/ชม. · กำไร {f.margin}%)</span>
                <span style={{marginLeft: "auto"}}>{showRates ? "▲" : "▼"}</span>
              </button>
              {showRates && (
                <div style={{display: "flex", flexDirection: "column", gap: 10, border: "1px solid var(--line)", borderRadius: 10, padding: "12px 14px"}}>
                  <div className="field"><label>วิธีคิดค่าตัด</label>
                    <div style={{display: "flex", border: "1px solid var(--line-strong)", borderRadius: 8, overflow: "hidden", width: "fit-content"}}>
                      {[["time", "⏱ ตามเวลาเครื่อง (แนะนำ)"], ["meter", "📏 ตามเมตร×ความหนา"]].map(([v, label]) => (
                        <button key={v} type="button" onClick={() => upd("priceMode", v)}
                          style={{border: 0, padding: "6px 13px", fontSize: 12, fontWeight: 600, cursor: "pointer", background: f.priceMode === v ? "var(--ink-1)" : "var(--surface)", color: f.priceMode === v ? "var(--surface)" : "var(--ink-2)", fontFamily: "inherit"}}>{label}</button>
                      ))}
                    </div>
                  </div>
                  <div className="field">
                    <label>ราคาวัสดุ (฿/kg) — แยกรายวัสดุ · เปลี่ยนวัสดุแล้วดึงราคาช่องนั้นอัตโนมัติ</label>
                    <div style={{display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(108px, 1fr))", gap: 6}}>
                      {PC_MATS.map((m, i) => (
                        <div key={i}>
                          <div style={{fontSize: 10.5, marginBottom: 3, fontWeight: i === f.mat ? 700 : 500, color: i === f.mat ? "var(--accent)" : "var(--ink-3)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis"}} title={m.name}>{i === f.mat ? "▶ " : ""}{m.name}</div>
                          <input className="input" type="number" value={(f.matPrices && f.matPrices[i] != null) ? f.matPrices[i] : m.price}
                            style={i === f.mat ? {borderColor: "var(--accent)", boxShadow: "0 0 0 1px var(--accent)"} : {}}
                            onChange={e => { const v = e.target.value; setF(p => ({ ...p, matPrices: { ...(p.matPrices || {}), [i]: v }, ...(i === p.mat ? { pricePerKg: v } : {}) })); }}/>
                        </div>
                      ))}
                    </div>
                  </div>
                  <div className="field-row cols-2">
                    {f.priceMode === "meter"
                      ? <div className="field"><label>อัตราค่าตัด (฿/ม./มม.หนา)</label><input className="input" type="number" value={f.cutBase} onChange={e => upd("cutBase", e.target.value)}/></div>
                      : <div className="field"><label>เรตเครื่อง (฿/ชั่วโมง รวมกำไรเครื่อง)</label><input className="input" type="number" value={f.machineRate} onChange={e => upd("machineRate", e.target.value)}/></div>}
                  </div>
                  {f.priceMode === "time" && (
                    <div className="field-row cols-2">
                      <div className="field"><label>กำลังเลเซอร์</label>
                        <select className="input" value={f.laserW} onChange={e => upd("laserW", Number(e.target.value))}>
                          {[1000, 1500, 2000, 3000, 4000, 6000].map(w => <option key={w} value={w}>{w.toLocaleString()} W</option>)}
                        </select>
                      </div>
                      <div className="field"><label>ความเร็วตัด (ม./นาที)</label>
                        <div style={{display: "flex", gap: 8, alignItems: "center"}}>
                          <input className="input" type="number" step="0.1" value={f.speedOv} onChange={e => upd("speedOv", e.target.value)} placeholder={pcF(speedAuto, 2) + " (ตาราง ×35%)"} style={{maxWidth: 130}}/>
                          <Btn size="sm" kind={f.speedOv === "" ? "primary" : "ghost"} onClick={() => upd("speedOv", "")}>⚡ อัตโนมัติ</Btn>
                        </div>
                      </div>
                    </div>
                  )}
                  {f.priceMode === "time" && <div style={{fontSize: 11.5, color: "var(--ink-3)"}}>{mat.name} หนา {T} มม. → <b>{pcF(speed, 2)}</b> ม./นาที</div>}
                  <div className="field-row cols-2">
                    <div className="field"><label>ค่าโหลดแผ่นขึ้น-ลง (฿/แผ่น · 0 = ไม่คิด)</label><input className="input" type="number" value={f.setupBase} onChange={e => upd("setupBase", e.target.value)}/></div>
                    <div className="field"><label>ค่าเก็บ/คัดแยกชิ้น (฿/พาร์ท)</label><input className="input" type="number" value={f.partHandle} onChange={e => upd("partHandle", e.target.value)}/></div>
                  </div>
                  <div className="field-row" style={{display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 8}}>
                    <div className="field"><label>เศษวัสดุเผื่อ (%)</label><input className="input" type="number" value={f.scrapPct} onChange={e => upd("scrapPct", e.target.value)}/></div>
                    <div className="field"><label>โสหุ้ย (%)</label><input className="input" type="number" value={f.overheadPct} onChange={e => upd("overheadPct", e.target.value)}/></div>
                    <div className="field"><label>กำไร (%)</label><input className="input" type="number" value={f.margin} onChange={e => upd("margin", e.target.value)}/></div>
                  </div>
                  <div style={{display: "flex", gap: 8, justifyContent: "flex-end", borderTop: "1px solid var(--line)", paddingTop: 10}}>
                    <Btn size="sm" kind="ghost" onClick={resetRates}>↺ คืนค่าเริ่มต้น</Btn>
                    <Btn size="sm" kind="primary" onClick={saveRates}>💾 บันทึกการตั้งค่า (ใช้ทุกงาน)</Btn>
                  </div>
                </div>
              )}
            </div>
          </Card>

          <Card title="Extras · ค่าใช้จ่ายเพิ่มเติม (ต่อรอบงาน)" sub="ติ๊กรายการที่ใช้ · จัดการรายการเองได้"
            right={<Btn size="sm" kind="ghost" onClick={() => setShowExtras(s => !s)}>{showExtras ? "ปิดจัดการ" : "จัดการรายการ"}</Btn>}>
            {extras.length === 0 && !showExtras && <div style={{fontSize: 12, color: "var(--ink-4)"}}>ยังไม่มีรายการ — กด "จัดการรายการ" เพื่อเพิ่ม เช่น ค่าตั้งเครื่อง / ค่าเขียนแบบ / ค่าต๊าบเกลียว</div>}
            <div style={{display: "flex", flexDirection: "column", gap: 6}}>
              {extras.map(x => (
                <label key={x.id} style={{display: "flex", gap: 8, alignItems: "center", cursor: "pointer", fontSize: 13, padding: "5px 8px", borderRadius: 7, background: extraSel.has(x.id) ? "var(--surface-2)" : "transparent"}}>
                  <input type="checkbox" style={{width: 16, height: 16}} checked={extraSel.has(x.id)} onChange={() => setExtraSel(s => { const n = new Set(s); n.has(x.id) ? n.delete(x.id) : n.add(x.id); return n; })}/>
                  <span style={{flex: 1}}>{x.name}</span>
                  <b className="mono">฿{pcF(x.price)}</b>
                  {showExtras && <button className="icon-btn" onClick={(e) => { e.preventDefault(); delExtra(x.id); }}><I.trash size={12}/></button>}
                </label>
              ))}
            </div>
            {showExtras && <PCExtraAdd onAdd={addExtra}/>}
          </Card>

          <Card title="สรุปราคา">
            <div style={{display: "flex", flexDirection: "column", gap: 5, fontSize: 12.5}}>
              {breakdown.map((b, k) => (
                <div key={k} style={{display: "flex", justifyContent: "space-between", gap: 10}}>
                  <span style={{color: "var(--ink-2)", fontSize: 13, lineHeight: 1.6}}>{pcHL(b.label)}{b.per ? "" : " (ต่อรอบ)"}</span>
                  <b className="mono" style={{whiteSpace: "nowrap"}}>฿{pcF(b.amt)}</b>
                </div>
              ))}
              <div style={{borderTop: "1px solid var(--line)", paddingTop: 6, display: "flex", justifyContent: "space-between"}}><span>ต้นทุนต่อชิ้น (วัสดุ+ตัด+พับ)</span><b className="mono">฿{pcF(perPiece)}</b></div>
              <div style={{display: "flex", justifyContent: "space-between"}}><span>× {qty} ชิ้น + setup + Extras</span><b className="mono">฿{pcF(subtotal)}</b></div>
              {Number(f.overheadPct) > 0 && <div style={{display: "flex", justifyContent: "space-between"}}><span>โสหุ้ยโรงงาน {f.overheadPct}%</span><b className="mono">฿{pcF(withOverhead - subtotal)}</b></div>}
              {Number(f.margin) > 0 && <div style={{display: "flex", justifyContent: "space-between"}}><span>กำไร {f.margin}%</span><b className="mono">฿{pcF(grandRaw - withOverhead)}</b></div>}
              <label style={{display: "flex", alignItems: "center", gap: 8, cursor: "pointer", padding: "7px 10px", borderRadius: 8, border: f.minChargeOn ? "1.5px solid var(--warning)" : "1px dashed var(--line-strong)", background: f.minChargeOn ? "color-mix(in oklab, var(--warning) 8%, transparent)" : "transparent", fontSize: 12.5}}>
                <input type="checkbox" checked={!!f.minChargeOn} onChange={e => upd("minChargeOn", e.target.checked)}/>
                <span style={{fontWeight: 600}}>ใช้ราคาขั้นต่ำต่องาน</span>
                <input className="input" type="number" value={f.minCharge} onChange={e => upd("minCharge", e.target.value)} onClick={e => e.preventDefault()}
                  disabled={!f.minChargeOn} style={{width: 90, height: 28, fontSize: 13, textAlign: "right", marginLeft: "auto", opacity: f.minChargeOn ? 1 : 0.45}}/>
                <span style={{color: "var(--ink-3)"}}>บาท</span>
              </label>
              {grand > grandRaw && <div style={{display: "flex", justifyContent: "space-between", color: "var(--warning)"}}><span>ปรับเป็นขั้นต่ำต่องาน</span><b className="mono">฿{pcF(f.minCharge, 0)}</b></div>}
              <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", background: "var(--ink-1)", color: "var(--surface)", borderRadius: 8, padding: "10px 12px", marginTop: 4}}>
                <span style={{fontWeight: 600}}>ราคารวมทั้งงาน</span>
                <b style={{fontSize: 18, fontVariantNumeric: "tabular-nums"}}>฿ {pcF(grand)}</b>
              </div>
              <div style={{textAlign: "right", fontSize: 11.5, color: "var(--ink-3)"}}>เฉลี่ย ฿{pcF(perPieceFinal)} / ชิ้น</div>
              {/* แผงอธิบาย — ให้เข้าใจว่าราคามาจากไหน + เช็คกับตลาด */}
              <div style={{marginTop: 6, padding: "9px 12px", background: "var(--surface-2)", borderRadius: 8, fontSize: 11.5, lineHeight: 1.8, color: "var(--ink-2)"}}>
                {f.priceMode === "time" && cutMin > 0 && <div>⏱ เวลาเครื่องประมาณ <b>{pcF(cutMin, 1)} นาที/ชิ้น</b> ({pcF(cutMin * qty, 0)} นาทีทั้งงาน) — ความหนา {T} มม. วิ่ง {pcF(speed, 1)} ม./นาที{pierceN ? ` · เจาะนำ ${pierceN} จุด` : ""}</div>}
                {setupCost > 0 && <div>🏗 setup ขึ้น-ลงชิ้นงาน ~{sheetsEst} แผ่น · {totalParts} พาร์ท = <b>฿{pcF(setupCost, 0)}</b> (ต่อรอบงาน)</div>}
                {totalKg > 0 && <div>⚖️ น้ำหนักรวม <b>{pcF(totalKg, 1)} kg</b> → ราคาขายเฉลี่ย <b>{pcF(pricePerKgFinal, 0)} ฿/kg</b> (วัสดุทุน {pcF(f.pricePerKg, 0)} ฿/kg)</div>}
                {kgVerdict && <div style={{color: kgVerdict.tone, fontWeight: 600}}>◉ {kgVerdict.msg}</div>}
              </div>
            </div>
            {/* รูปแนบ */}
            <div style={{display: "flex", gap: 6, flexWrap: "wrap", marginTop: 10, alignItems: "center"}}>
              {photos.map((p, k) => (
                <div key={k} style={{position: "relative"}}>
                  <img src={p.url} alt="" style={{width: 54, height: 54, borderRadius: 7, objectFit: "cover", border: "1px solid var(--line)"}}/>
                  <button onClick={() => setPhotos(ph => ph.filter((_, j) => j !== k))} style={{position: "absolute", top: -5, right: -5, width: 17, height: 17, borderRadius: "50%", border: 0, background: "var(--danger)", color: "white", fontSize: 10, cursor: "pointer"}}>×</button>
                </div>
              ))}
              <label className="btn sm" style={{cursor: "pointer"}}>
                <input type="file" accept="image/*" multiple style={{display: "none"}} onChange={onPhotos}></input>
                📷 แนบรูป
              </label>
            </div>
            <div style={{display: "flex", gap: 8, flexWrap: "wrap", marginTop: 12}}>
              <button style={{...inBtn, background: "var(--ink-1)", color: "var(--surface)", borderColor: "var(--ink-1)"}} onClick={printSummary}><I.print size={15}/> พิมพ์สรุปราคา</button>
              <button style={inBtn} onClick={makeQuotation}><I.doc size={15}/> ออกใบเสนอราคา</button>
              <button style={inBtn} onClick={saveHist}><I.check size={15}/> บันทึกประวัติ</button>
            </div>
          </Card>
        </div>
      </div>
      )}
    </>
  );
}

function PCExtraAdd({ onAdd }) {
  const [name, setName] = useStatePC("");
  const [price, setPrice] = useStatePC("");
  return (
    <div style={{display: "flex", gap: 6, marginTop: 8}}>
      <input className="input" placeholder="เช่น ค่าตั้งเครื่อง / ค่าเขียนแบบ / ค่าต๊าบเกลียว" value={name} onChange={e => setName(e.target.value)} style={{flex: 2}}/>
      <input className="input" type="number" placeholder="ราคา ฿" value={price} onChange={e => setPrice(e.target.value)} style={{flex: 1}}/>
      <window.UI.Btn size="sm" kind="primary" onClick={() => { onAdd(name, price); setName(""); setPrice(""); }}>เพิ่ม</window.UI.Btn>
    </div>
  );
}

window.PriceCalcPage = PriceCalcPage;
