// ===== Teuanjai Radio — Overview Dashboard (ภาพรวมการสตรีม) =====
// อ่านข้อมูลจริงจากฐานข้อมูลการเข้าร่วม (js/db.jsx) — แสดงผลแบบเรียลไทม์
// เลือกวันที่ย้อนหลังได้ + Export Database (Excel 2 ชีท)

// Catmull-Rom -> Bezier smooth path
function smoothPath(pts) {
  if (pts.length < 2) return "";
  let d = `M ${pts[0].x.toFixed(2)} ${pts[0].y.toFixed(2)}`;
  for (let i = 0; i < pts.length - 1; i++) {
    const p0 = pts[i - 1] || pts[i];
    const p1 = pts[i];
    const p2 = pts[i + 1];
    const p3 = pts[i + 2] || p2;
    const c1x = p1.x + (p2.x - p0.x) / 6;
    const c1y = p1.y + (p2.y - p0.y) / 6;
    const c2x = p2.x - (p3.x - p1.x) / 6;
    const c2y = p2.y - (p3.y - p1.y) / 6;
    d += ` C ${c1x.toFixed(2)} ${c1y.toFixed(2)}, ${c2x.toFixed(2)} ${c2y.toFixed(2)}, ${p2.x.toFixed(2)} ${p2.y.toFixed(2)}`;
  }
  return d;
}

function KpiCard({ icon, label, value, sub, grad, glow, valueSize }) {
  return (
    <div className="glass" style={{ borderRadius: 20, padding: "15px 15px", position: "relative", overflow: "hidden" }}>
      <div style={{ position: "absolute", top: -22, right: -22, width: 76, height: 76, borderRadius: "50%",
        background: glow, filter: "blur(8px)", opacity: 0.5 }} />
      <div style={{ width: 38, height: 38, borderRadius: 12, display: "grid", placeItems: "center", color: "#fff",
        background: grad, boxShadow: "inset 0 1px 1px rgba(255,255,255,.5)", marginBottom: 12, position: "relative" }}>
        <Icon name={icon} size={20} />
      </div>
      <div style={{ fontFamily: "var(--font-disp)", fontSize: valueSize || 27, fontWeight: 700, lineHeight: 1,
        color: "var(--ink)", fontVariantNumeric: "tabular-nums", letterSpacing: "-0.01em" }}>{value}</div>
      <div style={{ fontSize: 12.5, fontWeight: 600, color: "var(--ink-soft)", marginTop: 6 }}>{label}</div>
      {sub && <div style={{ fontSize: 11, color: "var(--ink-faint)", marginTop: 2 }}>{sub}</div>}
    </div>
  );
}

