/* ==========================================================================
 * ADMIN NEXT — components.jsx  (แยกจาก index.html เดิม; โหลดผ่าน <script type="text/babel">)
 * โหลดตามลำดับใน index.html — ทุกไฟล์แชร์ scope เดียวกัน (babel-standalone)
 * ========================================================================== */
/* ทำสีให้เข้มลงสำหรับปลายไล่เฉด (f = 0..1) */
function darkenHex(hex, f) {
  try { let h=String(hex).replace('#',''); if(h.length===3)h=h.split('').map(x=>x+x).join('');
    const n=parseInt(h,16); let r=(n>>16)&255,g=(n>>8)&255,b=n&255;
    r=Math.round(r*(1-f)); g=Math.round(g*(1-f)); b=Math.round(b*(1-f));
    return `rgb(${r},${g},${b})`; } catch(e){ return hex; }
}
/* delta = ผลต่างจริงเทียบช่วงก่อนหน้า (บาท/คน/%)
 * onColor = อยู่บนการ์ดพื้นสี → ใช้ตัวอักษรขาว (บอกขึ้น/ลงด้วยลูกศรแทนสี) */
function Delta({ d, onColor }) {
  if (!d || d.val==null) return null;
  const up = d.val>=0; const good = d.inverse ? !up : up;
  const zero = Math.abs(d.val) < (d.unit==="baht"?0.005:(d.unit==="pct"?0.05:0.5));
  const col = onColor ? "rgba(255,255,255,.95)" : (zero ? "var(--muted)" : (good?"var(--success)":"var(--danger)"));
  const a = Math.abs(d.val);
  const txt = d.unit==="baht" ? a.toLocaleString("th-TH",{minimumFractionDigits:2,maximumFractionDigits:2})+" บาท"
            : d.unit==="pct" ? a.toFixed(1)+" %"
            : a.toLocaleString("th-TH")+" คน";
  return (<span className={"inline-flex items-center gap-0.5 whitespace-nowrap text-[11px] font-semibold tnum "+(onColor?"rounded-full bg-white/20 px-1.5 py-0.5":"")} style={{color:col}}>
    <Ic d={up?I.up:I.dn} size={12} sw={2.4}/>{(up?"+":"-")+txt}</span>);
}
/* การ์ด KPI แบบไล่เฉดสีสด (ธีมสว่าง/มืด) — ใช้สีจาก tile.color, ตัวอักษรขาว, มีไอคอนใหญ่จางๆ มุมล่าง */
function KpiTile({ tile, loading }) {
  if (loading) return (<div className="rounded-2xl border border-line bg-surface p-4 shadow-sm">
    <div className="space-y-3"><div className="skel h-3.5 w-24"/><div className="skel h-7 w-28"/><div className="skel h-4 w-20"/></div></div>);
  const c = tile.color || "#6366F1";
  const grad = `linear-gradient(135deg, ${c} 0%, ${darkenHex(c,0.24)} 100%)`;
  return (<div className="relative overflow-hidden rounded-2xl p-4 text-white shadow-md" style={{background:grad}}>
    <div className="pointer-events-none absolute -bottom-4 -right-3 opacity-[0.16]"><Ic d={I[tile.icon]} size={92} sw={1.5}/></div>
    <div className="relative">
      <div className="flex items-center gap-2">
        <span className="grid h-7 w-7 shrink-0 place-items-center rounded-lg bg-white/20"><Ic d={I[tile.icon]} size={15} sw={2}/></span>
        <div className="truncate text-[13px] font-medium text-white/90">{tile.k}</div>
      </div>
      <div className="mt-2.5 text-[26px] font-bold leading-none tracking-tight tnum">{tile.v}</div>
      <div className="mt-2 flex items-center justify-between gap-2">
        <div className="truncate text-[11px] text-white/75">{tile.live?"อัปเดตทุกครั้งที่รีเฟรช":(tile.d?"เทียบช่วงก่อนหน้า":tile.sub||"")}</div>
        {tile.live ? <span className="inline-flex items-center gap-1 whitespace-nowrap rounded-full bg-white/20 px-1.5 py-0.5 text-[11px] font-semibold text-white"><span className="h-1.5 w-1.5 rounded-full bg-white"/>live</span> : <Delta d={tile.d} onColor/>}
      </div>
    </div>
  </div>);
}
function DepWdrBar({ dep, wdr }) {
  const d=N(dep), w=N(wdr), max=Math.max(d,w,1);
  const Row=({label,val,c})=>(<div>
    <div className="mb-1 flex items-center justify-between text-xs"><span className="text-fg-2">{label}</span><span className="font-semibold tnum">{baht(val)}</span></div>
    <div className="h-2.5 overflow-hidden rounded-full bg-sunken"><div className="h-full rounded-full" style={{width:(val/max*100)+"%",background:c}}/></div>
  </div>);
  return (<div className="space-y-3"><Row label="ยอดฝาก" val={d} c="var(--success)"/><Row label="ยอดถอน" val={w} c="var(--danger)"/></div>);
}
function Bars7d({ dep, wdr }) {
  const days = (dep||[]).map(d=>({date:d.date, dep:N(d.amount)/100, wdr:0}));
  (wdr||[]).forEach((w,i)=>{ if(days[i]) days[i].wdr=N(w.amount)/100; });
  const max = Math.max(1, ...days.map(d=>Math.max(d.dep,d.wdr)));
  const W=560,H=210,padL=50,padR=12,padT=12,padB=30,iw=W-padL-padR,ih=H-padT-padB,n=days.length||1;
  const y=(v)=>padT+ih-(v/max)*ih, gx=(i)=>padL+(i+0.5)*(iw/n), bw=Math.min(13,(iw/n)/3);
  const dow=["อา","จ","อ","พ","พฤ","ศ","ส"];
  const lbl=(ds)=>{ try{const d=new Date(ds+"T00:00:00+07:00"); return dow[d.getDay()]+" "+ds.slice(8,10);}catch{return ds.slice(8,10);} };
  const kfmt=(t)=> t>=1000000?(t/1000000).toFixed(1)+"M":t>=1000?Math.round(t/1000)+"k":String(Math.round(t));
  const ticks=[0,.25,.5,.75,1].map(f=>max*f);
  return (<div className="overflow-x-auto"><svg viewBox={`0 0 ${W} ${H}`} className="w-full min-w-[520px]" role="img" aria-label="ฝาก-ถอน 7 วัน">
    {ticks.map((t,i)=>(<g key={i}><line x1={padL} x2={W-padR} y1={y(t)} y2={y(t)} stroke="var(--line)" strokeWidth="1"/><text x={padL-8} y={y(t)+3} textAnchor="end" fontSize="9.5" fill="var(--faint)">{kfmt(t)}</text></g>))}
    {days.map((d,i)=>(<g key={i}>
      <rect x={gx(i)-bw-1.5} y={y(d.dep)} width={bw} height={Math.max(0,y(0)-y(d.dep))} rx="2.5" fill="var(--success)"/>
      <rect x={gx(i)+1.5} y={y(d.wdr)} width={bw} height={Math.max(0,y(0)-y(d.wdr))} rx="2.5" fill="var(--danger)" opacity="0.9"/>
      <text x={gx(i)} y={H-10} textAnchor="middle" fontSize="9.5" fill="var(--muted)">{lbl(d.date)}</text>
    </g>))}
  </svg></div>);
}
function ChannelBreak({ rows }) {
  const data=(rows||[]).map(r=>({label:CHANNEL_LABEL[r.channel]||r.channel||"อื่นๆ", amount:N(r.amount)/100})).filter(d=>d.amount>0);
  const total=data.reduce((s,d)=>s+d.amount,0)||1;
  const COLORS=["var(--accent)","#0EA5E9","#059669","var(--warn)","#EC4899","#8B5CF6","#14B8A6"];
  const R=52,C=2*Math.PI*R; let off=0;
  if(!data.length) return <div className="py-10 text-center text-sm text-muted">ไม่มีข้อมูล</div>;
  return (<div className="flex items-center gap-4">
    <svg width="120" height="120" viewBox="0 0 120 120" role="img" aria-label="สัดส่วนช่องทางฝาก">
      <circle cx="60" cy="60" r={R} fill="none" stroke="var(--sunken)" strokeWidth="16"/>
      {data.map((d,i)=>{const len=C*d.amount/total; const el=<circle key={i} cx="60" cy="60" r={R} fill="none" stroke={COLORS[i%COLORS.length]} strokeWidth="16" strokeDasharray={`${len} ${C-len}`} strokeDashoffset={-off} transform="rotate(-90 60 60)"/>; off+=len; return el;})}
      <text x="60" y="57" textAnchor="middle" fontSize="13" fontWeight="700" fill="var(--fg)">{money2(total*100).replace(" บาท","")}</text>
      <text x="60" y="72" textAnchor="middle" fontSize="9" fill="var(--muted)">รวม (บาท)</text>
    </svg>
    <div className="min-w-0 flex-1 space-y-1.5">{data.map((d,i)=>(<div key={i} className="flex items-center gap-2 text-xs"><span className="h-2.5 w-2.5 shrink-0 rounded-sm" style={{background:COLORS[i%COLORS.length]}}/><span className="truncate text-fg-2">{d.label}</span><span className="ml-auto font-medium tnum">{(d.amount/total*100).toFixed(0)}%</span></div>))}</div>
  </div>);
}
function MiniList({ title, icon, color, children, sub }) {
  return (<div className="rounded-xl border border-line bg-surface shadow-sm">
    <div className="flex items-center gap-2 border-b border-line px-4 py-3">
      <span className="grid h-6 w-6 place-items-center rounded-lg" style={{background:color+"1A",color}}><Ic d={I[icon]} size={14} sw={2}/></span>
      <div className="text-sm font-semibold">{title}</div>{sub && <span className="ml-auto text-xs text-muted tnum">{sub}</span>}
    </div>
    <div className="divide-y divide-line-soft">{children}</div>
  </div>);
}
function SlipModal({ img, onClose }) {
  return (<div className="fixed inset-0 z-[80] flex items-center justify-center p-4 fade-in" onMouseDown={onClose}>
    <div className="absolute inset-0 bg-black/70"/>
    <div className="pop relative max-h-[90vh] w-full max-w-md overflow-hidden rounded-2xl border border-line bg-surface2 shadow-lg" onMouseDown={(e)=>e.stopPropagation()}>
      <div className="flex items-center gap-2 border-b border-line px-4 py-3"><Ic d={I.eye} size={16}/><div className="text-sm font-semibold">สลิปที่ลูกค้าอัปโหลด</div>
        <a href={img} target="_blank" rel="noopener noreferrer" className="ml-auto text-xs font-medium text-accent">เปิดเต็มจอ</a>
        <button onClick={onClose} className="grid h-7 w-7 place-items-center rounded-lg text-muted hover:bg-sunken"><Ic d={I.x} size={15}/></button></div>
      <div className="max-h-[75vh] overflow-auto bg-black/20 p-3"><img src={img} alt="slip" className="mx-auto max-h-[70vh] w-auto rounded-lg" onError={(e)=>{e.currentTarget.style.display='none';}}/></div>
    </div>
  </div>);
}
function ConfirmModal({ kind, row, onClose, onConfirm, busy, titleText, descText, hideInput }) {
  const approve = kind==="approve";
  const showInput = !hideInput;
  const [val,setVal]=useState(approve ? (row&&row.username||"") : "");
  const inp="mt-1 h-10 w-full rounded-lg border border-line bg-sunken px-3 text-sm text-fg placeholder:text-faint focus:border-accent focus:bg-surface focus:outline-none focus:ring-4 focus:ring-[var(--accent-ring)]";
  const title = titleText || (approve?"อนุมัติรายการฝาก?":"ปฏิเสธรายการฝาก?");
  const desc = descText || (approve?"ระบบจะเติมเครดิตเข้ากระเป๋าลูกค้าทันที หากจับคู่ยูสผิด แก้ยูสผู้รับด้านล่างก่อนอนุมัติ":"ระบุเหตุผลการปฏิเสธ (จะบันทึกไว้)");
  return (<div className="fixed inset-0 z-[80] flex items-center justify-center p-4 fade-in" onMouseDown={onClose}>
    <div className="absolute inset-0 bg-black/55"/>
    <div className="pop relative w-full max-w-sm rounded-2xl border border-line bg-surface2 p-5 shadow-lg" onMouseDown={(e)=>e.stopPropagation()}>
      <div className="text-base font-semibold">{title}</div>
      <p className="mt-1 text-xs text-muted">{desc}</p>
      {showInput && <label className="mt-3 block"><span className="text-xs font-medium text-fg-2">{approve?"Username ผู้รับเครดิต":"เหตุผล"}</span>
        <input className={inp} value={val} onChange={(e)=>setVal(e.target.value)} placeholder={approve?"username":"เช่น สลิปไม่ชัดเจน"}/></label>}
      <div className="mt-4 flex items-center justify-end gap-2">
        <button onClick={onClose} className="h-9 rounded-lg border border-line bg-surface px-3 text-sm font-medium text-fg-2 hover:bg-sunken">ยกเลิก</button>
        <button disabled={busy} onClick={()=>onConfirm(showInput?val:"")} className="h-9 rounded-lg px-4 text-sm font-semibold text-white disabled:opacity-50" style={{background:approve?"var(--success)":"var(--danger)"}}>{busy?"กำลังทำ…":(approve?"อนุมัติ":"ปฏิเสธ")}</button>
      </div>
    </div>
  </div>);
}
function SimpleConfirmModal({ title, msg, onOk, onCancel, busy }) {
  return (<div className="fixed inset-0 z-[80] flex items-center justify-center p-4 fade-in" onMouseDown={onCancel}>
    <div className="absolute inset-0 bg-black/55"/>
    <div className="pop relative w-full max-w-sm rounded-2xl border border-line bg-surface2 p-5 shadow-lg" onMouseDown={(e)=>e.stopPropagation()}>
      <div className="text-base font-semibold">{title||"ยืนยัน"}</div>
      <p className="mt-1 text-sm text-muted">{msg}</p>
      <div className="mt-4 flex items-center justify-end gap-2">
        <button onClick={onCancel} className="h-9 rounded-lg border border-line bg-surface px-3 text-sm font-medium text-fg-2 hover:bg-sunken">ยกเลิก</button>
        <button disabled={busy} onClick={onOk} className="h-9 rounded-lg bg-danger px-4 text-sm font-semibold text-white disabled:opacity-50 hover:opacity-90">{busy?"กำลังทำ…":"ยืนยัน"}</button>
      </div>
    </div>
  </div>);
}
function KV({ k, children }) { return (<div className="flex gap-3 py-1.5 text-sm"><span className="w-28 shrink-0 text-muted">{k}</span><span className="min-w-0 flex-1 break-words font-medium text-fg-2">{children}</span></div>); }
function DetailDrawer({ title, subtitle, tone, rows, slip, onClose }) {
  useEffect(()=>{const h=(e)=>e.key==="Escape"&&onClose(); document.addEventListener("keydown",h); return ()=>document.removeEventListener("keydown",h);},[onClose]);
  return (<div className="fixed inset-0 z-50 flex justify-end fade-in">
    <div className="absolute inset-0 bg-black/45" onClick={onClose}/>
    <div className="drawer-in relative flex h-full w-full max-w-md flex-col border-l border-line bg-surface shadow-lg">
      <div className="flex h-16 items-center gap-3 border-b border-line px-5">
        <div className="min-w-0"><div className="truncate text-[15px] font-semibold">{title}</div>{subtitle && <div className="truncate text-xs text-muted">{subtitle}</div>}</div>
        {tone && <span className="ml-auto"><Badge tone={tone.c}>{tone.t}</Badge></span>}
        <button onClick={onClose} className={"grid h-9 w-9 place-items-center rounded-lg text-muted hover:bg-sunken hover:text-fg "+(tone?"":"ml-auto")}><Ic d={I.x}/></button>
      </div>
      <div className="flex-1 overflow-y-auto p-5">
        {slip && <div className="mb-4"><div className="mb-1.5 text-xs font-medium text-fg-2">สลิป</div>
          <a href={slip} target="_blank" rel="noopener noreferrer"><img src={slip} alt="slip" className="w-full rounded-lg border border-line bg-black/10" style={{maxHeight:360,objectFit:"contain"}} onError={(e)=>{e.currentTarget.style.display='none';}}/></a></div>}
        <div className="divide-y divide-line-soft">{rows.filter(r=>r&&r[1]!=null&&r[1]!=="").map(([k,v],i)=><KV key={i} k={k}>{v}</KV>)}</div>
      </div>
    </div>
  </div>);
}