function LineChart({ id, title, subtitle, allLabels, allValues, liveEnd, winLen, accent, area1, area2, badge, badgeTone, isLiveDate }) {
  const [activeIdx, setActiveIdx] = useState(null);
  const [winEnd, setWinEnd] = useState(liveEnd);
  // ติดตามเวลาจริง: เมื่อชั่วโมงปัจจุบันขยับ ให้ snap กลับมาที่ "สด"
  useEffect(() => { setWinEnd(liveEnd); }, [liveEnd]);

  const wStart = Math.max(0, winEnd - winLen + 1);
  const labels = allLabels.slice(wStart, winEnd + 1);
  const values = allValues.slice(wStart, winEnd + 1);
  const isLive = winEnd >= liveEnd;
  const canPrev = winEnd > winLen - 1;
  const canNext = winEnd < liveEnd;

  const W = 320, H = 168, padL = 6, padR = 10, padT = 16, padB = 26;
  const innerW = W - padL - padR, innerH = H - padT - padB;
  const maxV = Math.max.apply(null, values.concat([1]));
  const niceMax = maxV * 1.18;
  const pts = values.map((v, i) => ({
    x: padL + innerW * (i / (values.length - 1)),
    y: padT + innerH * (1 - v / niceMax),
  }));
  const line = smoothPath(pts);
  const baseY = padT + innerH;
  const areaD = line + ` L ${pts[pts.length-1].x.toFixed(2)} ${baseY} L ${pts[0].x.toFixed(2)} ${baseY} Z`;
  const last = pts[pts.length - 1];
  const gridYs = [0, 0.33, 0.66, 1].map((f) => padT + innerH * f);

  const nearestIdx = (clientX, svgEl) => {
    const rect = svgEl.getBoundingClientRect();
    const relX = (clientX - rect.left) / rect.width * W;
    let best = 0, bestD = Infinity;
    pts.forEach((p, i) => { const d = Math.abs(p.x - relX); if (d < bestD) { bestD = d; best = i; } });
    return best;
  };
  const handlePointer = (e) => {
    e.preventDefault();
    const cx = e.touches ? e.touches[0].clientX : e.clientX;
    setActiveIdx(nearestIdx(cx, e.currentTarget));
  };
  const handleLeave = () => setActiveIdx(null);

  return (
    <div className="glass" style={{ borderRadius: 22, padding: "16px 16px 12px" }}>
      <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 10, marginBottom: 8 }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <span style={{ width: 9, height: 9, borderRadius: "50%", flex: "0 0 auto", background: accent, boxShadow: `0 0 8px ${accent}` }} />
            <span style={{ fontSize: 14, fontWeight: 700, color: "var(--ink)", lineHeight: 1.25 }}>{title}</span>
          </div>
          <div style={{ fontSize: 11.5, color: "var(--ink-faint)", marginTop: 4, marginLeft: 17 }}>{subtitle}</div>
        </div>
        {badge && (
          <span style={{ flex: "0 0 auto", fontSize: 11, fontWeight: 700, padding: "4px 9px", borderRadius: 999,
            color: badgeTone || accent, background: "oklch(1 0 0 / 0.6)", border: "1px solid var(--glass-line)", whiteSpace: "nowrap" }}>{badge}</span>
        )}
      </div>

      <svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ display: "block", overflow: "visible", touchAction: "none" }}
        onPointerDown={handlePointer} onPointerMove={(e) => { if (e.buttons > 0 || e.type === "pointermove") handlePointer(e); }}
        onPointerLeave={handleLeave} onPointerUp={handleLeave}
        onTouchStart={handlePointer} onTouchMove={handlePointer} onTouchEnd={handleLeave}>
        <defs>
          <linearGradient id={id + "-stroke"} x1="0" y1="0" x2="1" y2="0">
            <stop offset="0%" stopColor={accent} stopOpacity="0.7" />
            <stop offset="100%" stopColor={accent} />
          </linearGradient>
          <linearGradient id={id + "-area"} x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor={area1} />
            <stop offset="100%" stopColor={area2} />
          </linearGradient>
        </defs>

        {/* grid */}
        {gridYs.map((y, i) => (
          <line key={i} x1={padL} y1={y.toFixed(1)} x2={W - padR} y2={y.toFixed(1)}
            stroke="oklch(0.6 0.02 250 / 0.12)" strokeWidth="1" strokeDasharray={i === gridYs.length - 1 ? "0" : "3 5"} />
        ))}

        {/* area + line — fully static so they render in every context (preview/print/reduced-motion) */}
        <path d={areaD} fill={`url(#${id}-area)`} />
        <path d={line} fill="none" stroke={`url(#${id}-stroke)`} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />

        {/* dots (static, always visible) */}
        {pts.map((p, i) => (
          <circle key={i} cx={p.x.toFixed(2)} cy={p.y.toFixed(2)} r="2.6" fill="#fff" stroke={accent} strokeWidth="2" />
        ))}
        {/* emphasized last point with soft glow */}
        <circle cx={last.x.toFixed(2)} cy={last.y.toFixed(2)} r="5.5" fill={accent} opacity="0.18" />
        <circle cx={last.x.toFixed(2)} cy={last.y.toFixed(2)} r="4" fill={accent} stroke="#fff" strokeWidth="1.5" />

        {/* x labels — adaptive thinning, always show first & last */}
        {(function(){
          const n = labels.length;
          const step = n <= 6 ? 1 : (n <= 9 ? 2 : 3);
          return labels.map((lb, i) => {
            const show = (i % step === 0) || i === n - 1;
            if (!show) return null;
            return (
              <text key={i} x={pts[i].x.toFixed(2)} y={H - 8} textAnchor="middle"
                fontSize="10.5" fill="var(--ink-faint)" fontFamily="var(--font-disp)">{lb}</text>
            );
          });
        })()}

        {/* active point + tooltip */}
        {activeIdx !== null && (function(){
          const p = pts[activeIdx];
          const v = values[activeIdx];
          const lb = labels[activeIdx];
          const tipW = 72, tipH = 38, tipR = 9;
          let tx = p.x - tipW / 2;
          if (tx < 2) tx = 2;
          if (tx + tipW > W - 2) tx = W - tipW - 2;
          const ty = p.y - tipH - 10 < padT - 4 ? p.y + 12 : p.y - tipH - 10;
          return (
            <g>
              <line x1={p.x.toFixed(2)} y1={padT} x2={p.x.toFixed(2)} y2={baseY}
                stroke={accent} strokeWidth="1" strokeDasharray="3 3" opacity="0.45" />
              <circle cx={p.x.toFixed(2)} cy={p.y.toFixed(2)} r="7" fill={accent} opacity="0.18" />
              <circle cx={p.x.toFixed(2)} cy={p.y.toFixed(2)} r="4.5" fill={accent} stroke="#fff" strokeWidth="2" />
              <rect x={tx} y={ty} width={tipW} height={tipH} rx={tipR}
                fill="oklch(0.18 0.04 240 / 0.88)" />
              <text x={(tx + tipW / 2).toFixed(1)} y={(ty + 14).toFixed(1)} textAnchor="middle"
                fontSize="13" fontWeight="700" fill="#fff" fontFamily="var(--font-disp)"
                style={{ fontVariantNumeric: "tabular-nums" }}>{v}</text>
              <text x={(tx + tipW / 2).toFixed(1)} y={(ty + 28).toFixed(1)} textAnchor="middle"
                fontSize="10" fill="rgba(255,255,255,.7)" fontFamily="var(--font-disp)">{lb}</text>
            </g>
          );
        })()}
      </svg>

      {/* time-window stepper — เลื่อนดูข้อมูลย้อนหลังได้ */}
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, marginTop: 8, padding: "0 2px" }}>
        <button onClick={() => { setActiveIdx(null); setWinEnd((w) => Math.max(winLen - 1, w - 1)); }} disabled={!canPrev}
          aria-label="ย้อนเวลา" style={{ width: 34, height: 34, borderRadius: 11, display: "grid", placeItems: "center", flex: "0 0 auto",
            color: canPrev ? "var(--ink-soft)" : "var(--ink-faint)", background: "oklch(1 0 0 / 0.55)", border: "1px solid var(--glass-line)",
            opacity: canPrev ? 1 : 0.4, cursor: canPrev ? "pointer" : "not-allowed" }}>
          <Icon name="chev-left" size={18} />
        </button>

        <div style={{ display: "flex", alignItems: "center", gap: 7, fontSize: 12.5, fontWeight: 600, color: "var(--ink)" }}>
          <span style={{ fontVariantNumeric: "tabular-nums" }}>{labels[0]}–{labels[labels.length - 1]}</span>
          {isLive && isLiveDate ? (
            <span style={{ display: "inline-flex", alignItems: "center", gap: 4, color: "oklch(0.55 0.14 150)", fontSize: 11, fontWeight: 700 }}>
              <span style={{ width: 6, height: 6, borderRadius: "50%", background: "oklch(0.62 0.18 150)", animation: "livedot 1.4s ease-in-out infinite" }} />สด
            </span>
          ) : !isLive ? (
            <button onClick={() => { setActiveIdx(null); setWinEnd(liveEnd); }}
              style={{ fontSize: 11, fontWeight: 700, color: accent, padding: "2px 8px", borderRadius: 999,
                background: "oklch(1 0 0 / 0.6)", border: "1px solid var(--glass-line)" }}>{isLiveDate ? "ไปปัจจุบัน" : "ไปล่าสุด"}</button>
          ) : null}
        </div>

        <button onClick={() => { setActiveIdx(null); setWinEnd((w) => Math.min(liveEnd, w + 1)); }} disabled={!canNext}
          aria-label="ถัดไป" style={{ width: 34, height: 34, borderRadius: 11, display: "grid", placeItems: "center", flex: "0 0 auto",
            color: canNext ? "var(--ink-soft)" : "var(--ink-faint)", background: "oklch(1 0 0 / 0.55)", border: "1px solid var(--glass-line)",
            opacity: canNext ? 1 : 0.4, cursor: canNext ? "pointer" : "not-allowed" }}>
          <Icon name="chev-right" size={18} />
        </button>
      </div>
    </div>
  );
}

const TH_MONTHS_FULL = ["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"];
const TH_MONTHS_ABBR = ["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."];
const TH_DOW = ["อา","จ","อ","พ","พฤ","ศ","ส"];

// ปฏิทินเลือก วัน/เดือน/ปี (พ.ศ.) — premium glass popover
function CalendarPopover({ value, today, onPick, onClose }) {
  const sel = keyToDate(value);
  const todayD = keyToDate(today);
  const [mode, setMode] = useState("day"); // day | month | year
  const [viewY, setViewY] = useState(sel.getFullYear());
  const [viewM, setViewM] = useState(sel.getMonth());

  const maxKey = today;
  const isFuture = (y, m, d) => {
    const k = y + "-" + pad2(m + 1) + "-" + pad2(d);
    return k > maxKey;
  };

  const first = new Date(viewY, viewM, 1);
  const startDow = first.getDay();
  const daysInMonth = new Date(viewY, viewM + 1, 0).getDate();
  const cells = [];
  for (let i = 0; i < startDow; i++) cells.push(null);
  for (let d = 1; d <= daysInMonth; d++) cells.push(d);

  const prevMonth = () => { if (viewM === 0) { setViewM(11); setViewY(viewY - 1); } else setViewM(viewM - 1); };
  const nextMonth = () => {
    const ny = viewM === 11 ? viewY + 1 : viewY, nm = viewM === 11 ? 0 : viewM + 1;
    if (new Date(ny, nm, 1) > new Date(todayD.getFullYear(), todayD.getMonth(), 1)) return;
    setViewM(nm); setViewY(ny);
  };
  const canNextMonth = new Date(viewY, viewM, 1) < new Date(todayD.getFullYear(), todayD.getMonth(), 1);

  const pick = (d) => {
    if (isFuture(viewY, viewM, d)) return;
    onPick(viewY + "-" + pad2(viewM + 1) + "-" + pad2(d));
    onClose();
  };

  const navBtn = { width: 34, height: 34, borderRadius: 11, display: "grid", placeItems: "center", flex: "0 0 auto",
    color: "var(--ink-soft)", background: "oklch(1 0 0 / 0.5)", border: "1px solid var(--glass-line)" };

  const years = [];
  // เริ่มใช้แอปปี พ.ศ. 2569 (ค.ศ. 2026) — ไม่มีปีย้อนหลังก่อนหน้านั้น
  const START_YEAR = 2026;
  for (let y = todayD.getFullYear(); y >= START_YEAR; y--) years.push(y);

  return (
    <React.Fragment>
      <div onClick={onClose} style={{ position: "fixed", inset: 0, zIndex: 69 }} />
      <div onClick={(e) => e.stopPropagation()} className="glass" style={{ position: "absolute", top: 48, left: 0, right: 0, zIndex: 70,
        borderRadius: 20, padding: 14, background: "oklch(0.98 0.012 220 / 0.92)", boxShadow: "var(--shadow-lg)",
        animation: "calIn 0.22s cubic-bezier(0.22,1,0.36,1) both" }}>
        {/* header */}
        <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 12 }}>
          {mode === "day" && (
            <button onClick={prevMonth} style={navBtn} aria-label="เดือนก่อน"><Icon name="chev-left" size={16} /></button>
          )}
          {mode === "day" ? (
            <div style={{ flex: 1, display: "flex", gap: 6 }}>
              <button onClick={() => setMode("month")}
                style={{ flex: 1, height: 34, borderRadius: 11, display: "flex", alignItems: "center", justifyContent: "center", gap: 5,
                  color: "var(--ink)", background: "oklch(1 0 0 / 0.5)", border: "1px solid var(--glass-line)", fontWeight: 700, fontSize: 13.5 }}>
                <span>{TH_MONTHS_FULL[viewM]}</span><Icon name="chevron-down" size={13} />
              </button>
              <button onClick={() => setMode("year")}
                style={{ width: 78, height: 34, borderRadius: 11, display: "flex", alignItems: "center", justifyContent: "center", gap: 4, flex: "0 0 auto",
                  color: "var(--ink)", background: "oklch(1 0 0 / 0.5)", border: "1px solid var(--glass-line)", fontWeight: 700, fontSize: 13.5 }}>
                <span>{viewY + 543}</span><Icon name="chevron-down" size={13} />
              </button>
            </div>
          ) : (
            <button onClick={() => setMode(mode === "month" ? "year" : "day")}
              style={{ flex: 1, height: 34, borderRadius: 11, display: "flex", alignItems: "center", justifyContent: "center", gap: 6,
                color: "var(--ink)", background: "oklch(1 0 0 / 0.5)", border: "1px solid var(--glass-line)", fontWeight: 700, fontSize: 14 }}>
              {mode === "month" && <span>เลือกเดือน · {viewY + 543}</span>}
              {mode === "year" && <span>เลือกปี (พ.ศ.)</span>}
              <Icon name="chevron-down" size={14} />
            </button>
          )}
          {mode === "day" && (
            <button onClick={() => canNextMonth && nextMonth()} disabled={!canNextMonth} style={{ ...navBtn, opacity: canNextMonth ? 1 : 0.35 }} aria-label="เดือนถัดไป"><Icon name="chev-right" size={16} /></button>
          )}
        </div>

        {/* day grid */}
        {mode === "day" && (
          <React.Fragment>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(7,1fr)", gap: 2, marginBottom: 4 }}>
              {TH_DOW.map((d, i) => (
                <div key={i} style={{ textAlign: "center", fontSize: 11, fontWeight: 700, color: i === 0 ? "oklch(0.6 0.14 25)" : "var(--ink-faint)", padding: "3px 0" }}>{d}</div>
              ))}
            </div>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(7,1fr)", gap: 3 }}>
              {cells.map((d, i) => {
                if (d == null) return <div key={i} />;
                const k = viewY + "-" + pad2(viewM + 1) + "-" + pad2(d);
                const isSel = k === value;
                const isToday = k === today;
                const future = k > maxKey;
                const dow0 = (startDow + d - 1) % 7;
                return (
                  <button key={i} onClick={() => pick(d)} disabled={future}
                    style={{ height: 38, borderRadius: 11, fontSize: 13.5, fontWeight: isSel ? 700 : 600,
                      color: future ? "var(--ink-faint)" : isSel ? "#fff" : dow0 === 0 ? "oklch(0.55 0.13 25)" : "var(--ink)",
                      background: isSel ? "linear-gradient(135deg, var(--accent), var(--accent-2))" : "transparent",
                      boxShadow: isSel ? "0 6px 14px oklch(0.6 0.12 215 / 0.32)" : "none",
                      border: isToday && !isSel ? "1.5px solid oklch(0.70 0.10 210 / 0.6)" : "1.5px solid transparent",
                      opacity: future ? 0.35 : 1, cursor: future ? "not-allowed" : "pointer", position: "relative" }}>
                    {d}
                  </button>
                );
              })}
            </div>
          </React.Fragment>
        )}

        {/* month grid */}
        {mode === "month" && (
          <div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 7 }}>
            {TH_MONTHS_ABBR.map((m, i) => {
              const future = new Date(viewY, i, 1) > new Date(todayD.getFullYear(), todayD.getMonth(), 1);
              const isSel = i === viewM;
              return (
                <button key={i} onClick={() => { if (!future) { setViewM(i); setMode("day"); } }} disabled={future}
                  style={{ height: 44, borderRadius: 12, fontSize: 13.5, fontWeight: isSel ? 700 : 600,
                    color: isSel ? "#fff" : "var(--ink)", background: isSel ? "linear-gradient(135deg, var(--accent), var(--accent-2))" : "oklch(1 0 0 / 0.5)",
                    border: "1px solid var(--glass-line)", opacity: future ? 0.35 : 1, cursor: future ? "not-allowed" : "pointer" }}>
                  {m}
                </button>
              );
            })}
          </div>
        )}

        {/* year grid */}
        {mode === "year" && (
          <div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 7 }}>
            {years.map((y) => {
              const isSel = y === viewY;
              return (
                <button key={y} onClick={() => { setViewY(y); setMode("month"); }}
                  style={{ height: 44, borderRadius: 12, fontSize: 14, fontWeight: isSel ? 700 : 600,
                    color: isSel ? "#fff" : "var(--ink)", background: isSel ? "linear-gradient(135deg, var(--accent), var(--accent-2))" : "oklch(1 0 0 / 0.5)",
                    border: "1px solid var(--glass-line)" }}>
                  {y + 543}
                </button>
              );
            })}
          </div>
        )}

        {/* quick: today */}
        <button onClick={() => { onPick(today); onClose(); }} style={{ width: "100%", height: 38, borderRadius: 12, marginTop: 12,
          fontSize: 13, fontWeight: 700, color: "var(--accent-deep)", background: "oklch(0.92 0.05 210 / 0.5)", border: "1px solid var(--glass-line)" }}>
          ไปที่วันนี้
        </button>
      </div>
    </React.Fragment>
  );
}