/* ---------- placeholder หน้าอื่น ---------- */
function ComingSoon({ label }) {
  return (<div className="rounded-xl border border-dashed border-line bg-surface p-10 text-center shadow-sm">
    <div className="mx-auto mb-3 grid h-12 w-12 place-items-center rounded-2xl bg-accent-soft text-accent"><Duo name="grid" size={26}/></div>
    <div className="text-base font-semibold">{label}</div>
    <p className="mx-auto mt-1 max-w-md text-sm text-muted">หน้านี้กำลังต่อข้อมูลจริงในเฟสถัดไป — โครง UI พร้อมแล้ว ระหว่างนี้ใช้งานหน้าเดิมได้ที่ <a href="/admin/" className="font-medium text-accent">/admin</a></p>
  </div>);
}

function Toasts({ items }) {
  return (<div className="pointer-events-none fixed bottom-5 left-1/2 z-[70] flex -translate-x-1/2 flex-col items-center gap-2">
    {items.map(t=>{ const err=t.tone==="error"; return (<div key={t.id} role={err?"alert":"status"} className="pop pointer-events-auto flex max-w-[min(560px,calc(100vw-32px))] items-center gap-2.5 rounded-xl border bg-surface2 px-4 py-2.5 text-sm shadow-lg" style={{borderColor:err?"var(--danger)":"var(--line)"}}>
      <span className="grid h-5 w-5 shrink-0 place-items-center rounded-full text-white" style={{background:err?"var(--danger)":"var(--success)"}}><Ic d={err?I.x:I.check} size={13} sw={3}/></span><span className="font-medium" style={err?{color:"var(--danger)"}:undefined}>{t.msg}</span>
    </div>); })}
  </div>);
}