function DateNav({ value, onChange, today }) {
  const [open, setOpen] = useState(false);
  const canNext = value < today;
  return (
    <div style={{ position: "relative" }}>
      <div className="glass" style={{ borderRadius: 16, padding: 5, display: "flex", alignItems: "center", gap: 4 }}>
        <button onClick={() => onChange(shiftDateKey(value, -1))} aria-label="วันก่อนหน้า"
          style={{ width: 36, height: 36, borderRadius: 11, display: "grid", placeItems: "center", flex: "0 0 auto",
            color: "var(--ink-soft)", background: "oklch(1 0 0 / 0.5)", border: "1px solid var(--glass-line)" }}>
          <Icon name="chev-left" size={17} />
        </button>
        <button onClick={() => setOpen((o) => !o)} style={{ flex: 1, height: 36, borderRadius: 11, display: "flex", alignItems: "center",
          justifyContent: "center", gap: 7, position: "relative", color: "var(--ink)",
          background: open ? "oklch(0.92 0.05 210 / 0.6)" : "oklch(1 0 0 / 0.5)", border: "1px solid var(--glass-line)" }}>
          <Icon name="clock" size={15} />
          <span style={{ fontSize: 13, fontWeight: 700 }}>{fmtThaiDate(value)}</span>
          {value === today && <span style={{ fontSize: 10.5, fontWeight: 700, color: "oklch(0.55 0.14 150)" }}>วันนี้</span>}
        </button>
        <button onClick={() => canNext && onChange(shiftDateKey(value, 1))} aria-label="วันถัดไป" disabled={!canNext}
          style={{ width: 36, height: 36, borderRadius: 11, display: "grid", placeItems: "center", flex: "0 0 auto",
            color: canNext ? "var(--ink-soft)" : "var(--ink-faint)", background: "oklch(1 0 0 / 0.5)", border: "1px solid var(--glass-line)",
            opacity: canNext ? 1 : 0.4, cursor: canNext ? "pointer" : "not-allowed" }}>
          <Icon name="chev-right" size={17} />
        </button>
      </div>
      {open && <CalendarPopover value={value} today={today} onPick={onChange} onClose={() => setOpen(false)} />}
    </div>
  );
}

function ExportPanel({ dateKey, dateLabel, onClose }) {
  const [busy, setBusy] = useState(false);
  const doExport = async (scope) => {
    if (busy) return;
    setBusy(true);
    let recs;
    if (scope === "date") {
      // use cached remote (admin) records for this date
      recs = dbRecordsForDate(dateKey);
    } else {
      // ทั้งหมด: ดึงทุก session จาก D1 (admin) ในคำขอเดียว
      recs = [];
      if (window.__adminToken) {
        try {
          const r = await fetch("/api/sessions", { headers: { "x-admin-token": window.__adminToken }, cache: "no-store" });
          if (r.ok) {
            const data = await r.json();
            if (data && Array.isArray(data.records)) recs = data.records;
          }
        } catch (e) {}
      }
      if (!recs.length) recs = dbLoad(); // fallback localStorage
    }
    const stamp = (scope === "date" ? dateKey : "all") + "-" + new Date().toISOString().slice(0, 10);
    const sheet1 = attendanceSheet(scope === "date" ? "ประวัติการเข้าร่วมสตรีม" : "ประวัติการเข้าร่วม (ทุกวัน)", recs);
    const sheet2 = notJoinedSheet(dateKey);
    const blob = buildXlsxBlob([sheet1, sheet2]);
    downloadBlob("teuanjai-" + stamp + ".xlsx", blob);
    setBusy(false);
    onClose();
  };
  const Opt = ({ label, sub, onClick }) => (
    <button onClick={onClick} style={{ width: "100%", display: "flex", alignItems: "center", gap: 12, padding: "13px 14px", borderRadius: 14,
      background: "oklch(1 0 0 / 0.55)", border: "1px solid var(--glass-line)", textAlign: "left" }}>
      <span style={{ width: 34, height: 34, borderRadius: 10, flex: "0 0 auto", display: "grid", placeItems: "center", color: "#fff",
        background: "linear-gradient(140deg, var(--accent), var(--accent-2))" }}><Icon name="download" size={17} /></span>
      <span style={{ flex: 1 }}>
        <span style={{ display: "block", fontSize: 14, fontWeight: 700, color: "var(--ink)" }}>{label}</span>
        <span style={{ display: "block", fontSize: 11.5, color: "var(--ink-faint)" }}>{sub}</span>
      </span>
    </button>
  );
  return (
    <div onClick={onClose} style={{ position: "absolute", inset: 0, zIndex: 60, display: "flex", flexDirection: "column", justifyContent: "flex-end",
      background: "oklch(0.4 0.04 250 / 0.34)", backdropFilter: "blur(4px)", animation: "fadeIn 0.25s both" }}>
      <div onClick={(e) => e.stopPropagation()} className="glass" style={{ borderRadius: "26px 26px 0 0",
        padding: "10px 20px calc(20px + env(safe-area-inset-bottom))", background: "oklch(0.98 0.012 220 / 0.85)",
        animation: "slideUp 0.35s cubic-bezier(0.22,1,0.36,1)" }}>
        <div style={{ width: 44, height: 5, borderRadius: 5, background: "oklch(0.7 0.02 250 / 0.3)", margin: "4px auto 14px" }} />
        <div style={{ fontFamily: "var(--font-disp)", fontSize: 17, fontWeight: 700, color: "var(--ink)", marginBottom: 3 }}>ส่งออกฐานข้อมูล</div>
        <div style={{ fontSize: 12, color: "var(--ink-faint)", marginBottom: 14 }}>ประวัติการเข้าร่วมสตรีม</div>
        <div style={{ display: "flex", flexDirection: "column", gap: 9 }}>
          <Opt label="วันที่เลือก (Excel)" sub={dateLabel} onClick={() => doExport("date")} />
          <Opt label="ทั้งหมด (Excel)" sub="ทั้งหมด" onClick={() => doExport("all")} />
        </div>
        <button onClick={onClose} style={{ ...btnGhost, marginTop: 12 }}>ปิด</button>
      </div>
    </div>
  );
}