/* ---------- ประตู auth ---------- */
function authErrorMessage(error) {
  const message = String(error?.message || "");
  if (message === "invalid_credentials") return "ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง";
  if (message === "account_suspended") return "บัญชีนี้ถูกระงับการใช้งาน กรุณาติดต่อผู้ดูแลระบบ";
  if (message === "invalid_2fa_code") return "รหัสยืนยันไม่ถูกต้อง กรุณาตรวจสอบแล้วลองอีกครั้ง";
  if (message === "invalid_2fa_challenge") return "คำขอยืนยันหมดอายุ กรุณากลับไปเข้าสู่ระบบใหม่";
  if (message === "admin_2fa_not_configured") return "บัญชียังไม่ได้ตั้งค่า 2FA กรุณาติดต่อผู้ดูแลระบบ";
  if (message === "invalid_login_response") return "ระบบตอบกลับข้อมูลเข้าสู่ระบบไม่ครบ กรุณาลองอีกครั้ง";
  if (message === "storage_unavailable") return "เบราว์เซอร์ไม่สามารถบันทึกเซสชันได้ กรุณาเปิดใช้งานพื้นที่จัดเก็บข้อมูลแล้วลองอีกครั้ง";
  if (message.startsWith("login_not_allowed_outside_hours:")) return "ยังไม่อยู่ในช่วงเวลาที่อนุญาตให้เข้าสู่ระบบ";
  if (message.startsWith("admin_ip_not_whitelisted:")) return "ไม่สามารถเข้าสู่ระบบจากเครือข่ายนี้ได้ กรุณาติดต่อผู้ดูแลระบบ";
  if (message === "Failed to fetch" || message === "NetworkError when attempting to fetch resource.") {
    return "เชื่อมต่อระบบไม่ได้ กรุณาตรวจสอบอินเทอร์เน็ตแล้วลองอีกครั้ง";
  }
  return "เข้าสู่ระบบไม่สำเร็จ กรุณาลองอีกครั้ง";
}

/* ---------- สร้างแอดมินคนแรก (ยังไม่มี role=admin ในระบบ) ---------- */
const AUTH_FIRST_ADMIN_ERRORS = {
  invalid_admin_key: "ADMIN_API_KEY ไม่ถูกต้อง",
  admin_already_exists: "มีแอดมินในระบบแล้ว กรุณาเข้าสู่ระบบตามปกติ",
  invalid_username: "ชื่อผู้ใช้ใช้ได้เฉพาะ a-z A-Z 0-9 _ . - ยาว 3-32 ตัว",
  username_reserved_for_env_admin_dev: "ชื่อผู้ใช้นี้ใช้ไม่ได้ กรุณาใช้ชื่ออื่น",
  password_min_8: "รหัสผ่านต้องมีอย่างน้อย 8 ตัวอักษร",
};

// ยิง fetch ตรง ไม่ผ่าน api() — api() จะเอา ADMIN_KEY ใน localStorage มาทับคีย์ที่เพิ่งกรอก
async function authCreateFirstAdmin(adminKey, account) {
  let r;
  try {
    r = await fetch("/api/admin/bootstrap/create-admin", {
      method: "POST",
      headers: { "Content-Type": "application/json", "x-admin-key": adminKey },
      body: JSON.stringify(account),
    });
  } catch (_netErr) {
    throw nxApiError(0, null);
  }
  const data = await r.json().catch(() => null);
  if (!r.ok) throw nxApiError(r.status, data);
  return data;
}