function OverviewView({ nowTs }) {
  const today = dateKey(nowTs || Date.now());
  const [selDate, setSelDate] = useState(today);
  const [showExport, setShowExport] = useState(false);
  const [remoteTick, setRemoteTick] = useState(0);
  const bucket = Math.floor((nowTs || Date.now()) / 2000); // refresh เรียลไทม์ทุก 2 วินาที

  // ดึงข้อมูล session ของวันที่เลือกจาก D1 (cross-device) ทุก 5 วินาที
  useEffect(() => {
    let active = true;
    const pull = () => {
      if (!window.dbFetchRemote) return;
      dbFetchRemote(selDate).then((ok) => { if (active && ok) setRemoteTick((t) => t + 1); });
    };
    pull();
    const id = setInterval(pull, 5000);
    return () => { active = false; clearInterval(id); };
  }, [selDate]);

  const { kpis, hourly } = useMemo(() => {
    const recs = dbRecordsForDate(selDate);
    return { kpis: computeKPIs(recs, nowTs), hourly: computeHourly(recs, selDate, nowTs) };
    // eslint-disable-next-line
  }, [selDate, bucket, remoteTick]);

  const isToday = selDate === today;
  const lastIdx = hourly.hours.length - 1;
  const winLen = 5;
  const curHourIdx = new Date(nowTs || Date.now()).getHours() - EVENT_START_H;
  const liveEnd = isToday ? Math.max(winLen - 1, Math.min(lastIdx, curHourIdx)) : lastIdx;

  const knownActive = isToday ? hourly.active.slice(0, liveEnd + 1) : hourly.active;
  const peakVal = Math.max.apply(null, knownActive.concat([0]));
  const peakIdx = Math.max(0, knownActive.indexOf(peakVal));
  const pct = kpis.total ? Math.round((kpis.joined / kpis.total) * 100) : 0;
  const hasData = kpis.joined > 0;

  return (
    <div className="scroll" style={{ flex: 1, padding: "4px 16px 160px" }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "4px 4px 12px" }}>
        <div>
          <div style={{ fontSize: 15, fontWeight: 700, color: "var(--ink)" }}>ภาพรวมการสตรีม</div>
          <div style={{ fontSize: 11, color: "var(--ink-faint)", marginTop: 1 }}>
            {isToday ? (
              <span style={{ display: "inline-flex", alignItems: "center", gap: 5 }}>
                <span style={{ width: 6, height: 6, borderRadius: "50%", background: "oklch(0.62 0.18 150)", animation: "livedot 1.4s ease-in-out infinite" }} />
                เรียลไทม์ · {fmtTimeOnly(nowTs)}
              </span>
            ) : "ข้อมูลย้อนหลัง"}
          </div>
        </div>
        <button onClick={() => setShowExport(true)} style={{ display: "inline-flex", alignItems: "center", gap: 6, height: 38, padding: "0 14px",
          borderRadius: 13, fontSize: 13, fontWeight: 700, color: "#fff",
          background: "linear-gradient(120deg, var(--accent), var(--accent-2))", boxShadow: "0 8px 18px oklch(0.6 0.12 215 / 0.32)" }}>
          <Icon name="download" size={16} />Export
        </button>
      </div>

      {/* date navigator */}
      <div style={{ marginBottom: 14 }}>
        <DateNav value={selDate} onChange={setSelDate} today={today} />
      </div>

      {/* KPI cards 2x2 */}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 11, marginBottom: 14 }}>
        <KpiCard icon="building" label="ทั้งหมด" value={kpis.total} sub="สาขาในระบบ"
          grad="linear-gradient(140deg, oklch(0.66 0.13 232), oklch(0.70 0.12 210))" glow="oklch(0.78 0.12 225)" />
        <KpiCard icon="check" label="เข้าร่วมแล้ว" value={kpis.joined} sub={pct + "% ของทั้งหมด"}
          grad="linear-gradient(140deg, oklch(0.68 0.15 158), oklch(0.72 0.13 168))" glow="oklch(0.80 0.13 160)" />
        <KpiCard icon="xmark" label="ไม่เข้าร่วม" value={kpis.notJoined} sub="ยังไม่เข้าระบบ"
          grad="linear-gradient(140deg, oklch(0.74 0.14 60), oklch(0.72 0.15 38))" glow="oklch(0.82 0.13 55)" />
        <KpiCard icon="clock" label="เวลาเฉลี่ย" value={fmtHMS(kpis.avgMs)} valueSize={21} sub={"จาก " + kpis.sessions + " ผู้ใช้"}
          grad="linear-gradient(140deg, oklch(0.66 0.12 280), oklch(0.66 0.13 250))" glow="oklch(0.80 0.12 270)" />
      </div>

      {hasData ? (
        <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
          <LineChart id="ch2" title="ผู้ใช้งานที่ Active ช่วงเวลานั้นๆ" subtitle="จำนวนสาขาที่ใช้งานพร้อมกัน · รายชั่วโมง"
            allLabels={hourly.hours} allValues={hourly.active} liveEnd={liveEnd} winLen={winLen} isLiveDate={isToday}
            accent="oklch(0.64 0.15 162)" area1="oklch(0.68 0.14 165 / 0.32)" area2="oklch(0.68 0.14 165 / 0)"
            badge={peakVal > 0 ? "พีค " + hourly.hours[peakIdx] : null} badgeTone="oklch(0.48 0.13 165)" />
          <LineChart id="ch1" title="เข้าร่วมสะสมสะท้อนตามช่วงเวลา" subtitle="จำนวนสาขาสะสม · รายชั่วโมง"
            allLabels={hourly.hours} allValues={hourly.cumulative} liveEnd={liveEnd} winLen={winLen} isLiveDate={isToday}
            accent="oklch(0.62 0.15 232)" area1="oklch(0.66 0.14 225 / 0.34)" area2="oklch(0.66 0.14 225 / 0)"
            badge={"สะสม " + kpis.joined + " สาขา"} badgeTone="oklch(0.50 0.14 235)" />
        </div>
      ) : (
        <div className="glass" style={{ borderRadius: 22, padding: "34px 20px", textAlign: "center" }}>
          <div style={{ width: 52, height: 52, borderRadius: 16, margin: "0 auto 14px", display: "grid", placeItems: "center",
            color: "var(--ink-faint)", background: "oklch(1 0 0 / 0.5)", border: "1px solid var(--glass-line)" }}>
            <Icon name="chart" size={26} />
          </div>
          <div style={{ fontSize: 14.5, fontWeight: 700, color: "var(--ink)", marginBottom: 4 }}>ยังไม่มีข้อมูลในวันที่เลือก</div>
          <div style={{ fontSize: 12.5, color: "var(--ink-faint)", lineHeight: 1.6 }}>
            {isToday ? "เมื่อมีสาขาเข้าร่วมสตรีม ข้อมูลจะแสดงที่นี่แบบเรียลไทม์" : "เลือกวันอื่น หรือกลับมาวันนี้เพื่อดูการเข้าร่วมสด"}
          </div>
        </div>
      )}

      <div style={{ display: "flex", alignItems: "flex-start", gap: 7, marginTop: 16, padding: "0 4px", fontSize: 11, color: "var(--ink-faint)", lineHeight: 1.5 }}>
        <span style={{ flex: "0 0 auto", marginTop: 1 }}><Icon name="spark" size={12} /></span>
        <span>บันทึกการเข้าร่วมจริงจากผู้ใช้ทุกสาขาที่เข้าผ่านระบบนี้ · เชื่อมฐานข้อมูลรวมศูนย์เพื่อรวมทุกอุปกรณ์</span>
      </div>

      {showExport && <ExportPanel dateKey={selDate} dateLabel={fmtThaiDate(selDate)} onClose={() => setShowExport(false)} />}
    </div>
  );
}

(function(){ if(document.getElementById("tj-chart"))return; const s=document.createElement("style"); s.id="tj-chart";
  s.textContent=`
    @keyframes chartDraw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
    @keyframes chartFade { from { opacity: 0; } to { opacity: 1; } }
    @keyframes calIn { from { opacity: 0; transform: translateY(-8px) scale(0.98); } to { opacity: 1; transform: none; } }
    @media (prefers-reduced-motion: reduce){
      [style*="chartDraw"],[style*="chartFade"]{animation:none !important}
    }
  `;
  document.head.appendChild(s); })();

Object.assign(window, { OverviewView });