const AUTH_INPUT_CLS = "h-11 w-full rounded-lg border border-line bg-surface px-3 text-sm outline-none transition focus:border-accent focus:ring-2 focus:ring-accent/20";

function AuthField({ id, label, hint, ...inputProps }) {
  return (<div>
    <label htmlFor={id} className="mb-1.5 block text-sm font-medium">{label}</label>
    <input id={id} className={AUTH_INPUT_CLS} {...inputProps}/>
    {hint && <p className="mt-1 text-xs text-muted">{hint}</p>}
  </div>);
}

function FirstAdminSetupForm({ onCreated }) {
  const [form, setForm] = useState({ adminKey: "", username: "", fullName: "", password: "", confirm: "" });
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState("");
  const field = (key) => ({ value: form[key], onChange: e => setForm(f => ({ ...f, [key]: e.target.value })) });

  const submit = async (event) => {
    event.preventDefault();
    if (form.password.length < 8) { setError(AUTH_FIRST_ADMIN_ERRORS.password_min_8); return; }
    if (form.password !== form.confirm) { setError("รหัสผ่านสองช่องไม่ตรงกัน"); return; }
    setBusy(true);
    setError("");
    try {
      const challenge = await authCreateFirstAdmin(form.adminKey.trim(), {
        username: form.username.trim(),
        password: form.password,
        fullName: form.fullName.trim() || form.username.trim(),
      });
      onCreated(challenge);
    } catch (e) {
      setError(AUTH_FIRST_ADMIN_ERRORS[e.message] || e.message || "สร้างแอดมินไม่สำเร็จ กรุณาลองอีกครั้ง");
    } finally {
      setBusy(false);
    }
  };

  return (<form onSubmit={submit} className="space-y-4">
    <div className="rounded-xl border border-line bg-sunken px-4 py-3 text-sm text-fg-2">
      ยังไม่มีแอดมินในระบบ — สร้าง <b>Owner</b> (แอดมินคนแรก) ด้วย ADMIN_API_KEY
    </div>
    <AuthField id="first-admin-key" label="ADMIN_API_KEY" type="password" required autoFocus autoComplete="off" placeholder="วาง ADMIN_API_KEY" {...field("adminKey")}/>
    <AuthField id="first-admin-username" label="ชื่อผู้ใช้ Owner" type="text" required autoComplete="username" placeholder="เช่น admin" hint="a-z A-Z 0-9 _ . - ยาว 3-32 ตัว" {...field("username")}/>
    <AuthField id="first-admin-fullname" label="ชื่อที่แสดง (ไม่บังคับ)" type="text" autoComplete="name" placeholder="เช่น เจ้าของเว็บ" {...field("fullName")}/>
    <AuthField id="first-admin-password" label="รหัสผ่าน" type="password" required autoComplete="new-password" placeholder="อย่างน้อย 8 ตัว" {...field("password")}/>
    <AuthField id="first-admin-confirm" label="ยืนยันรหัสผ่าน" type="password" required autoComplete="new-password" placeholder="กรอกรหัสผ่านอีกครั้ง" {...field("confirm")}/>
    {error && <p role="alert" className="rounded-lg bg-danger-soft px-3 py-2 text-sm text-danger">{error}</p>}
    <button type="submit" disabled={busy} className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg bg-accent px-4 text-sm font-semibold text-accent-fg shadow-sm transition-colors hover:bg-accent-h disabled:cursor-not-allowed disabled:opacity-60">
      {busy ? "กำลังสร้าง…" : "สร้างแอดมินคนแรก"}
    </button>
  </form>);
}

function AuthGate({ reason, onAuthenticated }) {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const [challenge, setChallenge] = useState(null);
  const [code, setCode] = useState("");
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState("");
  const [showReason, setShowReason] = useState(true);
  const [setupMode, setSetupMode] = useState(false);

  useEffect(() => {
    let alive = true;
    fetch("/api/admin/bootstrap/status")
      .then(r => r.json())
      .then(st => { if (alive && st?.hasAdmin === false) setSetupMode(true); })
      .catch(() => {});
    return () => { alive = false; };
  }, []);

  const firstAdminCreated = (result) => {
    setSetupMode(false);
    setError("");
    setChallenge(result);
  };

  const signIn = async (event) => {
    event.preventDefault();
    setBusy(true);
    setError("");
    setShowReason(false);
    try {
      const result = await api("/login", { method: "POST", body: { username, password } });
      if (result.requires2fa) {
        setChallenge(result);
        return;
      }
      onAuthenticated(result);
    } catch (e) {
      setError(authErrorMessage(e));
    } finally {
      setBusy(false);
    }
  };

  const verifyCode = async (event) => {
    event.preventDefault();
    const totpCode = code.replace(/\s+/g, "");
    if (!/^\d{6}$/.test(totpCode)) {
      setError("กรุณากรอกรหัสจากแอป Authenticator จำนวน 6 หลัก");
      return;
    }
    setBusy(true);
    setError("");
    try {
      const result = await api("/login/2fa", {
        method: "POST",
        body: { challengeToken: challenge.challengeToken, totpCode },
      });
      onAuthenticated(result);
    } catch (e) {
      setError(authErrorMessage(e));
    } finally {
      setBusy(false);
    }
  };

  const backToLogin = () => {
    setChallenge(null);
    setCode("");
    setError("");
  };

  return (<div className="flex min-h-full items-center justify-center px-4 py-10 sm:px-6">
    <div className="w-full max-w-md rounded-2xl border border-line bg-surface p-6 shadow-lg sm:p-8">
      <div className="mb-7 text-center">
        <div className="mx-auto mb-4 grid h-12 w-12 place-items-center rounded-xl bg-accent text-accent-fg shadow-sm">
          <Ic d={["M12 3 4.5 6v5.5c0 4.6 3.2 7.9 7.5 9.5 4.3-1.6 7.5-4.9 7.5-9.5V6L12 3z","m9 12 2 2 4-4"]} size={23} sw={1.7}/>
        </div>
        <h1 className="text-xl font-semibold tracking-tight">{setupMode && !challenge ? "สร้างแอดมินคนแรก" : "เข้าสู่ระบบผู้ดูแล"}</h1>
        <p className="mt-1.5 text-sm text-muted">
          {challenge ? "ยืนยันตัวตนสองขั้นตอนเพื่อเข้าใช้งาน" : setupMode ? "ตั้งค่าบัญชี Owner สำหรับจัดการเว็บไซต์" : "เข้าสู่ระบบเพื่อจัดการเว็บไซต์"}
        </p>
      </div>

      {!challenge && setupMode ? (
        <FirstAdminSetupForm onCreated={firstAdminCreated}/>
      ) : challenge ? (
        <form onSubmit={verifyCode} className="space-y-4">
          {challenge.setupRequired && <div className="rounded-xl border border-line bg-sunken p-4 text-center">
            <p className="text-sm font-medium">ตั้งค่า Google Authenticator</p>
            <p className="mt-1 text-xs leading-5 text-muted">สแกน QR Code ด้วยแอป Authenticator แล้วกรอกรหัส 6 หลัก</p>
            {challenge.qrCodeDataUrl && <img src={challenge.qrCodeDataUrl} alt="QR Code สำหรับตั้งค่า Authenticator" className="mx-auto mt-3 h-40 w-40 rounded-lg bg-white p-2"/>}
            {challenge.manualKey && <code className="mono mt-3 block break-all rounded-lg border border-line bg-surface px-3 py-2 text-xs select-all">{challenge.manualKey}</code>}
          </div>}
          {!challenge.setupRequired && <div className="rounded-xl border border-line bg-sunken px-4 py-3 text-sm text-fg-2">
            เปิดแอป Google Authenticator แล้วกรอกรหัสยืนยันของบัญชีนี้
          </div>}
          <div>
            <label htmlFor="admin-login-totp" className="mb-1.5 block text-sm font-medium">รหัสยืนยัน 2FA</label>
            <input
              id="admin-login-totp"
              autoFocus
              required
              type="text"
              inputMode="numeric"
              autoComplete="one-time-code"
              maxLength={6}
              value={code}
              onChange={e=>setCode(e.target.value.replace(/\D/g,"").slice(0,6))}
              placeholder="รหัส 6 หลัก"
              className="h-11 w-full rounded-lg border border-line bg-surface px-3 text-center text-lg tracking-[0.3em] outline-none transition focus:border-accent focus:ring-2 focus:ring-accent/20"
            />
          </div>
          {error && <p role="alert" className="rounded-lg bg-danger-soft px-3 py-2 text-sm text-danger">{error}</p>}
          <button type="submit" disabled={busy} className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg bg-accent px-4 text-sm font-semibold text-accent-fg shadow-sm transition-colors hover:bg-accent-h disabled:cursor-not-allowed disabled:opacity-60">
            {busy ? "กำลังตรวจสอบ…" : "ยืนยันและเข้าสู่ระบบ"}
          </button>
          <button type="button" disabled={busy} onClick={backToLogin} className="h-10 w-full rounded-lg px-4 text-sm font-medium text-fg-2 transition-colors hover:bg-sunken disabled:opacity-60">
            กลับไปกรอกชื่อผู้ใช้และรหัสผ่าน
          </button>
        </form>
      ) : (
        <form onSubmit={signIn} className="space-y-4">
          <div>
            <label htmlFor="admin-login-username" className="mb-1.5 block text-sm font-medium">ชื่อผู้ใช้</label>
            <input
              id="admin-login-username"
              autoFocus
              required
              type="text"
              autoComplete="username"
              value={username}
              onChange={e=>setUsername(e.target.value)}
              placeholder="กรอกชื่อผู้ใช้"
              className="h-11 w-full rounded-lg border border-line bg-surface px-3 text-sm outline-none transition focus:border-accent focus:ring-2 focus:ring-accent/20"
            />
          </div>
          <div>
            <label htmlFor="admin-login-password" className="mb-1.5 block text-sm font-medium">รหัสผ่าน</label>
            <input
              id="admin-login-password"
              required
              type="password"
              autoComplete="current-password"
              value={password}
              onChange={e=>setPassword(e.target.value)}
              placeholder="กรอกรหัสผ่าน"
              className="h-11 w-full rounded-lg border border-line bg-surface px-3 text-sm outline-none transition focus:border-accent focus:ring-2 focus:ring-accent/20"
            />
          </div>
          {(error || (showReason && reason)) && <p role="alert" className="rounded-lg bg-danger-soft px-3 py-2 text-sm text-danger">{error || reason}</p>}
          <button type="submit" disabled={busy} className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg bg-accent px-4 text-sm font-semibold text-accent-fg shadow-sm transition-colors hover:bg-accent-h disabled:cursor-not-allowed disabled:opacity-60">
            {busy ? "กำลังเข้าสู่ระบบ…" : "เข้าสู่ระบบ"}
          </button>
        </form>
      )}
      <p className="mt-6 text-center text-xs text-muted">การเข้าถึงสำหรับผู้ดูแลระบบเท่านั้น</p>
    </div>
  </div>);
}
